Skip to content

Commit 550d47b

Browse files
committed
[GR-40333] Ruby 3.1 Support optional level argument for File.dirname method
PullRequest: truffleruby/3703
2 parents 5e81941 + 18d7445 commit 550d47b

File tree

6 files changed

+83
-53
lines changed

6 files changed

+83
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Compatibility:
9393
* Update to JCodings 1.0.58 and Joni 2.1.44 (@eregon).
9494
* Add `MatchData#match` and `MatchData#match_length` (#2733, @horakivo).
9595
* Add `StructClass#keyword_init?` method (#2377, @moste00).
96+
* Support optional `level` argument for `File.dirname` method (#2733, @moste00).
9697

9798
Performance:
9899

spec/ruby/core/file/dirname_spec.rb

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,33 @@
1212
end
1313

1414
ruby_version_is '3.1' do
15-
it "returns all the components of filename except the last parts by the level" do
16-
File.dirname('/home/jason', 2).should == '/'
17-
File.dirname('/home/jason/poot.txt', 2).should == '/home'
18-
end
19-
20-
it "returns the same string if the level is 0" do
21-
File.dirname('poot.txt', 0).should == 'poot.txt'
22-
File.dirname('/', 0).should == '/'
23-
end
24-
25-
it "raises ArgumentError if the level is negative" do
26-
-> {File.dirname('/home/jason', -1)}.should raise_error(ArgumentError)
15+
context "when level is passed" do
16+
it "returns all the components of filename except the last parts by the level" do
17+
File.dirname('/home/jason', 2).should == '/'
18+
File.dirname('/home/jason/poot.txt', 2).should == '/home'
19+
end
20+
21+
it "returns the same String if the level is 0" do
22+
File.dirname('poot.txt', 0).should == 'poot.txt'
23+
File.dirname('/', 0).should == '/'
24+
end
25+
26+
it "raises ArgumentError if the level is negative" do
27+
-> {
28+
File.dirname('/home/jason', -1)
29+
}.should raise_error(ArgumentError, "negative level: -1")
30+
end
31+
32+
it "returns '/' when level exceeds the number of segments in the path" do
33+
File.dirname("/home/jason", 100).should == '/'
34+
end
35+
36+
it "calls #to_int if passed not numeric value" do
37+
object = Object.new
38+
def object.to_int; 2; end
39+
40+
File.dirname("/a/b/c/d", object).should == '/a/b'
41+
end
2742
end
2843
end
2944

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: 13 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -395,57 +395,32 @@ def self.directory?(io_or_path)
395395
Truffle::StatOperations.directory?(mode)
396396
end
397397

398-
def self.last_nonslash(path, start = nil)
399-
# Find the first non-/ from the right
400-
data = path.bytes
401-
start ||= (path.size - 1)
402-
403-
start.downto(0) do |i|
404-
if data[i] != 47 # ?/
405-
return i
406-
end
407-
end
408-
nil
409-
end
410-
411398
##
412399
# Returns all components of the filename given in
413400
# file_name except the last one. The filename must be
414401
# formed using forward slashes ("/") regardless of
415402
# the separator used on the local file system.
416403
#
417404
# File.dirname("/home/gumby/work/ruby.rb") #=> "/home/gumby/work"
418-
def self.dirname(path)
405+
def self.dirname(path, level = 1)
419406
path = Truffle::Type.coerce_to_path(path)
407+
level = Primitive.rb_num2int(level)
420408

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
409+
raise ArgumentError, "negative level: #{level}" if level < 0
410+
return path if level == 0
429411

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
438-
439-
# prune any trailing /'s
440-
idx = last_nonslash(path, pos)
441-
442-
# edge case, only /'s, return /
443-
return +'/' unless idx
412+
# fast path
413+
if level == 1
414+
return +'.' if path.empty?
415+
return Truffle::FileOperations.dirname(path)
416+
end
444417

445-
return path.byteslice(0, idx - 1)
418+
level.times do
419+
return +'.' if path.empty?
420+
path = Truffle::FileOperations.dirname(path)
446421
end
447422

448-
+'.'
423+
path
449424
end
450425

451426
##

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,47 @@ 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+
def self.dirname(path)
83+
slash = '/'
84+
85+
# pull off any /'s at the end to ignore
86+
chunk_size = last_nonslash(path)
87+
return +'/' unless chunk_size
88+
89+
if pos = Primitive.find_string_reverse(path, slash, chunk_size)
90+
return +'/' if pos == 0
91+
92+
path = path.byteslice(0, pos)
93+
94+
return +'/' if path == '/'
95+
96+
return path unless path.end_with? slash
97+
98+
# prune any trailing /'s
99+
idx = last_nonslash(path, pos)
100+
101+
# edge case, only /'s, return /
102+
return +'/' unless idx
103+
104+
return path.byteslice(0, idx - 1)
105+
end
106+
107+
+'.'
108+
end
109+
110+
def self.last_nonslash(path, start = nil)
111+
# Find the first non-/ from the right
112+
data = path.bytes
113+
start ||= (path.size - 1)
114+
115+
start.downto(0) do |i|
116+
if data[i] != 47 # ?/
117+
return i
118+
end
119+
end
120+
121+
nil
122+
end
81123
end
82124
end

test/mri/excludes/TestFileExhaustive.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,5 @@
1515
exclude :test_expand_path_for_existent_username, "needs investigation"
1616
exclude :test_readlink_long_path, "needs investigation"
1717
exclude :test_utime, "needs investigation"
18-
exclude :test_dirname, "ArgumentError: wrong number of arguments (given 2, expected 1)"
1918
exclude :test_flock_shared, "Zlib::InProgressError: zlib stream is in progress"
2019
exclude :test_flock_exclusive, "Zlib::InProgressError: zlib stream is in progress"

0 commit comments

Comments
 (0)