Skip to content

Commit 6df3fd3

Browse files
committed
[GR-18163] Fix compatibility issue - support :chomp argument
PullRequest: truffleruby/3339
2 parents 3da69b4 + bf49ece commit 6df3fd3

31 files changed

+429
-157
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Compatibility:
2929
* Fix `instance_variable_get` and `instance_variable_set` for immutable objects (@bjfish).
3030
* `Thread#raise(exc, message)` now calls `exc.exception` in the target thread like CRuby (@eregon).
3131
* Define `Process::{CLOCK_BOOTTIME,CLOCK_BOOTTIME_ALARM,CLOCK_REALTIME_ALARM}` (#1480, @eregon).
32+
* Improve support of `:chomp` keyword argument in `IO` and `StringIO` methods (#2650, @andrykonchin).
3233

3334
Performance:
3435

lib/truffle/pathname.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -806,8 +806,8 @@ class Pathname # * IO *
806806
#
807807
# This method has existed since 1.8.1.
808808
#
809-
def each_line(*args, &block) # :yield: line
810-
IO.foreach(@path, *args, &block)
809+
def each_line(*args, **kw, &block) # :yield: line
810+
IO.foreach(@path, *args, **kw, &block)
811811
end
812812

813813
# See <tt>IO.read</tt>. Returns all data from the file, or the first +N+ bytes
@@ -825,7 +825,7 @@ def write(*args) IO.write(@path, *args) end
825825
def binwrite(*args) IO.binwrite(@path, *args) end
826826

827827
# See <tt>IO.readlines</tt>. Returns all the lines from the file.
828-
def readlines(*args) IO.readlines(@path, *args) end
828+
def readlines(*args, **kw) IO.readlines(@path, *args, **kw) end
829829

830830
# See <tt>IO.sysopen</tt>.
831831
def sysopen(*args) IO.sysopen(@path, *args) end

lib/truffle/stringio.rb

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ def readbyte
4141
readchar.getbyte(0)
4242
end
4343

44-
def readline(sep=$/, limit=::Undefined)
44+
def readline(sep=$/, limit=::Undefined, chomp: false)
4545
check_readable
4646
raise EOFError, 'end of file reached' if eof?
4747

48-
Primitive.io_last_line_set(Primitive.caller_special_variables, getline(true, sep, limit))
48+
Primitive.io_last_line_set(Primitive.caller_special_variables, getline(false, sep, limit, chomp))
4949
end
5050

5151
def sysread(length = nil, buffer = nil)
@@ -220,7 +220,6 @@ def each_byte
220220

221221
self
222222
end
223-
alias_method :bytes, :each_byte
224223

225224
def each_char
226225
return to_enum :each_char unless block_given?
@@ -230,7 +229,6 @@ def each_char
230229

231230
self
232231
end
233-
alias_method :chars, :each_char
234232

235233
def each_codepoint(&block)
236234
return to_enum :each_codepoint unless block_given?
@@ -253,20 +251,18 @@ def each_codepoint(&block)
253251

254252
self
255253
end
256-
alias_method :codepoints, :each_codepoint
257254

258-
def each(sep=$/, limit=Undefined)
259-
return to_enum :each, sep, limit unless block_given?
255+
def each(sep=$/, limit=Undefined, chomp: false)
256+
return to_enum :each, sep, limit, chomp: chomp unless block_given?
260257
check_readable
261258

262-
while line = getline(true, sep, limit)
259+
while line = getline(true, sep, limit, chomp)
263260
yield line
264261
end
265262

266263
self
267264
end
268265
alias_method :each_line, :each
269-
alias_method :lines, :each
270266

271267
def binmode
272268
set_encoding(Encoding::BINARY)
@@ -373,12 +369,12 @@ def getbyte
373369
byte
374370
end
375371

376-
def gets(sep=$/, limit=Undefined)
372+
def gets(sep=$/, limit=Undefined, chomp: false)
377373
check_readable
378374

379375
# Truffle: $_ is thread and frame local, so we use a primitive to
380376
# set it in the caller's frame.
381-
Primitive.io_last_line_set(Primitive.caller_special_variables, getline(false, sep, limit))
377+
Primitive.io_last_line_set(Primitive.caller_special_variables, getline(false, sep, limit, chomp))
382378
end
383379

384380
def isatty
@@ -476,11 +472,11 @@ def read(length = nil, buffer = nil)
476472
str
477473
end
478474

479-
def readlines(sep=$/, limit=Undefined)
475+
def readlines(sep=$/, limit=Undefined, chomp: false)
480476
check_readable
481477

482478
ary = []
483-
while line = getline(true, sep, limit)
479+
while line = getline(true, sep, limit, chomp)
484480
ary << line
485481
end
486482

@@ -682,7 +678,7 @@ def yaml_initialize(type, val)
682678
d.string.replace('') if (mode & IO::TRUNC) != 0
683679
end
684680

685-
private def getline(arg_error, sep, limit)
681+
private def getline(arg_error, sep, limit, chomp=false)
686682
if limit != Undefined
687683
limit = Primitive.rb_to_int limit if limit
688684
sep = Truffle::Type.coerce_to sep, String, :to_str if sep
@@ -692,12 +688,17 @@ def yaml_initialize(type, val)
692688
unless sep == $/ or sep.nil?
693689
osep = sep
694690
sep = Truffle::Type.rb_check_convert_type sep, String, :to_str
695-
limit = Primitive.rb_to_int osep unless sep
691+
unless sep
692+
limit = Primitive.rb_to_int osep
693+
sep = $/
694+
end
696695
end
697696
end
698697

699698
raise ArgumentError if arg_error and limit == 0
700699

700+
limit = nil if limit && limit < 0
701+
701702
return nil if eof?
702703

703704
d = @__data__
@@ -744,6 +745,10 @@ def yaml_initialize(type, val)
744745

745746
d.lineno += 1
746747

748+
if chomp
749+
line.chomp!(sep || DEFAULT_RECORD_SEPARATOR)
750+
end
751+
747752
line
748753
end
749754

spec/ruby/core/io/fixtures/classes.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ def self.lines_space_separator
108108
"linha ", "cinco.\nHere ", "is ", "line ", "six.\n" ]
109109
end
110110

111+
def self.lines_space_separator_without_trailing_spaces
112+
[ "Voici", "la", "ligne", "une.\nQui",
113+
"\303\250", "la", "linea", "due.\n\n\nAqu\303\255",
114+
"est\303\241", "la", "l\303\255nea", "tres.\nHier",
115+
"ist", "Zeile", "vier.\n\nEst\303\241", "aqui", "a",
116+
"linha", "cinco.\nHere", "is", "line", "six.\n" ]
117+
end
118+
111119
def self.lines_arbitrary_separator
112120
[ "Voici la ligne une.\nQui \303\250",
113121
" la linea due.\n\n\nAqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.\n\nEst\303\241 aqui a linha cinco.\nHere is line six.\n" ]
@@ -119,6 +127,12 @@ def self.paragraphs
119127
"Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
120128
end
121129

130+
def self.paragraphs_without_trailing_new_line_characters
131+
[ "Voici la ligne une.\nQui \303\250 la linea due.",
132+
"Aqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.",
133+
"Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
134+
end
135+
122136
# Creates an IO instance for an existing fixture file. The
123137
# file should obviously not be deleted.
124138
def self.io_fixture(name, mode = "r:utf-8")

spec/ruby/core/io/gets_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@
119119
it "returns the first line without a trailing newline character" do
120120
@io.gets(chomp: true).should == IOSpecs.lines_without_newline_characters[0]
121121
end
122+
123+
ruby_version_is "3.0" do
124+
it "raises exception when options passed as Hash" do
125+
-> { @io.gets({ chomp: true }) }.should raise_error(TypeError)
126+
127+
-> {
128+
@io.gets("\n", 1, { chomp: true })
129+
}.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
130+
end
131+
end
122132
end
123133
end
124134

@@ -200,6 +210,10 @@
200210
it "reads all bytes when pass a separator and reading more than all bytes" do
201211
@io.gets("\t", 100).should == "one\n\ntwo\n\nthree\nfour\n"
202212
end
213+
214+
it "returns empty string when 0 passed as a limit" do
215+
@io.gets(0).should == ""
216+
end
203217
end
204218

205219
describe "IO#gets" do

spec/ruby/core/io/readline_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,40 @@
4343
end
4444
end
4545

46+
describe "when passed limit" do
47+
it "reads limit bytes" do
48+
@io.readline(3).should == "Voi"
49+
end
50+
51+
it "returns an empty string when passed 0 as a limit" do
52+
@io.readline(0).should == ""
53+
end
54+
end
55+
56+
describe "when passed separator and limit" do
57+
it "reads limit bytes till the separator" do
58+
# Voici la ligne une.\
59+
@io.readline(" ", 4).should == "Voic"
60+
@io.readline(" ", 4).should == "i "
61+
@io.readline(" ", 4).should == "la "
62+
@io.readline(" ", 4).should == "lign"
63+
@io.readline(" ", 4).should == "e "
64+
end
65+
end
66+
4667
describe "when passed chomp" do
4768
it "returns the first line without a trailing newline character" do
4869
@io.readline(chomp: true).should == IOSpecs.lines_without_newline_characters[0]
4970
end
71+
72+
ruby_version_is "3.0" do
73+
it "raises exception when options passed as Hash" do
74+
-> { @io.readline({ chomp: true }) }.should raise_error(TypeError)
75+
76+
-> {
77+
@io.readline("\n", 1, { chomp: true })
78+
}.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
79+
end
80+
end
5081
end
5182
end

spec/ruby/core/io/readlines_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,28 @@
101101
@io.readlines(obj).should == IOSpecs.lines_r_separator
102102
end
103103
end
104+
105+
describe "when passed limit" do
106+
it "raises ArgumentError when passed 0 as a limit" do
107+
-> { @io.readlines(0) }.should raise_error(ArgumentError)
108+
end
109+
end
110+
111+
describe "when passed chomp" do
112+
it "returns the first line without a trailing newline character" do
113+
@io.readlines(chomp: true).should == IOSpecs.lines_without_newline_characters
114+
end
115+
116+
ruby_version_is "3.0" do
117+
it "raises exception when options passed as Hash" do
118+
-> { @io.readlines({ chomp: true }) }.should raise_error(TypeError)
119+
120+
-> {
121+
@io.readlines("\n", 1, { chomp: true })
122+
}.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
123+
end
124+
end
125+
end
104126
end
105127

106128
describe "IO#readlines" do

spec/ruby/core/io/shared/each.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,54 @@
161161
@io.send(@method, chomp: true) { |s| ScratchPad << s }
162162
ScratchPad.recorded.should == IOSpecs.lines_without_newline_characters
163163
end
164+
165+
ruby_version_is "3.0" do
166+
it "raises exception when options passed as Hash" do
167+
-> {
168+
@io.send(@method, { chomp: true }) { |s| }
169+
}.should raise_error(TypeError)
170+
171+
-> {
172+
@io.send(@method, "\n", 1, { chomp: true }) { |s| }
173+
}.should raise_error(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
174+
end
175+
end
176+
end
177+
178+
describe "when passed chomp and a separator" do
179+
it "yields each line without separator to the passed block" do
180+
@io.send(@method, " ", chomp: true) { |s| ScratchPad << s }
181+
ScratchPad.recorded.should == IOSpecs.lines_space_separator_without_trailing_spaces
182+
end
183+
end
184+
185+
describe "when passed chomp and empty line as a separator" do
186+
it "yields each paragraph without trailing new line characters" do
187+
@io.send(@method, "", 1024, chomp: true) { |s| ScratchPad << s }
188+
ScratchPad.recorded.should == IOSpecs.paragraphs_without_trailing_new_line_characters
189+
end
190+
end
191+
192+
describe "when passed chomp and nil as a separator" do
193+
it "yields self's content without trailing new line character" do
194+
@io.pos = 100
195+
@io.send(@method, nil, chomp: true) { |s| ScratchPad << s }
196+
ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six."]
197+
end
198+
end
199+
200+
describe "when passed chomp, nil as a separator, and a limit" do
201+
it "yields each line of limit size without truncating trailing new line character" do
202+
# 43 - is a size of the 1st paragraph in the file
203+
@io.send(@method, nil, 43, chomp: true) { |s| ScratchPad << s }
204+
205+
ScratchPad.recorded.should == [
206+
"Voici la ligne une.\nQui è la linea due.\n\n\n",
207+
"Aquí está la línea tres.\n" + "Hier ist Zeile ",
208+
"vier.\n\nEstá aqui a linha cinco.\nHere is li",
209+
"ne six.\n"
210+
]
211+
end
164212
end
165213
end
166214

0 commit comments

Comments
 (0)