Skip to content

Commit

Permalink
Add ARGF.each_file for iterating file/io objects
Browse files Browse the repository at this point in the history
This makes it possible to iterate through `ARGF` file objects without
having to call `ARGF.file` and `ARGF.skip` directly. `ARGF` already has
a number of iteration helpers but nothing that exposes the underlying
file objects. This can be useful when you want to operate on file
arguments as separate files instead of as a single string or
line-by-line.

There's probably a more direct approach than using `yield_self` but for
now it seemed best to keep things consistent with the other similar
`ARGF` methods (eg, `argf_each_codepoint`).
  • Loading branch information
davishmcclurg committed May 3, 2023
1 parent 32cc630 commit cc8054c
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 0 deletions.
20 changes: 20 additions & 0 deletions io.c
Expand Up @@ -14278,6 +14278,25 @@ argf_each_codepoint(VALUE argf)
return argf;
}

/*
* call-seq:
* ARGF.each_file {|file| block } -> ARGF
* ARGF.each_file -> an_enumerator
*
* Iterates over each file in +ARGF+.
*
* If no block is given, an enumerator is returned instead.
*/
static VALUE
argf_each_file(VALUE argf)
{
RETURN_ENUMERATOR(argf, 0, 0);
FOREACH_ARGF() {
argf_block_call(rb_intern("yield_self"), 0, 0, argf);
}
return argf;
}

/*
* call-seq:
* ARGF.filename -> String
Expand Down Expand Up @@ -15577,6 +15596,7 @@ Init_IO(void)
rb_define_method(rb_cARGF, "each_byte", argf_each_byte, 0);
rb_define_method(rb_cARGF, "each_char", argf_each_char, 0);
rb_define_method(rb_cARGF, "each_codepoint", argf_each_codepoint, 0);
rb_define_method(rb_cARGF, "each_file", argf_each_file, 0);

rb_define_method(rb_cARGF, "read", argf_read, -1);
rb_define_method(rb_cARGF, "readpartial", argf_readpartial, -1);
Expand Down
6 changes: 6 additions & 0 deletions spec/ruby/core/argf/each_file_spec.rb
@@ -0,0 +1,6 @@
require_relative '../../spec_helper'
require_relative 'shared/each_file'

describe "ARGF.each_file" do
it_behaves_like :argf_each_file, :each_file
end
76 changes: 76 additions & 0 deletions spec/ruby/core/argf/shared/each_file.rb
@@ -0,0 +1,76 @@
describe :argf_each_file, shared: true do
before :each do
@file1_name = fixture __FILE__, "file1.txt"
@file2_name = fixture __FILE__, "file2.txt"
@filenames = [@file1_name, @file2_name]

@content = File.read(@file1_name)
@content += File.read(@file2_name)
end

it "is a public method" do
argf @filenames do
@argf.public_methods(false).should include(@method)
end
end

it "does not require arguments" do
argf @filenames do
@argf.method(@method).arity.should == 0
end
end

it "returns self when passed a block" do
argf @filenames do
@argf.send(@method) {}.should equal(@argf)
end
end

it "yields each file of all streams" do
argf @filenames do
filenames = []
content = ''
@argf.send(@method) do |file|
filenames << file.path
content += file.read
end
filenames.should == @filenames
content.should == @content
end
end

describe "stdin" do
before :each do
@stdin_name = fixture __FILE__, "stdin.txt"
@stdin = File.read(@stdin_name)
end

it "yields each file and stdin" do
stdin = ruby_exe("print ARGF.each_file.map(&:read).join", args: "#{@file1_name} #{@file2_name} - < #{@stdin_name}")
stdin.should == @content + @stdin
end

it "uses stdin as default" do
stdin = ruby_exe("print ARGF.each_file.map(&:read).join", args: "< #{@stdin_name}")
stdin.should == @stdin
end
end

describe "when no block is given" do
it "returns an Enumerator" do
argf @filenames do
@argf.send(@method).should be_an_instance_of(Enumerator)
end
end

describe "returned Enumerator" do
describe "size" do
it "should return nil" do
argf @filenames do
@argf.send(@method).size.should == nil
end
end
end
end
end
end

0 comments on commit cc8054c

Please sign in to comment.