Skip to content

Commit 18d7445

Browse files
committed
Refactor and add changelog entry
1 parent 0de3fd6 commit 18d7445

File tree

5 files changed

+53
-59
lines changed

5 files changed

+53
-59
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: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +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
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
2420

25-
it "raises ArgumentError if the level is negative" do
26-
-> {File.dirname('/home/jason', -1)}.should raise_error(ArgumentError)
27-
end
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
2825

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)
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
3231

33-
obj = mock("to_path")
34-
def obj.to_path
35-
"path"
32+
it "returns '/' when level exceeds the number of segments in the path" do
33+
File.dirname("/home/jason", 100).should == '/'
3634
end
37-
File.dirname(obj,0).should .equal?(obj)
3835

39-
obj = Object.new
40-
File.dirname(obj,0).should .equal?(obj)
41-
end
36+
it "calls #to_int if passed not numeric value" do
37+
object = Object.new
38+
def object.to_int; 2; end
4239

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-
}
40+
File.dirname("/a/b/c/d", object).should == '/a/b'
41+
end
4742
end
4843
end
4944

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

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -395,45 +395,31 @@ 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,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
428-
405+
def self.dirname(path, level = 1)
429406
path = Truffle::Type.coerce_to_path(path)
407+
level = Primitive.rb_num2int(level)
408+
409+
raise ArgumentError, "negative level: #{level}" if level < 0
410+
return path if level == 0
430411

431-
num_levels.times do
432-
# edge case
412+
# fast path
413+
if level == 1
433414
return +'.' if path.empty?
415+
return Truffle::FileOperations.dirname(path)
416+
end
434417

435-
path = Truffle::FileOperations.remove_last_segment_from_path(path)
418+
level.times do
419+
return +'.' if path.empty?
420+
path = Truffle::FileOperations.dirname(path)
436421
end
422+
437423
path
438424
end
439425

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,11 @@ def self.exist?(path)
7979
Truffle::POSIX.truffleposix_stat_mode(path) > 0
8080
end
8181

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)
82+
def self.dirname(path)
8583
slash = '/'
84+
8685
# pull off any /'s at the end to ignore
87-
chunk_size = File.last_nonslash(path)
86+
chunk_size = last_nonslash(path)
8887
return +'/' unless chunk_size
8988

9089
if pos = Primitive.find_string_reverse(path, slash, chunk_size)
@@ -97,7 +96,7 @@ def self.remove_last_segment_from_path(path)
9796
return path unless path.end_with? slash
9897

9998
# prune any trailing /'s
100-
idx = File.last_nonslash(path, pos)
99+
idx = last_nonslash(path, pos)
101100

102101
# edge case, only /'s, return /
103102
return +'/' unless idx
@@ -107,5 +106,19 @@ def self.remove_last_segment_from_path(path)
107106

108107
+'.'
109108
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
110123
end
111124
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)