diff --git a/core/src/main/java/org/jruby/RubyArgsFile.java b/core/src/main/java/org/jruby/RubyArgsFile.java index c94959e056a..a8e7c6999ed 100644 --- a/core/src/main/java/org/jruby/RubyArgsFile.java +++ b/core/src/main/java/org/jruby/RubyArgsFile.java @@ -430,10 +430,10 @@ private static boolean isGenericInput(ThreadContext context, ArgsFileData data) /** Read a line. * */ - @JRubyMethod(name = "gets", optional = 1, keywords = true, checkArity = false, writes = LASTLINE) + @JRubyMethod(name = "gets", optional = 2, keywords = true, checkArity = false, writes = LASTLINE) public static IRubyObject gets(ThreadContext context, IRubyObject recv, IRubyObject[] args) { final int callInfo = ThreadContext.resetCallInfo(context); - Arity.checkArgumentCount(context, args, 0, 1); + checkGetlineArity(context, callInfo, args); return context.setLastLine(argf_getline(context, callInfo, args)); } @@ -441,7 +441,7 @@ public static IRubyObject gets(ThreadContext context, IRubyObject recv, IRubyObj /** Read a line. * */ - @JRubyMethod(name = "readline", optional = 1, keywords = true, checkArity = false, writes = LASTLINE) + @JRubyMethod(name = "readline", optional = 2, keywords = true, checkArity = false, writes = LASTLINE) public static IRubyObject readline(ThreadContext context, IRubyObject recv, IRubyObject[] args) { IRubyObject line = gets(context, recv, args); @@ -450,17 +450,17 @@ public static IRubyObject readline(ThreadContext context, IRubyObject recv, IRub return line; } - @JRubyMethod(optional = 1, keywords = true, checkArity = false) + @JRubyMethod(optional = 2, keywords = true, checkArity = false) public static IRubyObject readlines(ThreadContext context, IRubyObject recv, IRubyObject[] args) { final int callInfo = ThreadContext.resetCallInfo(context); - Arity.checkArgumentCount(context, args, 0, 1); + checkGetlineArity(context, callInfo, args); ArgsFileData data = ArgsFileData.getArgsFileData(context.runtime); if (!data.next_argv(context)) return newEmptyArray(context); if (!(data.currentFile instanceof RubyIO)) { - // TODO do we need to restore callInfo here? + context.callInfo = callInfo; // restore callInfo for kwargs return data.currentFile.callMethod(context, "readlines", args); } @@ -472,21 +472,15 @@ public static IRubyObject readlines(ThreadContext context, IRubyObject recv, IRu return ary; } - @JRubyMethod(optional = 1, checkArity = false) + // MRI: to_a is an alias of readlines + @JRubyMethod(optional = 2, keywords = true, checkArity = false) public static IRubyObject to_a(ThreadContext context, IRubyObject recv, IRubyObject[] args) { - Arity.checkArgumentCount(context, args, 0, 1); - - ArgsFileData data = ArgsFileData.getArgsFileData(context.runtime); - - if (!data.next_argv(context)) return newEmptyArray(context); - if (!(data.currentFile instanceof RubyIO)) return data.currentFile.callMethod(context, "to_a", args); + return readlines(context, recv, args); + } - var ary = newArray(context); - IRubyObject line; - while ((line = argf_getline(context, 0, args)) != context.nil) { - ary.append(context, line); - } - return ary; + // MRI: rb_io_getline takes up to two positional arguments (separator, limit) plus keywords + private static void checkGetlineArity(ThreadContext context, int callInfo, IRubyObject[] args) { + Arity.checkArgumentCount(context, args.length - (hasKeywords(callInfo) ? 1 : 0), 0, 2); } @JRubyMethod @@ -584,21 +578,25 @@ public static IRubyObject codepoints(ThreadContext context, IRubyObject recv, Bl /** Invoke a block for each line. * */ - @JRubyMethod(name = "each_line", optional = 1, keywords = true, checkArity = false) + @JRubyMethod(name = "each_line", optional = 2, keywords = true, checkArity = false) public static IRubyObject each_line(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { - if (!block.isGiven()) return enumeratorize(context.runtime, recv, "each_line", args); - final int callInfo = ThreadContext.resetCallInfo(context); - Arity.checkArgumentCount(context, args, 0, 1); + if (!block.isGiven()) return enumeratorize(context.runtime, recv, "each_line", args, hasKeywords(callInfo)); + + return eachLineCommon(context, recv, callInfo, args, block); + } + + private static IRubyObject eachLineCommon(ThreadContext context, IRubyObject recv, int callInfo, IRubyObject[] args, Block block) { + checkGetlineArity(context, callInfo, args); ArgsFileData data = ArgsFileData.getArgsFileData(context.runtime); if (!data.next_argv(context)) return context.nil; + // MRI: argf_each_line calls each_line on the current file with the same arguments if (!(data.currentFile instanceof RubyIO)) { - if (!data.next_argv(context)) return recv; - - data.currentFile.callMethod(context, "each", NULL_ARRAY, block); + context.callInfo = callInfo; // restore callInfo for kwargs + data.currentFile.callMethod(context, "each_line", args, block); data.next_p = NextFile; } @@ -616,9 +614,12 @@ public static IRubyObject lines(ThreadContext context, IRubyObject recv, IRubyOb return each_line(context, recv, args, block); } - @JRubyMethod(name = "each", optional = 1, checkArity = false) + @JRubyMethod(name = "each", optional = 2, keywords = true, checkArity = false) public static IRubyObject each(final ThreadContext context, IRubyObject recv, IRubyObject[] args, final Block block) { - return block.isGiven() ? each_line(context, recv, args, block) : enumeratorize(context.runtime, recv, "each", args); + final int callInfo = ThreadContext.resetCallInfo(context); + if (!block.isGiven()) return enumeratorize(context.runtime, recv, "each", args, hasKeywords(callInfo)); + + return eachLineCommon(context, recv, callInfo, args, block); } @JRubyMethod(name = "file") diff --git a/core/src/main/java/org/jruby/RubyEnumerator.java b/core/src/main/java/org/jruby/RubyEnumerator.java index c8e43c5c6d6..431c45b450a 100644 --- a/core/src/main/java/org/jruby/RubyEnumerator.java +++ b/core/src/main/java/org/jruby/RubyEnumerator.java @@ -194,6 +194,14 @@ public static IRubyObject enumeratorize(Ruby runtime, IRubyObject object, String return new RubyEnumerator(runtime, runtime.getEnumerator(), object, runtime.fastNewSymbol(method), args); } + /** + * Like {@link #enumeratorize(Ruby, IRubyObject, String, IRubyObject...)}, but if keywords is true the last + * element of args is passed back to the method as keywords. + */ + public static IRubyObject enumeratorize(Ruby runtime, IRubyObject object, String method, IRubyObject[] args, boolean keywords) { + return new RubyEnumerator(runtime, runtime.getEnumerator(), object, runtime.fastNewSymbol(method), args, null, null, keywords); + } + public static IRubyObject enumeratorize(Ruby runtime, RubyClass type, IRubyObject object, String method) { return new RubyEnumerator(runtime, type, object, runtime.fastNewSymbol(method), NULL_ARRAY); } diff --git a/core/src/main/java/org/jruby/RubyIO.java b/core/src/main/java/org/jruby/RubyIO.java index 01767bc3ce8..f6b351cc0e8 100644 --- a/core/src/main/java/org/jruby/RubyIO.java +++ b/core/src/main/java/org/jruby/RubyIO.java @@ -3912,7 +3912,7 @@ public IRubyObject each(final ThreadContext context, final Block block) { @JRubyMethod(keywords = true) public IRubyObject each(final ThreadContext context, IRubyObject arg0, final Block block) { boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); - if (!block.isGiven()) return enumeratorize(context.runtime, this, "each"); + if (!block.isGiven()) return enumeratorize(context.runtime, this, "each", new IRubyObject[] { arg0 }, keywords); return Getline.getlineCall(context, GETLINE_YIELD, this, getReadEncoding(context), arg0, block, keywords); } @@ -3920,7 +3920,7 @@ public IRubyObject each(final ThreadContext context, IRubyObject arg0, final Blo @JRubyMethod(keywords = true) public IRubyObject each(final ThreadContext context, IRubyObject arg0, IRubyObject arg1, final Block block) { boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); - if (!block.isGiven()) return enumeratorize(context.runtime, this, "each"); + if (!block.isGiven()) return enumeratorize(context.runtime, this, "each", new IRubyObject[] { arg0, arg1 }, keywords); return Getline.getlineCall(context, GETLINE_YIELD, this, getReadEncoding(context), arg0, arg1, block, keywords); } @@ -3928,7 +3928,7 @@ public IRubyObject each(final ThreadContext context, IRubyObject arg0, IRubyObje @JRubyMethod(keywords = true) public IRubyObject each(final ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, final Block block) { boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); - if (!block.isGiven()) return enumeratorize(context.runtime, this, "each"); + if (!block.isGiven()) return enumeratorize(context.runtime, this, "each", new IRubyObject[] { arg0, arg1, arg2 }, keywords); return Getline.getlineCall(context, GETLINE_YIELD, this, getReadEncoding(context), arg0, arg1, arg2, block, keywords); } @@ -3960,7 +3960,7 @@ public IRubyObject each_line(final ThreadContext context, final Block block) { public IRubyObject each_line(final ThreadContext context, IRubyObject arg0, final Block block) { boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); - if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_line", arg0); + if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_line", new IRubyObject[] { arg0 }, keywords); return Getline.getlineCall(context, GETLINE_YIELD, this, getReadEncoding(context), arg0, block, keywords); } @@ -3969,7 +3969,7 @@ public IRubyObject each_line(final ThreadContext context, IRubyObject arg0, fina public IRubyObject each_line(final ThreadContext context, IRubyObject arg0, IRubyObject arg1, final Block block) { boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); - if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_line", arg0, arg1); + if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_line", new IRubyObject[] { arg0, arg1 }, keywords); return Getline.getlineCall(context, GETLINE_YIELD, this, getReadEncoding(context), arg0, arg1, block, keywords); } @@ -3978,7 +3978,7 @@ public IRubyObject each_line(final ThreadContext context, IRubyObject arg0, IRub public IRubyObject each_line(final ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, final Block block) { boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); - if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_line", arg0, arg1, arg2); + if (!block.isGiven()) return enumeratorize(context.runtime, this, "each_line", new IRubyObject[] { arg0, arg1, arg2 }, keywords); return Getline.getlineCall(context, GETLINE_YIELD, this, getReadEncoding(context), arg0, arg1, arg2, block, keywords); } @@ -4101,7 +4101,7 @@ public static IRubyObject foreach(final ThreadContext context, IRubyObject recv, Arity.checkArgumentCount(context, args.length - (keywords ? 1 : 0), 1, 4); - if (!block.isGiven()) return enumeratorize(context.runtime, recv, "foreach", args); + if (!block.isGiven()) return enumeratorize(context.runtime, recv, "foreach", args, keywords); return foreachInternal(context, recv, args, keywords, block); } diff --git a/spec/ruby/core/argf/fixtures/classes.rb b/spec/ruby/core/argf/fixtures/classes.rb new file mode 100644 index 00000000000..3c8abbb180f --- /dev/null +++ b/spec/ruby/core/argf/fixtures/classes.rb @@ -0,0 +1,41 @@ +require 'stringio' + +module ARGFSpecs + # An object that is not an IO, for assigning to $stdin. It reads from a + # StringIO and records the arguments and keywords each method receives. + class Stdin + attr_reader :calls + + def initialize(string) + @io = StringIO.new(string) + @calls = [] + end + + def gets(*args, **kw) + @calls << [:gets, args, kw] + @io.gets(*args, **kw) + end + + def each_line(*args, **kw, &block) + @calls << [:each_line, args, kw] + @io.each_line(*args, **kw, &block) + self + end + + def each(*args, **kw, &block) + @calls << [:each, args, kw] + @io.each(*args, **kw, &block) + self + end + + def readlines(*args, **kw) + @calls << [:readlines, args, kw] + @io.readlines(*args, **kw) + end + + def to_a(*args, **kw) + @calls << [:to_a, args, kw] + @io.to_a(*args, **kw) + end + end +end diff --git a/spec/ruby/core/argf/shared/each_line.rb b/spec/ruby/core/argf/shared/each_line.rb index c0ef77dc543..671a80ebc69 100644 --- a/spec/ruby/core/argf/shared/each_line.rb +++ b/spec/ruby/core/argf/shared/each_line.rb @@ -1,3 +1,5 @@ +require_relative '../fixtures/classes' + describe :argf_each_line, shared: true do before :each do @file1_name = fixture __FILE__, "file1.txt" @@ -42,7 +44,74 @@ end end + describe "with a separator and a limit" do + it "yields each limited section of all streams" do + argf [@file1_name, @file2_name] do + lines = [] + @argf.send(@method, '.', 4) { |s| lines << s } + lines.should == (File.readlines(@file1_name, '.', 4) + File.readlines(@file2_name, '.', 4)) + end + end + end + + describe "when passed chomp" do + it "yields each line without the trailing separator" do + argf [@file1_name, @file2_name] do + lines = [] + @argf.send(@method, chomp: true) { |s| lines << s } + lines.should == @lines.map(&:chomp) + end + end + + it "yields each separated section without the separator when also passed a separator and a limit" do + argf [@file1_name, @file2_name] do + lines = [] + @argf.send(@method, '.', 8, chomp: true) { |s| lines << s } + lines.should == (File.readlines(@file1_name, '.', 8, chomp: true) + File.readlines(@file2_name, '.', 8, chomp: true)) + end + end + end + + describe "when reading $stdin and $stdin is not an IO" do + before :each do + @stdin = $stdin + $stdin = ARGFSpecs::Stdin.new("a\nb\n") + @stdin_argf = ARGF.class.new + end + + after :each do + $stdin = @stdin + end + + it "calls each_line on $stdin and yields each line" do + lines = [] + @stdin_argf.send(@method) { |s| lines << s } + lines.should == ["a\n", "b\n"] + $stdin.calls.should == [[:each_line, [], {}]] + end + + it "passes the separator, limit and keywords to $stdin.each_line" do + lines = [] + @stdin_argf.send(@method, "\n", 1, chomp: true) { |s| lines << s } + lines.should == ["a", "", "b", ""] + $stdin.calls.should == [[:each_line, ["\n", 1], { chomp: true }]] + end + end + describe "when no block is given" do + it "returns an Enumerator that passes chomp to the method" do + argf [@file1_name, @file2_name] do + @argf.send(@method, chomp: true).to_a.should == @lines.map(&:chomp) + end + end + + it "returns an Enumerator that passes the separator, limit and chomp to the method" do + argf [@file1_name, @file2_name] do + @argf.send(@method, '.', 8, chomp: true).to_a.should == + (File.readlines(@file1_name, '.', 8, chomp: true) + File.readlines(@file2_name, '.', 8, chomp: true)) + end + end + it "returns an Enumerator" do argf [@file1_name, @file2_name] do @argf.send(@method).should be_an_instance_of(Enumerator) diff --git a/spec/ruby/core/argf/shared/gets.rb b/spec/ruby/core/argf/shared/gets.rb index 160d24c27bb..94c2cdf0d53 100644 --- a/spec/ruby/core/argf/shared/gets.rb +++ b/spec/ruby/core/argf/shared/gets.rb @@ -15,6 +15,24 @@ end end + it "reads one line of a file without the trailing separator when passed chomp" do + argf [@file1_name] do + @argf.send(@method, chomp: true).should == @file1.first.chomp + end + end + + it "reads up to the limit when passed a separator and a limit" do + argf [@file1_name] do + @argf.send(@method, ".", 4).should == "file" + end + end + + it "reads one separated section without the separator when passed a separator and chomp" do + argf [@file1_name] do + @argf.send(@method, ".", chomp: true).should == "file1" + end + end + it "reads all lines of a file" do argf [@file1_name] do lines = [] diff --git a/spec/ruby/core/argf/shared/readlines.rb b/spec/ruby/core/argf/shared/readlines.rb index 505fa94acbf..67124636ec7 100644 --- a/spec/ruby/core/argf/shared/readlines.rb +++ b/spec/ruby/core/argf/shared/readlines.rb @@ -1,3 +1,5 @@ +require_relative '../fixtures/classes' + describe :argf_readlines, shared: true do before :each do @file1 = fixture __FILE__, "file1.txt" @@ -13,6 +15,41 @@ end end + it "reads all lines of all files without the trailing separator when passed chomp" do + argf [@file1, @file2] do + @argf.send(@method, chomp: true).should == @lines.map(&:chomp) + end + end + + it "reads all limited sections of all files when passed a separator and a limit" do + argf [@file1, @file2] do + @argf.send(@method, ".", 4).should == + (File.readlines(@file1, ".", 4) + File.readlines(@file2, ".", 4)) + end + end + + describe "when reading $stdin and $stdin is not an IO" do + before :each do + @stdin = $stdin + $stdin = ARGFSpecs::Stdin.new("a\nb\n") + @stdin_argf = ARGF.class.new + end + + after :each do + $stdin = @stdin + end + + it "calls readlines on $stdin" do + @stdin_argf.send(@method).should == ["a\n", "b\n"] + $stdin.calls.should == [[:readlines, [], {}]] + end + + it "passes the separator, limit and keywords to $stdin.readlines" do + @stdin_argf.send(@method, "\n", 1, chomp: true).should == ["a", "", "b", ""] + $stdin.calls.should == [[:readlines, ["\n", 1], { chomp: true }]] + end + end + it "returns an empty Array when end of stream reached" do argf [@file1, @file2] do @argf.read diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index 28d6fef7ae5..616574eac45 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -82,6 +82,12 @@ IO.foreach(@name).to_a.should == IOSpecs.lines end + it "returns an Enumerator that passes keyword arguments to IO.foreach" do + IO.foreach(@name, chomp: true).to_a.should == IOSpecs.lines_without_newline_characters + IO.foreach(@name, " ", chomp: true).to_a.should == IOSpecs.lines_space_separator_without_trailing_spaces + IO.foreach(@name, encoding: Encoding::BINARY).first.encoding.should == Encoding::BINARY + end + describe "returned Enumerator" do describe "size" do it "should return nil" do diff --git a/spec/ruby/core/io/shared/each.rb b/spec/ruby/core/io/shared/each.rb index 0747f31b8a8..7378b0b938b 100644 --- a/spec/ruby/core/io/shared/each.rb +++ b/spec/ruby/core/io/shared/each.rb @@ -176,6 +176,10 @@ ScratchPad.recorded.should == IOSpecs.lines_without_newline_characters end + it "returns an Enumerator that passes chomp to the method when no block is given" do + @io.send(@method, chomp: true).to_a.should == IOSpecs.lines_without_newline_characters + end + it "raises exception when options passed as Hash" do -> { @io.send(@method, { chomp: true }) { |s| } @@ -192,6 +196,10 @@ @io.send(@method, " ", chomp: true) { |s| ScratchPad << s } ScratchPad.recorded.should == IOSpecs.lines_space_separator_without_trailing_spaces end + + it "returns an Enumerator that passes the separator and chomp to the method when no block is given" do + @io.send(@method, " ", chomp: true).to_a.should == IOSpecs.lines_space_separator_without_trailing_spaces + end end describe "when passed chomp and empty line as a separator" do @@ -199,6 +207,10 @@ @io.send(@method, "", 1024, chomp: true) { |s| ScratchPad << s } ScratchPad.recorded.should == IOSpecs.paragraphs_without_trailing_new_line_characters end + + it "returns an Enumerator that passes the separator, limit and chomp to the method when no block is given" do + @io.send(@method, "", 1024, chomp: true).to_a.should == IOSpecs.paragraphs_without_trailing_new_line_characters + end end describe "when passed chomp and nil as a separator" do diff --git a/spec/tags/ruby/core/argf/each_line_tags.txt b/spec/tags/ruby/core/argf/each_line_tags.txt new file mode 100644 index 00000000000..e0a5e4b2e30 --- /dev/null +++ b/spec/tags/ruby/core/argf/each_line_tags.txt @@ -0,0 +1,2 @@ +windows:ARGF.each_line when passed chomp yields each separated section without the separator when also passed a separator and a limit +windows:ARGF.each_line when no block is given returns an Enumerator that passes the separator, limit and chomp to the method diff --git a/spec/tags/ruby/core/argf/each_tags.txt b/spec/tags/ruby/core/argf/each_tags.txt new file mode 100644 index 00000000000..e72eae1f70b --- /dev/null +++ b/spec/tags/ruby/core/argf/each_tags.txt @@ -0,0 +1,2 @@ +windows:ARGF.each when passed chomp yields each separated section without the separator when also passed a separator and a limit +windows:ARGF.each when no block is given returns an Enumerator that passes the separator, limit and chomp to the method diff --git a/spec/tags/ruby/core/io/each_line_tags.txt b/spec/tags/ruby/core/io/each_line_tags.txt index 52310963efe..7b9b7b7817d 100644 --- a/spec/tags/ruby/core/io/each_line_tags.txt +++ b/spec/tags/ruby/core/io/each_line_tags.txt @@ -12,7 +12,9 @@ windows:IO#each_line with both separator and limit when a block is given when pa windows:IO#each_line with both separator and limit when a block is given when passed an empty String as a separator yields each paragraph windows:IO#each_line with both separator and limit when a block is given when passed an empty String as a separator discards leading newlines windows:IO#each_line when passed chomp and a separator yields each line without separator to the passed block +windows:IO#each_line when passed chomp and a separator returns an Enumerator that passes the separator and chomp to the method when no block is given windows:IO#each_line when passed chomp and empty line as a separator yields each paragraph without trailing new line characters +windows:IO#each_line when passed chomp and empty line as a separator returns an Enumerator that passes the separator, limit and chomp to the method when no block is given windows:IO#each_line when passed chomp and nil as a separator yields self's content windows:IO#each_line when passed chomp, nil as a separator, and a limit yields each line of limit size without truncating trailing new line character windows:IO#each_line uses $/ as the default line separator diff --git a/spec/tags/ruby/core/io/each_tags.txt b/spec/tags/ruby/core/io/each_tags.txt index 899582e417b..0376e082db8 100644 --- a/spec/tags/ruby/core/io/each_tags.txt +++ b/spec/tags/ruby/core/io/each_tags.txt @@ -1,9 +1,9 @@ critical:IO#each with limit when limit is 0 raises an ArgumentError -fails:IO#each with both separator and limit when no block is given returns an Enumerator fails:IO#each when passed chomp raises exception when options passed as Hash windows:IO#each with no separator yields each line to the passed block windows:IO#each with no separator yields each line starting from the current position windows:IO#each with no separator when no block is given returns an Enumerator +windows:IO#each with both separator and limit when no block is given returns an Enumerator windows:IO#each when passed a String containing one space as a separator uses the passed argument as the line separator windows:IO#each when passed a String containing one space as a separator tries to convert the passed separator to a String using #to_str windows:IO#each when passed nil as a separator yields self's content starting from the current position when the passed separator is nil @@ -13,7 +13,9 @@ windows:IO#each with both separator and limit when a block is given when passed windows:IO#each with both separator and limit when a block is given when passed an empty String as a separator yields each paragraph windows:IO#each with both separator and limit when a block is given when passed an empty String as a separator discards leading newlines windows:IO#each when passed chomp and a separator yields each line without separator to the passed block +windows:IO#each when passed chomp and a separator returns an Enumerator that passes the separator and chomp to the method when no block is given windows:IO#each when passed chomp and empty line as a separator yields each paragraph without trailing new line characters +windows:IO#each when passed chomp and empty line as a separator returns an Enumerator that passes the separator, limit and chomp to the method when no block is given windows:IO#each when passed chomp and nil as a separator yields self's content windows:IO#each when passed chomp, nil as a separator, and a limit yields each line of limit size without truncating trailing new line character windows:IO#each uses $/ as the default line separator