Skip to content

Commit e2ce56d

Browse files
committed
Add Zlib::GzipReader.zcat for handling multiple gzip streams in gz file
Most gzip tools support concatenated gz streams in a gz file. This offers a way to handle such gz files in Ruby. Fixes [Bug #9790] Fixes [Bug #11180] Fixes [Bug #14804]
1 parent eaa6b02 commit e2ce56d

File tree

2 files changed

+79
-0
lines changed

2 files changed

+79
-0
lines changed

ext/zlib/zlib.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3723,6 +3723,60 @@ rb_gzreader_s_open(int argc, VALUE *argv, VALUE klass)
37233723
return gzfile_s_open(argc, argv, klass, "rb");
37243724
}
37253725

3726+
/*
3727+
* Document-method: Zlib::GzipReader.zcat
3728+
*
3729+
* call-seq:
3730+
* Zlib::GzipReader.zcat(io, options = {}, &block) => nil
3731+
* Zlib::GzipReader.zcat(io, options = {}) => string
3732+
*
3733+
* Decompresses all gzip data in the +io+, handling multiple gzip
3734+
* streams until the end of the +io+. There should not be any non-gzip
3735+
* data after the gzip streams.
3736+
*
3737+
* If a block is given, it is yielded strings of uncompressed data,
3738+
* and the method returns +nil+.
3739+
* If a block is not given, the method returns the concatenation of
3740+
* all uncompressed data in all gzip streams.
3741+
*/
3742+
static VALUE
3743+
rb_gzreader_s_zcat(int argc, VALUE *argv, VALUE klass)
3744+
{
3745+
VALUE io, unused, obj, buf=0, tmpbuf;
3746+
long pos;
3747+
3748+
rb_check_arity(argc, 1, 2);
3749+
io = argv[0];
3750+
3751+
do {
3752+
obj = rb_funcallv(klass, rb_intern("new"), argc, argv);
3753+
if (rb_block_given_p()) {
3754+
rb_gzreader_each(0, 0, obj);
3755+
}
3756+
else {
3757+
if (!buf) {
3758+
buf = rb_str_new(0, 0);
3759+
}
3760+
tmpbuf = gzfile_read_all(get_gzfile(obj));
3761+
rb_str_cat(buf, RSTRING_PTR(tmpbuf), RSTRING_LEN(tmpbuf));
3762+
}
3763+
3764+
rb_gzreader_read(0, 0, obj);
3765+
pos = NUM2LONG(rb_funcall(io, rb_intern("pos"), 0));
3766+
unused = rb_gzreader_unused(obj);
3767+
rb_gzfile_finish(obj);
3768+
if (!NIL_P(unused)) {
3769+
pos -= NUM2LONG(rb_funcall(unused, rb_intern("length"), 0));
3770+
rb_funcall(io, rb_intern("pos="), 1, LONG2NUM(pos));
3771+
}
3772+
} while (pos < NUM2LONG(rb_funcall(io, rb_intern("size"), 0)));
3773+
3774+
if (rb_block_given_p()) {
3775+
return Qnil;
3776+
}
3777+
return buf;
3778+
}
3779+
37263780
/*
37273781
* Document-method: Zlib::GzipReader.new
37283782
*
@@ -4696,6 +4750,7 @@ Init_zlib(void)
46964750
rb_define_method(cGzipWriter, "puts", rb_gzwriter_puts, -1);
46974751

46984752
rb_define_singleton_method(cGzipReader, "open", rb_gzreader_s_open,-1);
4753+
rb_define_singleton_method(cGzipReader, "zcat", rb_gzreader_s_zcat, -1);
46994754
rb_define_alloc_func(cGzipReader, rb_gzreader_s_allocate);
47004755
rb_define_method(cGzipReader, "initialize", rb_gzreader_initialize, -1);
47014756
rb_define_method(cGzipReader, "rewind", rb_gzreader_rewind, 0);

test/zlib/test_zlib.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,30 @@ def test_set_dictionary
446446
end
447447

448448
class TestZlibGzipFile < Test::Unit::TestCase
449+
def test_gzip_reader_zcat
450+
Tempfile.create("test_zlib_gzip_file_to_io") {|t|
451+
gz = Zlib::GzipWriter.new(t)
452+
gz.print("foo")
453+
gz.close
454+
t = File.open(t.path, 'ab')
455+
gz = Zlib::GzipWriter.new(t)
456+
gz.print("bar")
457+
gz.close
458+
459+
results = []
460+
t = File.open(t.path)
461+
Zlib::GzipReader.zcat(t) do |str|
462+
results << str
463+
end
464+
assert_equal(["foo", "bar"], results)
465+
t.close
466+
467+
t = File.open(t.path)
468+
assert_equal("foobar", Zlib::GzipReader.zcat(t))
469+
t.close
470+
}
471+
end
472+
449473
def test_to_io
450474
Tempfile.create("test_zlib_gzip_file_to_io") {|t|
451475
t.close

0 commit comments

Comments
 (0)