Skip to content

Commit f688a15

Browse files
authored
Merge pull request #38 from pycompression/tostdout
Allow compressing to stdout like gzip and pigz on the isal.igzip CLI.
2 parents 49d1a5c + d1f440f commit f688a15

File tree

4 files changed

+158
-25
lines changed

4 files changed

+158
-25
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Changelog
77
.. This document is user facing. Please word the changes in such a way
88
.. that users understand how the changes affect the new version.
99
10+
version 0.5.0-dev
11+
-----------------
12+
+ A ``-c`` or ``--stdout`` flag was added to the CLI interface of isal.igzip.
13+
This allows it to behave more like the ``gzip`` or ``pigz`` command line
14+
interfaces.
15+
1016
version 0.4.0
1117
-----------------
1218
+ Move wheel building to cibuildwheel on github actions CI. Wheels are now

src/isal/igzip.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -276,30 +276,38 @@ def main():
276276
"-d", "--decompress", action="store_false",
277277
dest="compress",
278278
help="Decompress the file instead of compressing.")
279+
parser.add_argument("-c", "--stdout", action="store_true",
280+
help="write on standard output")
279281
args = parser.parse_args()
280282

281283
compresslevel = args.compresslevel or _COMPRESS_LEVEL_TRADEOFF
282284

283-
if args.file is None:
284-
if args.compress:
285-
in_file = sys.stdin.buffer
286-
out_file = IGzipFile(mode="wb", compresslevel=compresslevel,
287-
fileobj=sys.stdout.buffer)
288-
else:
289-
in_file = IGzipFile(mode="rb", fileobj=sys.stdin.buffer)
290-
out_file = sys.stdout.buffer
291-
else:
292-
if args.compress:
293-
in_file = io.open(args.file, mode="rb")
294-
out_file = open(args.file + ".gz", mode="wb",
295-
compresslevel=compresslevel)
296-
else:
297-
base, extension = os.path.splitext(args.file)
298-
if extension != ".gz":
299-
print(f"filename doesn't end in .gz: {args.file}")
300-
return
301-
in_file = open(args.file, "rb")
302-
out_file = io.open(base, "wb")
285+
# Determine output file
286+
if args.compress and (args.file is None or args.stdout):
287+
out_file = IGzipFile(mode="wb", compresslevel=compresslevel,
288+
fileobj=sys.stdout.buffer)
289+
elif args.compress and args.file is not None:
290+
out_file = open(args.file + ".gz", mode="wb",
291+
compresslevel=compresslevel)
292+
elif not args.compress and (args.file is None or args.stdout):
293+
out_file = sys.stdout.buffer
294+
elif not args.compress and args.file is not None:
295+
base, extension = os.path.splitext(args.file)
296+
if extension != ".gz":
297+
raise ValueError(f"filename doesn't end in .gz: {args.file}. "
298+
f"Cannot determine filename for output")
299+
out_file = io.open(base, "wb")
300+
301+
# Determine input file
302+
if args.compress and args.file is None:
303+
in_file = sys.stdin.buffer
304+
elif args.compress and args.file is not None:
305+
in_file = io.open(args.file, mode="rb")
306+
elif not args.compress and args.file is None:
307+
in_file = IGzipFile(mode="rb", fileobj=sys.stdin.buffer)
308+
elif not args.compress and args.file is not None:
309+
in_file = open(args.file, "rb")
310+
303311
try:
304312
while True:
305313
block = in_file.read(BUFFER_SIZE)

tests/test_gzip_compliance.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -816,11 +816,17 @@ def test_decompress_infile_outfile(self):
816816
self.assertTrue(os.path.exists(igzipname))
817817

818818
def test_decompress_infile_outfile_error(self):
819-
rc, out, err = assert_python_ok('-m', 'isal.igzip', '-d',
820-
'thisisatest.out')
821-
self.assertIn(b"filename doesn't end in .gz:", out)
822-
self.assertEqual(rc, 0)
823-
self.assertEqual(err, b'')
819+
rc, out, err = assert_python_failure('-m', 'isal.igzip', '-d',
820+
'thisisatest.out')
821+
# We take a divide from the original gzip module here. Error messages
822+
# should be printed in stderr. Also exit code should not be 0!
823+
# in python -m gzip -d mycompressedfile > decompressed
824+
# will simply make decompressed contents 'filename doesn't end in .gz'
825+
# without throwing an error. Crazy!
826+
# TODO: Report a bug in CPython for gzip module
827+
self.assertIn(b"filename doesn't end in .gz:", err)
828+
self.assertNotEqual(rc, 0)
829+
self.assertEqual(out, b'')
824830

825831
@create_and_remove_directory(TEMPDIR)
826832
def test_compress_stdin_outfile(self):

tests/test_igzip_cli.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright (c) 2020 Leiden University Medical Center
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to deal
5+
# in the Software without restriction, including without limitation the rights
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
# copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
# SOFTWARE.
20+
21+
"""Tests for the igzip CLI. Uses pytest which works better than unittest for
22+
these sort of tests. Meant to complement the gzip module compliance tests.
23+
It should improve coverage as well."""
24+
25+
import gzip
26+
import io
27+
import sys
28+
29+
from isal import igzip
30+
31+
import pytest
32+
33+
DATA = b'This is a simple test with igzip'
34+
COMPRESSED_DATA = gzip.compress(DATA)
35+
36+
37+
@pytest.mark.parametrize("level", range(1, 10))
38+
def test_decompress_stdin_stdout(capsysbinary, level):
39+
"""Test if the command line can decompress data that has been compressed
40+
by gzip at all levels."""
41+
mock_stdin = io.BytesIO(gzip.compress(DATA, level))
42+
sys.stdin = io.TextIOWrapper(mock_stdin)
43+
sys.argv = ["", "-d"]
44+
igzip.main()
45+
out, err = capsysbinary.readouterr()
46+
assert err == b''
47+
assert out == DATA
48+
49+
50+
@pytest.mark.parametrize("level", [str(x) for x in range(4)])
51+
def test_compress_stdin_stdout(capsysbinary, level):
52+
mock_stdin = io.BytesIO(DATA)
53+
sys.stdin = io.TextIOWrapper(mock_stdin)
54+
sys.argv = ["", f"-{level}"]
55+
igzip.main()
56+
out, err = capsysbinary.readouterr()
57+
assert err == b''
58+
assert gzip.decompress(out) == DATA
59+
60+
61+
def test_decompress_infile_outfile(tmp_path, capsysbinary):
62+
test_file = tmp_path / "test"
63+
compressed_temp = test_file.with_suffix(".gz")
64+
compressed_temp.write_bytes(gzip.compress(DATA))
65+
sys.argv = ['', '-d', str(compressed_temp)]
66+
igzip.main()
67+
out, err = capsysbinary.readouterr()
68+
assert err == b''
69+
assert out == b''
70+
assert test_file.exists()
71+
assert test_file.read_bytes() == DATA
72+
73+
74+
def test_compress_infile_outfile(tmp_path, capsysbinary):
75+
test_file = tmp_path / "test"
76+
test_file.write_bytes(DATA)
77+
sys.argv = ['', str(test_file)]
78+
igzip.main()
79+
out_file = test_file.with_suffix(".gz")
80+
out, err = capsysbinary.readouterr()
81+
assert err == b''
82+
assert out == b''
83+
assert out_file.exists()
84+
assert gzip.decompress(out_file.read_bytes()) == DATA
85+
86+
87+
def test_decompress_infile_outfile_error(capsysbinary):
88+
sys.argv = ['', '-d', 'thisisatest.out']
89+
with pytest.raises(ValueError) as error:
90+
igzip.main()
91+
assert error.match("filename doesn't end")
92+
out, err = capsysbinary.readouterr()
93+
assert out == b''
94+
95+
96+
def test_decompress_infile_stdout(capsysbinary, tmp_path):
97+
test_gz = tmp_path / "test.gz"
98+
test_gz.write_bytes(gzip.compress(DATA))
99+
sys.argv = ['', '-cd', str(test_gz)]
100+
igzip.main()
101+
out, err = capsysbinary.readouterr()
102+
assert out == DATA
103+
assert err == b''
104+
105+
106+
def test_compress_infile_stdout(capsysbinary, tmp_path):
107+
test = tmp_path / "test"
108+
test.write_bytes(DATA)
109+
sys.argv = ['', '-c', str(test)]
110+
igzip.main()
111+
out, err = capsysbinary.readouterr()
112+
assert gzip.decompress(out) == DATA
113+
assert err == b''

0 commit comments

Comments
 (0)