Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 29 additions & 28 deletions core/src/main/java/org/jruby/RubyArgsFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -430,18 +430,18 @@ 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));
}

/** 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);

Expand All @@ -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);
}

Expand All @@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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")
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/org/jruby/RubyEnumerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
14 changes: 7 additions & 7 deletions core/src/main/java/org/jruby/RubyIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -3912,23 +3912,23 @@ 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);
}

@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);
}

@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);
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
41 changes: 41 additions & 0 deletions spec/ruby/core/argf/fixtures/classes.rb
Original file line number Diff line number Diff line change
@@ -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
69 changes: 69 additions & 0 deletions spec/ruby/core/argf/shared/each_line.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require_relative '../fixtures/classes'

describe :argf_each_line, shared: true do
before :each do
@file1_name = fixture __FILE__, "file1.txt"
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions spec/ruby/core/argf/shared/gets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
37 changes: 37 additions & 0 deletions spec/ruby/core/argf/shared/readlines.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require_relative '../fixtures/classes'

describe :argf_readlines, shared: true do
before :each do
@file1 = fixture __FILE__, "file1.txt"
Expand All @@ -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
Expand Down
Loading
Loading