From a22ea327fb80ffd5a490ae858be93f1dd9d1acf4 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Tue, 26 Nov 2019 17:31:47 -0800 Subject: [PATCH] Add Zlib::GzipReader.each_file for handling multiple files in gz file Most gzip tools support concatenated gz streams in a gz file. This offers an easy way to handle such gz files in Ruby. Fixes [Bug #9790] Fixes [Bug #11180] Fixes [Bug #14804] --- ext/zlib/zlib.c | 44 ++++++++++++++++++++++++++++++++++++++++++ test/zlib/test_zlib.rb | 20 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 23466d1c94..b6d4a249b5 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -3723,6 +3723,49 @@ rb_gzreader_s_open(int argc, VALUE *argv, VALUE klass) return gzfile_s_open(argc, argv, klass, "rb"); } +/* + * Document-method: Zlib::GzipReader.each_file + * + * call-seq: + * Zlib::GzipReader.each_file(io, options = {}, &block) => nil + * + * Many gzip handling utilities support multiple concatenated files + * in a single .gz file. Zlib::GzipReader instances only support + * a single compressed file in .gz file, starting at the current + * position in the .gz file. Zlib::GzipReader.each_file yields + * Zlib::GzipReader instances, one per file in the .gz file. + * Example: + * + * Zlib::GzipReader.each_file(io) do |gz| + * puts gz.read + * end + */ +static VALUE +rb_gzreader_s_each_file(int argc, VALUE *argv, VALUE klass) +{ + VALUE io, unused, obj; + long pos; + + rb_check_arity(argc, 1, 2); + io = argv[0]; + + do { + obj = rb_funcallv(klass, rb_intern("new"), argc, argv); + rb_yield(obj); + + rb_gzreader_read(0, 0, obj); + pos = NUM2LONG(rb_funcall(io, rb_intern("pos"), 0)); + unused = rb_gzreader_unused(obj); + rb_gzfile_finish(obj); + if (!NIL_P(unused)) { + pos -= NUM2LONG(rb_funcall(unused, rb_intern("length"), 0)); + rb_funcall(io, rb_intern("pos="), 1, LONG2NUM(pos)); + } + } while (pos < NUM2LONG(rb_funcall(io, rb_intern("size"), 0))); + + return Qnil; +} + /* * Document-method: Zlib::GzipReader.new * @@ -4696,6 +4739,7 @@ Init_zlib(void) rb_define_method(cGzipWriter, "puts", rb_gzwriter_puts, -1); rb_define_singleton_method(cGzipReader, "open", rb_gzreader_s_open,-1); + rb_define_singleton_method(cGzipReader, "each_file", rb_gzreader_s_each_file, -1); rb_define_alloc_func(cGzipReader, rb_gzreader_s_allocate); rb_define_method(cGzipReader, "initialize", rb_gzreader_initialize, -1); rb_define_method(cGzipReader, "rewind", rb_gzreader_rewind, 0); diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 7d703d15e4..9b90342460 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -446,6 +446,26 @@ def test_set_dictionary end class TestZlibGzipFile < Test::Unit::TestCase + def test_gzip_reader_each_file + Tempfile.create("test_zlib_gzip_file_to_io") {|t| + gz = Zlib::GzipWriter.new(t) + gz.print("foo") + gz.close + t = File.open(t.path, 'ab') + gz = Zlib::GzipWriter.new(t) + gz.print("bar") + gz.close + + results = [] + t = File.open(t.path) + Zlib::GzipReader.each_file(t) do |f| + results << f.read + end + assert_equal(["foo", "bar"], results) + t.close + } + end + def test_to_io Tempfile.create("test_zlib_gzip_file_to_io") {|t| t.close -- 2.23.0