Skip to content

Commit 0de3fd6

Browse files
moste00andrykonchin
authored andcommitted
Support optional level argument for File.dirname method
1 parent 8d6a531 commit 0de3fd6

File tree

4 files changed

+65
-29
lines changed

4 files changed

+65
-29
lines changed

spec/ruby/core/file/dirname_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@
2525
it "raises ArgumentError if the level is negative" do
2626
-> {File.dirname('/home/jason', -1)}.should raise_error(ArgumentError)
2727
end
28+
29+
it "returns the exact same object when passed 0 as the level" do
30+
obj = "path"
31+
File.dirname(obj,0).should .equal?(obj)
32+
33+
obj = mock("to_path")
34+
def obj.to_path
35+
"path"
36+
end
37+
File.dirname(obj,0).should .equal?(obj)
38+
39+
obj = Object.new
40+
File.dirname(obj,0).should .equal?(obj)
41+
end
42+
43+
it "returns / when passed number of levels exceeds the number of segments in the path" do
44+
(3..100).each { |level|
45+
File.dirname("/home/jason", level).should == '/'
46+
}
47+
end
2848
end
2949

3050
it "returns a String" do

spec/tags/core/file/dirname_tags.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/main/ruby/truffleruby/core/file.rb

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -415,37 +415,26 @@ def self.last_nonslash(path, start = nil)
415415
# the separator used on the local file system.
416416
#
417417
# File.dirname("/home/gumby/work/ruby.rb") #=> "/home/gumby/work"
418-
def self.dirname(path)
419-
path = Truffle::Type.coerce_to_path(path)
420-
421-
# edge case
422-
return +'.' if path.empty?
423-
424-
slash = '/'
425-
426-
# pull off any /'s at the end to ignore
427-
chunk_size = last_nonslash(path)
428-
return +'/' unless chunk_size
429-
430-
if pos = Primitive.find_string_reverse(path, slash, chunk_size)
431-
return +'/' if pos == 0
432-
433-
path = path.byteslice(0, pos)
434-
435-
return +'/' if path == '/'
436-
437-
return path unless path.end_with? slash
418+
def self.dirname(path,num_levels=1)
419+
if num_levels < 0
420+
raise ArgumentError, "level can't be negative"
421+
end
422+
#This must happen before the type coercion to string below, because File.dirname accepts any objects which can respond to a :to_path message
423+
#If path was such an object, File.dirname(obj,0) returns it as-is without conversion to string
424+
#Tricky, but that's how Matz ruby behave
425+
if num_levels == 0
426+
return path
427+
end
438428

439-
# prune any trailing /'s
440-
idx = last_nonslash(path, pos)
429+
path = Truffle::Type.coerce_to_path(path)
441430

442-
# edge case, only /'s, return /
443-
return +'/' unless idx
431+
num_levels.times do
432+
# edge case
433+
return +'.' if path.empty?
444434

445-
return path.byteslice(0, idx - 1)
435+
path = Truffle::FileOperations.remove_last_segment_from_path(path)
446436
end
447-
448-
+'.'
437+
path
449438
end
450439

451440
##

src/main/ruby/truffleruby/core/truffle/file_operations.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,34 @@ def self.expand_path(path, dir, expand_tilde)
7878
def self.exist?(path)
7979
Truffle::POSIX.truffleposix_stat_mode(path) > 0
8080
end
81+
82+
#Helper to File.dirname, extracts the directory of a path (doesn't need to handle the empty string)
83+
#This was the original File.dirname, but ruby 3.1 made File.dirname take an optional number of times to do its logic
84+
def self.remove_last_segment_from_path(path)
85+
slash = '/'
86+
# pull off any /'s at the end to ignore
87+
chunk_size = File.last_nonslash(path)
88+
return +'/' unless chunk_size
89+
90+
if pos = Primitive.find_string_reverse(path, slash, chunk_size)
91+
return +'/' if pos == 0
92+
93+
path = path.byteslice(0, pos)
94+
95+
return +'/' if path == '/'
96+
97+
return path unless path.end_with? slash
98+
99+
# prune any trailing /'s
100+
idx = File.last_nonslash(path, pos)
101+
102+
# edge case, only /'s, return /
103+
return +'/' unless idx
104+
105+
return path.byteslice(0, idx - 1)
106+
end
107+
108+
+'.'
109+
end
81110
end
82111
end

0 commit comments

Comments
 (0)