diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5fdf4a1a3..d33df5ec7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Unreleased - 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. - fix: :code:`-C/--not-columns` now excludes the last column of an open-ended range (e.g. :code:`2-`). +- fix: An open-ended range (e.g. :code:`0:` or :code:`:1`) now respects the :code:`--zero` option. 2.2.0 - December 15, 2025 ------------------------- diff --git a/csvkit/cli.py b/csvkit/cli.py index e99acad34..fb0b991af 100644 --- a/csvkit/cli.py +++ b/csvkit/cli.py @@ -534,8 +534,10 @@ def _resolve_column_identifier(identifier, column_names, column_offset, ignore_u raise try: - a = int(a) if a else 1 - b = int(b) + 1 if b else len(column_names) + 1 + # The bounds are in identifier space, so the implicit ends of an open-ended range + # depend on whether identifiers are 1-based (the default) or 0-based (--zero). + a = int(a) if a else column_offset + b = int(b) + 1 if b else len(column_names) + column_offset except ValueError: if ignore_invalid_range: return [] diff --git a/tests/test_cli.py b/tests/test_cli.py index c4fe55d2f..40c2658d5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -84,9 +84,20 @@ def test_exclude_ignores_unknown_names_but_reports_invalid_ranges(self): def test_range_notation_open_ended(self): self.assertEqual([0, 1, 2], parse_column_identifiers(':3', self.headers)) + self.assertEqual([0, 1, 2, 3], parse_column_identifiers(':3', self.headers, column_offset=0)) target = list(range(3, len(self.headers))) # protect against devs adding to self.headers target.insert(0, 0) self.assertEqual(target, parse_column_identifiers('1,4:', self.headers)) + target = list(range(4, len(self.headers))) # protect against devs adding to self.headers + target.insert(0, 1) + self.assertEqual(target, parse_column_identifiers('1,4:', self.headers, column_offset=0)) + self.assertEqual(list(range(0, len(self.headers))), parse_column_identifiers('1:', self.headers)) + self.assertEqual(list(range(0, len(self.headers))), + parse_column_identifiers('0:', self.headers, column_offset=0)) + + # An open-ended exclusion range respects the column offset too. + self.assertEqual([0], parse_column_identifiers(None, self.headers, excluded_columns='2-')) + self.assertEqual([0], parse_column_identifiers(None, self.headers, column_offset=0, excluded_columns='1-'))