diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5fdf4a1a3..b9972ce7f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,8 @@ Unreleased ---------- +- fix: :doc:`/scripts/csvformat` accepts empty input with :code:`--skip-header` and :code:`--no-header-row`, including when :code:`--skip-lines` consumes the entire input. + - feat: :doc:`/scripts/csvcut` adds an :code:`--ignore-unknown-columns` option to skip identifiers in :code:`-c/--columns` that do not match a column in the input. - feat: :doc:`/scripts/csvclean` adds a :code:`--remove-empty-columns` option to remove empty columns from standard output. - feat: :doc:`/scripts/in2csv` guesses the ``ndjson`` format for files with :code:`.ndjson`, :code:`.jsonl` and :code:`.jl` extensions. diff --git a/csvkit/utilities/csvformat.py b/csvkit/utilities/csvformat.py index c376f5ec1..8d54a03aa 100644 --- a/csvkit/utilities/csvformat.py +++ b/csvkit/utilities/csvformat.py @@ -91,12 +91,14 @@ def main(self): reader = agate.csv.reader(self.skip_lines(), **self.reader_kwargs) if self.args.no_header_row: # Peek at a row to get the number of columns. - _row = next(reader) + _row = next(reader, None) + if _row is None: + return headers = make_default_headers(len(_row)) reader = itertools.chain([headers, _row], reader) if self.args.skip_header: - next(reader) + next(reader, None) writer.writerows(reader) diff --git a/tests/test_utilities/test_csvformat.py b/tests/test_utilities/test_csvformat.py index 99042d1cd..b03873733 100644 --- a/tests/test_utilities/test_csvformat.py +++ b/tests/test_utilities/test_csvformat.py @@ -24,6 +24,19 @@ def test_skip_header(self): '1,2,3', ]) + def test_empty_with_header_options(self): + for options in (['--skip-header'], ['--no-header-row'], + ['--no-header-row', '--skip-header']): + with self.subTest(options=options): + with stdin_as_string(io.BytesIO(b'')): + self.assertLines(options, []) + + def test_skip_all_lines_with_header_options(self): + for options in (['--skip-header'], ['--no-header-row'], + ['--no-header-row', '--skip-header']): + with self.subTest(options=options): + self.assertLines(options + ['--skip-lines', '2', 'examples/dummy.csv'], []) + def test_skip_header_no_header_row(self): self.assertLines(['--no-header-row', '--skip-header', 'examples/no_header_row.csv'], [ '1,2,3',