From e036f3fd52e3605a6209d98383ae7fe7365d042c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:40:49 +0000 Subject: [PATCH 01/21] Bump codecov from 2.0.9 to 2.0.16 Bumps [codecov](https://github.com/codecov/codecov-python) from 2.0.9 to 2.0.16. - [Release notes](https://github.com/codecov/codecov-python/releases) - [Changelog](https://github.com/codecov/codecov-python/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-python/compare/v2.0.9...v2.0.16) --- updated-dependencies: - dependency-name: codecov dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7846a4d..de1e52f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ autopep8==1.3.3 -codecov==2.0.9 +codecov==2.0.16 coverage==4.3.4 black>=20.8b1 Pygments>=2.4.0 From 09de670124702e694b15a501a252aaccbf1f6ca5 Mon Sep 17 00:00:00 2001 From: Satya Kommula Date: Thu, 1 Sep 2022 19:52:46 +0530 Subject: [PATCH 02/21] updated help with iterator printing --- docs/source/quickstart.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index 319655d..b304de2 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -15,7 +15,7 @@ CLI Helpers provides a simple way to display your tabular data (columns/rows) in >>> data = [[1, 'Asgard', True], [2, 'Camelot', False], [3, 'El Dorado', True]] >>> headers = ['id', 'city', 'visited'] - >>> print(tabular_output.format_output(data, headers, format_name='simple')) + >>> print("\n".join(tabular_output.format_output(iter(data), headers, format_name='simple'))) id city visited ---- --------- --------- @@ -57,7 +57,7 @@ same data from our first example and put it in the ``fancy_grid`` format:: >>> data = [[1, 'Asgard', True], [2, 'Camelot', False], [3, 'El Dorado', True]] >>> headers = ['id', 'city', 'visited'] - >>> print(formatter.format_output(data, headers, format_name='fancy_grid')) + >>> print("\n".join(formatter.format_output(iter(data), headers, format_name='fancy_grid'))) ╒══════╤═══════════╤═══════════╕ │ id │ city │ visited │ ╞══════╪═══════════╪═══════════╡ @@ -70,7 +70,7 @@ same data from our first example and put it in the ``fancy_grid`` format:: That was easy! How about CLI Helper's vertical table layout? - >>> print(formatter.format_output(data, headers, format_name='vertical')) + >>> print("\n".join(formatter.format_output(iter(data), headers, format_name='vertical'))) ***************************[ 1. row ]*************************** id | 1 city | Asgard @@ -93,7 +93,7 @@ object, you can specify a default formatter so you don't have to pass the format name each time you want to format your data:: >>> formatter = TabularOutputFormatter(format_name='plain') - >>> print(formatter.format_output(data, headers)) + >>> print("\n".join(formatter.format_output(iter(data), headers))) id city visited 1 Asgard True 2 Camelot False @@ -115,13 +115,13 @@ formats, we could:: >>> data = [[1, 1.5], [2, 19.605], [3, 100.0]] >>> headers = ['id', 'rating'] - >>> print(format_output(data, headers, format_name='simple', disable_numparse=True)) + >>> print("\n".join(format_output(iter(data), headers, format_name='simple', disable_numparse=True))) id rating ---- -------- 1 1.5 2 19.605 3 100.0 - >>> print(format_output(data, headers, format_name='simple', disable_numparse=False)) + >>> print("\n".join(format_output(iter(data), headers, format_name='simple', disable_numparse=False))) id rating ---- -------- 1 1.5 @@ -140,7 +140,7 @@ far-fetched example to prove the point:: >>> step = 3 >>> data = [range(n, n + step) for n in range(0, 9, step)] >>> headers = 'abc' - >>> print(format_output(data, headers, format_name='simple')) + >>> print("\n".join(format_output(iter(data), headers, format_name='simple'))) a b c --- --- --- 0 1 2 From 3d8e46bef72ba68014686346ab3bda25c64ced68 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Wed, 14 Sep 2022 08:37:17 -0400 Subject: [PATCH 03/21] don't escape newlines, etc. in ascii tables, and add ascii_escaped table format for users who want that behavior --- AUTHORS | 1 + CHANGELOG | 4 ++ .../tabular_output/tabulate_adapter.py | 22 +++++++++- tests/tabular_output/test_output_formatter.py | 41 ++++++++++++++++++- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4bad3dd..40f2b90 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,6 +24,7 @@ This project receives help from these awesome contributors: - Waldir Pimenta - Mel Dafert - Andrii Kohut +- Roland Walker Thanks ------ diff --git a/CHANGELOG b/CHANGELOG index 8563af2..da2b436 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ Changelog ========= +TBD +------------- +* don't escape newlines, etc. in ascii tables, and add ascii_escaped table format + Version 2.2.1 ------------- diff --git a/cli_helpers/tabular_output/tabulate_adapter.py b/cli_helpers/tabular_output/tabulate_adapter.py index a7eabc0..2c557f8 100644 --- a/cli_helpers/tabular_output/tabulate_adapter.py +++ b/cli_helpers/tabular_output/tabulate_adapter.py @@ -52,9 +52,25 @@ with_header_hide=None, ) +tabulate._table_formats["ascii_escaped"] = tabulate.TableFormat( + lineabove=tabulate.Line("+", "-", "+", "+"), + linebelowheader=tabulate.Line("+", "-", "+", "+"), + linebetweenrows=None, + linebelow=tabulate.Line("+", "-", "+", "+"), + headerrow=tabulate.DataRow("|", "|", "|"), + datarow=tabulate.DataRow("|", "|", "|"), + padding=1, + with_header_hide=None, +) + # "minimal" is the same as "plain", but without headers tabulate._table_formats["minimal"] = tabulate._table_formats["plain"] +tabulate.multiline_formats["psql_unicode"] = "psql_unicode" +tabulate.multiline_formats["double"] = "double" +tabulate.multiline_formats["ascii"] = "ascii" +tabulate.multiline_formats["minimal"] = "minimal" + supported_markup_formats = ( "mediawiki", "html", @@ -66,6 +82,7 @@ ) supported_table_formats = ( "ascii", + "ascii_escaped", "plain", "simple", "minimal", @@ -82,7 +99,10 @@ supported_formats = supported_markup_formats + supported_table_formats -default_kwargs = {"ascii": {"numalign": "left"}} +default_kwargs = { + "ascii": {"numalign": "left"}, + "ascii_escaped": {"numalign": "left"}, +} headless_formats = ("minimal",) diff --git a/tests/tabular_output/test_output_formatter.py b/tests/tabular_output/test_output_formatter.py index b307c1c..8e1fa92 100644 --- a/tests/tabular_output/test_output_formatter.py +++ b/tests/tabular_output/test_output_formatter.py @@ -21,6 +21,41 @@ def test_tabular_output_formatter(): ["hi", Decimal("1.1")], ["Pablo\rß\n", 0], ] + expected = dedent( + """\ + +-------+---------+ + | text | numeric | + +-------+---------+ + | abc | 1 | + | defg | 11.1 | + | hi | 1.1 | + | Pablo | 0 | + | ß | | + +-------+---------+""" + ) + + print(expected) + print( + "\n".join( + TabularOutputFormatter().format_output( + iter(data), headers, format_name="ascii" + ) + ) + ) + assert expected == "\n".join( + TabularOutputFormatter().format_output(iter(data), headers, format_name="ascii") + ) + + +def test_tabular_output_escaped(): + """Test the ascii_escaped output format.""" + headers = ["text", "numeric"] + data = [ + ["abc", Decimal(1)], + ["defg", Decimal("11.1")], + ["hi", Decimal("1.1")], + ["Pablo\rß\n", 0], + ] expected = dedent( """\ +------------+---------+ @@ -37,12 +72,14 @@ def test_tabular_output_formatter(): print( "\n".join( TabularOutputFormatter().format_output( - iter(data), headers, format_name="ascii" + iter(data), headers, format_name="ascii_escaped" ) ) ) assert expected == "\n".join( - TabularOutputFormatter().format_output(iter(data), headers, format_name="ascii") + TabularOutputFormatter().format_output( + iter(data), headers, format_name="ascii_escaped" + ) ) From 5d4eb1a4abad027c2426e2cc5fc0ca5224814ae0 Mon Sep 17 00:00:00 2001 From: LGTM Migrator Date: Thu, 10 Nov 2022 14:13:01 +0000 Subject: [PATCH 04/21] Add CodeQL workflow for GitHub code scanning --- .github/workflows/codeql.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..057ff98 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: "36 4 * * 1" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" From f576f67fb416172edc4b86ac46724319d0e2859a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20P=C3=A4rnaste?= Date: Mon, 13 Nov 2023 15:41:15 +0200 Subject: [PATCH 05/21] Updated tabulate version to latest, to fix `ImportError: cannot import name 'Iterable' from 'collections'` in pgcli. Also updated codecov version to latest that's in PyPI (version originally specified wasn't found) and pytest to latest (originally specified version wouldn't run under Python 3.11/Win). There are some tests that fail, but if I understand the pytest output correctly, they are all in test_config.py so not sure how functionally relevant they are. Additionally, pgcli started to work when installing this version from local folder. --- cli_helpers/__init__.py | 2 +- requirements-dev.txt | 4 ++-- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli_helpers/__init__.py b/cli_helpers/__init__.py index b19ee4b..ba51ced 100644 --- a/cli_helpers/__init__.py +++ b/cli_helpers/__init__.py @@ -1 +1 @@ -__version__ = "2.2.1" +__version__ = "2.2.2" diff --git a/requirements-dev.txt b/requirements-dev.txt index de1e52f..9b331c2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,9 +1,9 @@ autopep8==1.3.3 -codecov==2.0.16 +codecov==2.1.13 coverage==4.3.4 black>=20.8b1 Pygments>=2.4.0 -pytest==3.0.7 +pytest==7.4.3 pytest-cov==2.4.0 Sphinx==1.5.5 tox==2.7.0 diff --git a/setup.py b/setup.py index 46fbdc8..16ecf5b 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ def open_file(filename): long_description_content_type="text/x-rst", install_requires=[ "configobj >= 5.0.5", - "tabulate[widechars] >= 0.8.2", + "tabulate[widechars] >= 0.9.0", ], extras_require={ "styles": ["Pygments >= 1.6"], From d80188b4a55da64fb4a9a46d8283a963b2f7be53 Mon Sep 17 00:00:00 2001 From: manjabes Date: Fri, 12 Jan 2024 22:06:10 +0200 Subject: [PATCH 06/21] Revert version bump --- cli_helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli_helpers/__init__.py b/cli_helpers/__init__.py index ba51ced..b19ee4b 100644 --- a/cli_helpers/__init__.py +++ b/cli_helpers/__init__.py @@ -1 +1 @@ -__version__ = "2.2.2" +__version__ = "2.2.1" From 8e1a7f4bc0cda78c254d972bc2d5c398c84fdb47 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Mon, 5 Feb 2024 21:28:34 -0800 Subject: [PATCH 07/21] Prepare to release. --- CHANGELOG | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index da2b436..b08685a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,10 @@ Changelog ========= -TBD +Version 2.3.0 ------------- -* don't escape newlines, etc. in ascii tables, and add ascii_escaped table format +* Don't escape newlines in `ascii` tables, and add `ascii_escaped` table format. +* Updated tabulate version to latest, to fix ImportError in pgcli. Version 2.2.1 ------------- From 0e30253be7fdfcdd48b7e75fe8fdbc218b4775f7 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Mon, 5 Feb 2024 21:32:56 -0800 Subject: [PATCH 08/21] Releasing version 2.3.0 --- cli_helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli_helpers/__init__.py b/cli_helpers/__init__.py index b19ee4b..55e4709 100644 --- a/cli_helpers/__init__.py +++ b/cli_helpers/__init__.py @@ -1 +1 @@ -__version__ = "2.2.1" +__version__ = "2.3.0" From e228b3c255430c2532fcdcaeeb223364d780c730 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Mon, 5 Feb 2024 21:35:27 -0800 Subject: [PATCH 09/21] Bump the version; already have 2.3.0. --- CHANGELOG | 160 +++++++++++++++++++++++------------------------------- 1 file changed, 69 insertions(+), 91 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b08685a..7b7bd02 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,156 +1,134 @@ -Changelog -========= +# Changelog -Version 2.3.0 -------------- -* Don't escape newlines in `ascii` tables, and add `ascii_escaped` table format. -* Updated tabulate version to latest, to fix ImportError in pgcli. +## Version 2.3.1 -Version 2.2.1 -------------- +- Don't escape newlines in `ascii` tables, and add `ascii_escaped` table format. +- Updated tabulate version to latest, to fix ImportError in pgcli. + +## Version 2.2.1 (released on 2022-01-17) -* Fix pygments tokens passed as strings +- Fix pygments tokens passed as strings -Version 2.2.0 -------------- +## Version 2.2.0 (released on 2021-08-27) -* Remove dependency on terminaltables -* Add psql_unicode table format -* Add minimal table format -* Fix pip2 installing py3-only versions -* Format unprintable bytes (eg 0x00, 0x01) as hex +- Remove dependency on terminaltables +- Add psql_unicode table format +- Add minimal table format +- Fix pip2 installing py3-only versions +- Format unprintable bytes (eg 0x00, 0x01) as hex -Version 2.1.0 -------------- +## Version 2.1.0 (released on 2020-07-29) -* Speed up output styling of tables. +- Speed up output styling of tables. -Version 2.0.1 -------------- +## Version 2.0.1 (released on 2020-05-27) -* Fix newline escaping in plain-text formatters (ascii, double, github) -* Use built-in unittest.mock instead of mock. +- Fix newline escaping in plain-text formatters (ascii, double, github) +- Use built-in unittest.mock instead of mock. -Version 2.0.0 -------------- +## Version 2.0.0 (released on 2020-05-26) -* Remove Python 2.7 and 3.5. -* Style config for missing value. +- Remove Python 2.7 and 3.5. +- Style config for missing value. -Version 1.2.1 -------------- +## Version 1.2.1 (released on 2019-06-09) -* Pin Pygments to >= 2.4.0 for tests. -* Remove Python 3.4 from tests and Trove classifier. -* Add an option to skip truncating multi-line strings. -* When truncating long strings, add ellipsis. +- Pin Pygments to >= 2.4.0 for tests. +- Remove Python 3.4 from tests and Trove classifier. +- Add an option to skip truncating multi-line strings. +- When truncating long strings, add ellipsis. -Version 1.2.0 -------------- +## Version 1.2.0 (released on 2019-04-05) -* Fix issue with writing non-ASCII characters to config files. -* Run tests on Python 3.7. -* Use twine check during packaging tests. -* Rename old tsv format to csv-tab (because it add quotes), introduce new tsv output adapter. -* Truncate long fields for tabular display. -* Return the supported table formats as unicode. -* Override tab with 4 spaces for terminal tables. +- Fix issue with writing non-ASCII characters to config files. +- Run tests on Python 3.7. +- Use twine check during packaging tests. +- Rename old tsv format to csv-tab (because it add quotes), introduce new tsv output adapter. +- Truncate long fields for tabular display. +- Return the supported table formats as unicode. +- Override tab with 4 spaces for terminal tables. -Version 1.1.0 -------------- +## Version 1.1.0 (released on 2018-10-18) -* Adds config file reading/writing. -* Style formatted tables with Pygments (optional). +- Adds config file reading/writing. +- Style formatted tables with Pygments (optional). -Version 1.0.2 -------------- +## Version 1.0.2 (released on 2018-04-07) -* Copy unit test from pgcli -* Use safe float for unit test -* Move strip_ansi from tests.utils to cli_helpers.utils +- Copy unit test from pgcli +- Use safe float for unit test +- Move strip_ansi from tests.utils to cli_helpers.utils -Version 1.0.1 -------------- +## Version 1.0.1 (released on 2017-11-27) -* Output all unicode for terminaltables, add unit test. +- Output all unicode for terminaltables, add unit test. -Version 1.0.0 -------------- +## Version 1.0.0 (released on 2017-10-11) -* Output as generator -* Use backports.csv only for py2 -* Require tabulate as a dependency instead of using vendored module. -* Drop support for Python 3.3. - +- Output as generator +- Use backports.csv only for py2 +- Require tabulate as a dependency instead of using vendored module. +- Drop support for Python 3.3. -Version 0.2.3 -------------- +## Version 0.2.3 (released on 2017-08-01) -* Fix unicode error on Python 2 with newlines in output row. -* Fixes to accept iterator. - +- Fix unicode error on Python 2 with newlines in output row. +- Fixes to accept iterator. -Version 0.2.2 -------------- +## Version 0.2.2 (released on 2017-07-16) -* Fix IndexError from being raised with uneven rows. +- Fix IndexError from being raised with uneven rows. - -Version 0.2.1 -------------- +## Version 0.2.1 (released on 2017-07-11) -* Run tests on macOS via Travis. -* Fix unicode issues on Python 2 (csv and styling output). - +- Run tests on macOS via Travis. +- Fix unicode issues on Python 2 (csv and styling output). -Version 0.2.0 -------------- +## Version 0.2.0 (released on 2017-06-23) -* Make vertical table separator more customizable. -* Add format numbers preprocessor. -* Add test coverage reports. -* Add ability to pass additional preprocessors when formatting output. -* Don't install tests.tabular_output. -* Add .gitignore -* Coverage for tox tests. -* Style formatted output with Pygments (optional). -* Fix issue where tabulate can't handle ANSI escape codes in default values. -* Run tests on Windows via Appveyor. - +- Make vertical table separator more customizable. +- Add format numbers preprocessor. +- Add test coverage reports. +- Add ability to pass additional preprocessors when formatting output. +- Don't install tests.tabular_output. +- Add .gitignore +- Coverage for tox tests. +- Style formatted output with Pygments (optional). +- Fix issue where tabulate can't handle ANSI escape codes in default values. +- Run tests on Windows via Appveyor. -Version 0.1.0 -------------- +## Version 0.1.0 (released on 2017-05-01) -* Pretty print tabular data using a variety of formatting libraries. +- Pretty print tabular data using a variety of formatting libraries. From 3e91c8164ea4c8e3e79dae6c7714f79c3227e74f Mon Sep 17 00:00:00 2001 From: Doug Harris Date: Tue, 31 Dec 2024 11:05:28 -0500 Subject: [PATCH 10/21] Black reformatted --- cli_helpers/tabular_output/preprocessors.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cli_helpers/tabular_output/preprocessors.py b/cli_helpers/tabular_output/preprocessors.py index 8342d67..5e44a1c 100644 --- a/cli_helpers/tabular_output/preprocessors.py +++ b/cli_helpers/tabular_output/preprocessors.py @@ -125,9 +125,11 @@ def escape_newlines(data, headers, **_): return ( ( [ - v.replace("\r", r"\r").replace("\n", r"\n") - if isinstance(v, text_type) - else v + ( + v.replace("\r", r"\r").replace("\n", r"\n") + if isinstance(v, text_type) + else v + ) for v in row ] for row in data From 14e1fe290c273f97e6c5ba12cc03873aa26fa4dd Mon Sep 17 00:00:00 2001 From: Doug Harris Date: Tue, 31 Dec 2024 11:29:18 -0500 Subject: [PATCH 11/21] Adding format_timestamps preprocessor --- AUTHORS | 1 + CHANGELOG | 5 +++ cli_helpers/tabular_output/preprocessors.py | 42 +++++++++++++++++++++ tests/tabular_output/test_preprocessors.py | 23 +++++++++++ 4 files changed, 71 insertions(+) diff --git a/AUTHORS b/AUTHORS index 40f2b90..536b972 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,6 +25,7 @@ This project receives help from these awesome contributors: - Mel Dafert - Andrii Kohut - Roland Walker +- Doug Harris Thanks ------ diff --git a/CHANGELOG b/CHANGELOG index 7b7bd02..58dcb83 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ # Changelog +TBD +--- + +- Added format_timestamps preprocessor for per-column date/time formatting. + ## Version 2.3.1 - Don't escape newlines in `ascii` tables, and add `ascii_escaped` table format. diff --git a/cli_helpers/tabular_output/preprocessors.py b/cli_helpers/tabular_output/preprocessors.py index 5e44a1c..a47fec0 100644 --- a/cli_helpers/tabular_output/preprocessors.py +++ b/cli_helpers/tabular_output/preprocessors.py @@ -2,6 +2,7 @@ """These preprocessor functions are used to process data prior to output.""" import string +from datetime import datetime from cli_helpers import utils from cli_helpers.compat import text_type, int_types, float_types, HAS_PYGMENTS, Token @@ -353,3 +354,44 @@ def _format_number(field, column_type): [_format_number(v, column_types[i]) for i, v in enumerate(row)] for row in data ) return data, headers + + +def format_timestamps(data, headers, column_date_formats=None, **_): + """Format timestamps according to user preference. + + This allows for per-column formatting for date, time, or datetime like data. + + Add a `column_date_formats` section to your config file with separate lines for each column + that you'd like to specify a format using `name=format`. Use standard Python strftime + formatting strings + + Example: `signup_date = "%Y-%m-%d"` + + :param iterable data: An :term:`iterable` (e.g. list) of rows. + :param iterable headers: The column headers. + :param str column_date_format: The format strings to use for specific columns. + :return: The processed data and headers. + :rtype: tuple + + """ + if column_date_formats is None: + return iter(data), headers + + def _format_timestamp(value, name, column_date_formats): + if name not in column_date_formats: + return value + try: + dt = datetime.fromisoformat(value) + return dt.strftime(column_date_formats[name]) + except (ValueError, TypeError): + # not a date + return value + + data = ( + [ + _format_timestamp(v, headers[i], column_date_formats) + for i, v in enumerate(row) + ] + for row in data + ) + return data, headers diff --git a/tests/tabular_output/test_preprocessors.py b/tests/tabular_output/test_preprocessors.py index e428bfa..5ebd06d 100644 --- a/tests/tabular_output/test_preprocessors.py +++ b/tests/tabular_output/test_preprocessors.py @@ -16,6 +16,7 @@ override_tab_value, style_output, format_numbers, + format_timestamps, ) if HAS_PYGMENTS: @@ -348,3 +349,25 @@ def test_enforce_iterable(): assert False, "{} doesn't return iterable".format(name) if isinstance(preprocessed[1], types.GeneratorType): assert False, "{} returns headers as iterator".format(name) + + +def test_format_timestamps(): + data = ( + ("name1", "2024-12-13T18:32:22", "2024-12-13T19:32:22", "2024-12-13T20:32:22"), + ("name2", "2025-02-13T02:32:22", "2025-02-13T02:32:22", "2025-02-13T02:32:22"), + ("name3", None, "not-actually-timestamp", "2025-02-13T02:32:22"), + ) + headers = ["name", "date_col", "datetime_col", "unchanged_col"] + column_date_formats = { + "date_col": "%Y-%m-%d", + "datetime_col": "%I:%M:%S %m/%d/%y", + } + result_data, result_headers = format_timestamps(data, headers, column_date_formats) + + expected = [ + ["name1", "2024-12-13", "07:32:22 12/13/24", "2024-12-13T20:32:22"], + ["name2", "2025-02-13", "02:32:22 02/13/25", "2025-02-13T02:32:22"], + ["name3", None, "not-actually-timestamp", "2025-02-13T02:32:22"], + ] + assert expected == list(result_data) + assert headers == result_headers From 861a2f57e13365a6408479add6db638521fced6e Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Mon, 10 Mar 2025 16:54:00 -0700 Subject: [PATCH 12/21] Changelog update before release. --- .gitignore | 1 + CHANGELOG | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 213f266..9986fe3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ __pycache__ /cli_helpers_dev .idea/ .cache/ +.vscode/ \ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG index 58dcb83..b17256b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,8 @@ # Changelog -TBD ---- +## Version 2.4.0 + +(released on 2025-03-10) - Added format_timestamps preprocessor for per-column date/time formatting. From e4fe506b332deacc053143326bb2876580248a83 Mon Sep 17 00:00:00 2001 From: Irina Truong Date: Mon, 10 Mar 2025 17:03:05 -0700 Subject: [PATCH 13/21] Releasing version 2.4.0 --- cli_helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli_helpers/__init__.py b/cli_helpers/__init__.py index 55e4709..3d67cd6 100644 --- a/cli_helpers/__init__.py +++ b/cli_helpers/__init__.py @@ -1 +1 @@ -__version__ = "2.3.0" +__version__ = "2.4.0" From 3f8ae6d344c73a18ef6c9f678d93c5b2e3b1b71a Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Thu, 10 Jul 2025 07:01:52 -0400 Subject: [PATCH 14/21] add headerless variants of CSV and TSV formats * csv-noheader * csv-tab-noheader * tsv_noheader Some formats seem to use dashes whereas other formats use underscores. Underscores should probably be preferred, and even better would be to accept both. --- CHANGELOG | 2 ++ .../delimited_output_adapter.py | 11 +++---- .../tabular_output/tsv_output_adapter.py | 14 ++++++--- .../test_delimited_output_adapter.py | 30 +++++++++++++++++++ .../tabular_output/test_tsv_output_adapter.py | 13 ++++++++ 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b17256b..3d80a69 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ # Changelog +- Added noheader CSV and TSV output formats. + ## Version 2.4.0 (released on 2025-03-10) diff --git a/cli_helpers/tabular_output/delimited_output_adapter.py b/cli_helpers/tabular_output/delimited_output_adapter.py index b812456..a8d51cc 100644 --- a/cli_helpers/tabular_output/delimited_output_adapter.py +++ b/cli_helpers/tabular_output/delimited_output_adapter.py @@ -8,7 +8,7 @@ from cli_helpers.utils import filter_dict_by_key from .preprocessors import bytes_to_string, override_missing_value -supported_formats = ("csv", "csv-tab") +supported_formats = ("csv", "csv-tab", "csv-noheader", "csv-tab-noheader") preprocessors = (override_missing_value, bytes_to_string) @@ -35,9 +35,9 @@ def adapter(data, headers, table_format="csv", **kwargs): "skipinitialspace", "strict", ) - if table_format == "csv": + if table_format in ("csv", "csv-noheader"): delimiter = "," - elif table_format == "csv-tab": + elif table_format in ("csv-tab", "csv-tab-noheader"): delimiter = "\t" else: raise ValueError("Invalid table_format specified.") @@ -47,8 +47,9 @@ def adapter(data, headers, table_format="csv", **kwargs): l = linewriter() writer = csv.writer(l, **ckwargs) - writer.writerow(headers) - yield l.line + if "noheader" not in table_format: + writer.writerow(headers) + yield l.line for row in data: l.reset() diff --git a/cli_helpers/tabular_output/tsv_output_adapter.py b/cli_helpers/tabular_output/tsv_output_adapter.py index 75518b3..c0dfc34 100644 --- a/cli_helpers/tabular_output/tsv_output_adapter.py +++ b/cli_helpers/tabular_output/tsv_output_adapter.py @@ -7,11 +7,17 @@ from itertools import chain from cli_helpers.utils import replace -supported_formats = ("tsv",) +supported_formats = ("tsv", "tsv_noheader") preprocessors = (override_missing_value, bytes_to_string, convert_to_string) -def adapter(data, headers, **kwargs): +def adapter(data, headers, table_format="tsv", **kwargs): """Wrap the formatting inside a function for TabularOutputFormatter.""" - for row in chain((headers,), data): - yield "\t".join((replace(r, (("\n", r"\n"), ("\t", r"\t"))) for r in row)) + if table_format == "tsv": + for row in chain((headers,), data): + yield "\t".join((replace(r, (("\n", r"\n"), ("\t", r"\t"))) for r in row)) + elif table_format == "tsv_noheader": + for row in data: + yield "\t".join((replace(r, (("\n", r"\n"), ("\t", r"\t"))) for r in row)) + else: + raise ValueError(f"Invalid table_format specified: {table_format}.") diff --git a/tests/tabular_output/test_delimited_output_adapter.py b/tests/tabular_output/test_delimited_output_adapter.py index 86a622e..c9553ba 100644 --- a/tests/tabular_output/test_delimited_output_adapter.py +++ b/tests/tabular_output/test_delimited_output_adapter.py @@ -42,6 +42,36 @@ def test_csv_wrapper(): list(output) +def test_csv_noheader_wrapper(): + """Test the delimited output adapter without headers.""" + # Test comma-delimited output. + data = [["abc", "1"], ["d", "456"]] + headers = ["letters", "number"] + output = delimited_output_adapter.adapter( + iter(data), + headers, + table_format="csv-noheader", + dialect="unix", + ) + assert "\n".join(output) == dedent( + '''\ + "abc","1"\n\ + "d","456"''' + ) + + # Test tab-delimited output. + data = [["abc", "1"], ["d", "456"]] + headers = ["letters", "number"] + output = delimited_output_adapter.adapter( + iter(data), headers, table_format="csv-tab-noheader", dialect="unix" + ) + assert "\n".join(output) == dedent( + '''\ + "abc"\t"1"\n\ + "d"\t"456"''' + ) + + def test_unicode_with_csv(): """Test that the csv wrapper can handle non-ascii characters.""" data = [["观音", "1"], ["Ποσειδῶν", "456"]] diff --git a/tests/tabular_output/test_tsv_output_adapter.py b/tests/tabular_output/test_tsv_output_adapter.py index 9249d87..8097a77 100644 --- a/tests/tabular_output/test_tsv_output_adapter.py +++ b/tests/tabular_output/test_tsv_output_adapter.py @@ -23,6 +23,19 @@ def test_tsv_wrapper(): ) +def test_tsv_headerless_wrapper(): + """Test the tsv headerless_output adapter.""" + # Test tab-delimited output. + data = [["ab\r\nc", "1"], ["d", "456"]] + headers = ["letters", "number"] + output = tsv_output_adapter.adapter(iter(data), headers, table_format="tsv_noheader") + assert "\n".join(output) == dedent( + """\ + ab\r\\nc\t1\n\ + d\t456""" + ) + + def test_unicode_with_tsv(): """Test that the tsv wrapper can handle non-ascii characters.""" data = [["观音", "1"], ["Ποσειδῶν", "456"]] From d17b7b8e1a12c1536f0d93e4caaa90585f4e9ed4 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Wed, 9 Jul 2025 16:51:04 -0400 Subject: [PATCH 15/21] Add two JSON-based output formats * jsonl: JSONlines format: in which each row is represented on a line as a JSON object, with the column names recapitulated on every line as the property names. * jsonl_escaped: like jsonl, except that JSON escaping is applied for non-ASCII characters. Most users will want jsonl. The implementation file has the generic name json_output_adapter.py in case other JSONish forms are desired. This output format can be combined with jq redirection in mycli: * https://github.com/dbcli/mycli/pull/1248 --- CHANGELOG | 1 + .../tabular_output/json_output_adapter.py | 27 +++++++++++++ .../test_json_output_adapter.py | 40 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 cli_helpers/tabular_output/json_output_adapter.py create mode 100644 tests/tabular_output/test_json_output_adapter.py diff --git a/CHANGELOG b/CHANGELOG index 3d80a69..54331eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ # Changelog - Added noheader CSV and TSV output formats. +- Added `jsonl` and `jsonl_escaped` output formats. ## Version 2.4.0 diff --git a/cli_helpers/tabular_output/json_output_adapter.py b/cli_helpers/tabular_output/json_output_adapter.py new file mode 100644 index 0000000..8176fce --- /dev/null +++ b/cli_helpers/tabular_output/json_output_adapter.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +"""A JSON data output adapter""" + +from itertools import chain +import json + +from .preprocessors import bytes_to_string, override_missing_value, convert_to_string + +supported_formats = ("jsonl", "jsonl_escaped") +preprocessors = (override_missing_value, bytes_to_string, convert_to_string) + + +def adapter(data, headers, table_format="jsonl", **_kwargs): + """Wrap the formatting inside a function for TabularOutputFormatter.""" + if table_format == "jsonl": + ensure_ascii = False + elif table_format == "jsonl_escaped": + ensure_ascii = True + else: + raise ValueError("Invalid table_format specified.") + + for row in chain(data): + yield json.dumps( + dict(zip(headers, row, strict=True)), + separators=(",", ":"), + ensure_ascii=ensure_ascii, + ) diff --git a/tests/tabular_output/test_json_output_adapter.py b/tests/tabular_output/test_json_output_adapter.py new file mode 100644 index 0000000..d0bd202 --- /dev/null +++ b/tests/tabular_output/test_json_output_adapter.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +"""Test the json output adapter.""" + +from __future__ import unicode_literals + +from cli_helpers.tabular_output import json_output_adapter + + +def test_jsonl_wrapper(): + """Test the jsonl output adapter.""" + # Test jsonl output. + data = [["ab\r\nc", 1], ["d", 456]] + headers = ["letters", "number"] + output = json_output_adapter.adapter(iter(data), headers, table_format="jsonl") + assert ( + "\n".join(output) + == """{"letters":"ab\\r\\nc","number":1}\n{"letters":"d","number":456}""" + ) + + +def test_unicode_with_jsonl(): + """Test that the jsonl wrapper can pass through non-ascii characters.""" + data = [["观音", 1], ["Ποσειδῶν", 456]] + headers = ["letters", "number"] + output = json_output_adapter.adapter(data, headers, table_format="jsonl") + assert ( + "\n".join(output) + == """{"letters":"观音","number":1}\n{"letters":"Ποσειδῶν","number":456}""" + ) + + +def test_unicode_with_jsonl_esc(): + """Test that the jsonl_escaped wrapper JSON-escapes non-ascii characters.""" + data = [["观音", 1], ["Ποσειδῶν", 456]] + headers = ["letters", "number"] + output = json_output_adapter.adapter(data, headers, table_format="jsonl_escaped") + assert ( + "\n".join(output) + == """{"letters":"\\u89c2\\u97f3","number":1}\n{"letters":"\\u03a0\\u03bf\\u03c3\\u03b5\\u03b9\\u03b4\\u1ff6\\u03bd","number":456}""" + ) From 24fb5fc2bdc561301c34e3e7e572a51352741ba2 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Thu, 10 Jul 2025 15:26:37 -0400 Subject: [PATCH 16/21] bump version for v2.5.0 --- CHANGELOG | 2 ++ cli_helpers/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 54331eb..e756bfa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ # Changelog +## Version 2.5.0 + - Added noheader CSV and TSV output formats. - Added `jsonl` and `jsonl_escaped` output formats. diff --git a/cli_helpers/__init__.py b/cli_helpers/__init__.py index 3d67cd6..50062f8 100644 --- a/cli_helpers/__init__.py +++ b/cli_helpers/__init__.py @@ -1 +1 @@ -__version__ = "2.4.0" +__version__ = "2.5.0" From 49bfdb922915a9906ca6af23e1cab9216b055253 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Fri, 11 Jul 2025 07:22:23 -0400 Subject: [PATCH 17/21] followups for JSON output formats * formats must be _registered_, or they aren't available for use in client software * make JSON formats able to encode Decimal values, emitting floats * make JSON formats able to encode None/NULL values, emitting nulls --- CHANGELOG | 3 +++ .../tabular_output/json_output_adapter.py | 14 +++++++++-- .../tabular_output/output_formatter.py | 12 ++++++++++ .../test_json_output_adapter.py | 24 +++++++++++++++++++ tests/tabular_output/test_output_formatter.py | 7 ++++++ 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e756bfa..264948a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Changelog +- Register the JSON formats so they are actually usable. +- Make JSON formats able to encode Decimals and None/NULLs. + ## Version 2.5.0 - Added noheader CSV and TSV output formats. diff --git a/cli_helpers/tabular_output/json_output_adapter.py b/cli_helpers/tabular_output/json_output_adapter.py index 8176fce..9153fef 100644 --- a/cli_helpers/tabular_output/json_output_adapter.py +++ b/cli_helpers/tabular_output/json_output_adapter.py @@ -1,13 +1,22 @@ # -*- coding: utf-8 -*- """A JSON data output adapter""" +from decimal import Decimal from itertools import chain import json -from .preprocessors import bytes_to_string, override_missing_value, convert_to_string +from .preprocessors import bytes_to_string supported_formats = ("jsonl", "jsonl_escaped") -preprocessors = (override_missing_value, bytes_to_string, convert_to_string) +preprocessors = (bytes_to_string,) + + +class CustomEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, Decimal): + return float(o) + else: + return super(CustomEncoder, self).default(o) def adapter(data, headers, table_format="jsonl", **_kwargs): @@ -22,6 +31,7 @@ def adapter(data, headers, table_format="jsonl", **_kwargs): for row in chain(data): yield json.dumps( dict(zip(headers, row, strict=True)), + cls=CustomEncoder, separators=(",", ":"), ensure_ascii=ensure_ascii, ) diff --git a/cli_helpers/tabular_output/output_formatter.py b/cli_helpers/tabular_output/output_formatter.py index 6cadf6c..7fa2873 100644 --- a/cli_helpers/tabular_output/output_formatter.py +++ b/cli_helpers/tabular_output/output_formatter.py @@ -17,6 +17,7 @@ vertical_table_adapter, tabulate_adapter, tsv_output_adapter, + json_output_adapter, ) from decimal import Decimal @@ -253,3 +254,14 @@ def format_output(data, headers, format_name, **kwargs): tsv_output_adapter.preprocessors, {"table_format": tsv_format, "missing_value": "", "max_field_width": None}, ) + +for json_format in json_output_adapter.supported_formats: + TabularOutputFormatter.register_new_formatter( + json_format, + json_output_adapter.adapter, + json_output_adapter.preprocessors, + { + "table_format": json_format, + "max_field_width": None, + }, + ) diff --git a/tests/tabular_output/test_json_output_adapter.py b/tests/tabular_output/test_json_output_adapter.py index d0bd202..53dfeae 100644 --- a/tests/tabular_output/test_json_output_adapter.py +++ b/tests/tabular_output/test_json_output_adapter.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals +from decimal import Decimal + from cli_helpers.tabular_output import json_output_adapter @@ -29,6 +31,28 @@ def test_unicode_with_jsonl(): ) +def test_decimal_with_jsonl(): + """Test that the jsonl wrapper can pass through Decimal values.""" + data = [["ab\r\nc", 1], ["d", Decimal(4.56)]] + headers = ["letters", "number"] + output = json_output_adapter.adapter(iter(data), headers, table_format="jsonl") + assert ( + "\n".join(output) + == """{"letters":"ab\\r\\nc","number":1}\n{"letters":"d","number":4.56}""" + ) + + +def test_null_with_jsonl(): + """Test that the jsonl wrapper can pass through null values.""" + data = [["ab\r\nc", None], ["d", None]] + headers = ["letters", "value"] + output = json_output_adapter.adapter(iter(data), headers, table_format="jsonl") + assert ( + "\n".join(output) + == """{"letters":"ab\\r\\nc","value":null}\n{"letters":"d","value":null}""" + ) + + def test_unicode_with_jsonl_esc(): """Test that the jsonl_escaped wrapper JSON-escapes non-ascii characters.""" data = [["观音", 1], ["Ποσειδῶν", 456]] diff --git a/tests/tabular_output/test_output_formatter.py b/tests/tabular_output/test_output_formatter.py index 8e1fa92..a76ca7c 100644 --- a/tests/tabular_output/test_output_formatter.py +++ b/tests/tabular_output/test_output_formatter.py @@ -177,6 +177,13 @@ def test_unsupported_format(): formatter.format_output((), (), format_name="foobar") +def test_supported_json_formats(): + """Test that the JSONl formats are known.""" + formatter = TabularOutputFormatter() + assert "jsonl" in formatter.supported_formats + assert "jsonl_escaped" in formatter.supported_formats + + def test_tabulate_ansi_escape_in_default_value(): """Test that ANSI escape codes work with tabulate.""" From a94997008b955647a4d0c5951b5d19b9105efc36 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Sat, 12 Jul 2025 12:24:30 -0400 Subject: [PATCH 18/21] update changelog for release v2.6.0 --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 264948a..bfdd299 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,10 @@ # Changelog +## Version 2.6.0 + - Register the JSON formats so they are actually usable. - Make JSON formats able to encode Decimals and None/NULLs. - + ## Version 2.5.0 - Added noheader CSV and TSV output formats. From 22881bb13b98f0fdf85faf61af3c5c92a2184044 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Sat, 12 Jul 2025 12:30:28 -0400 Subject: [PATCH 19/21] Releasing version 2.6.0 --- cli_helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli_helpers/__init__.py b/cli_helpers/__init__.py index 50062f8..e5e59e3 100644 --- a/cli_helpers/__init__.py +++ b/cli_helpers/__init__.py @@ -1 +1 @@ -__version__ = "2.5.0" +__version__ = "2.6.0" From 6f804e9636499f2c37154192e8d60c47670403cc Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Sat, 26 Jul 2025 08:59:25 -0400 Subject: [PATCH 20/21] add mysql/mysql_unicode output formats The difference being that for the mysql formats, numbers are right aligned. Addresses https://github.com/dbcli/mycli/issues/1102 . --- CHANGELOG | 10 +++ .../tabular_output/tabulate_adapter.py | 28 ++++++++ tests/tabular_output/test_output_formatter.py | 72 +++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index bfdd299..0ed7bee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,22 @@ # Changelog +## Version 2.7.0 + +(released on 2025-07-28) + +- Add `mysql` and `mysql_unicode` output formats which right-align numbers. + ## Version 2.6.0 +(released on 2025-07-12) + - Register the JSON formats so they are actually usable. - Make JSON formats able to encode Decimals and None/NULLs. ## Version 2.5.0 +(released on 2025-07-10) + - Added noheader CSV and TSV output formats. - Added `jsonl` and `jsonl_escaped` output formats. diff --git a/cli_helpers/tabular_output/tabulate_adapter.py b/cli_helpers/tabular_output/tabulate_adapter.py index 2c557f8..f175f20 100644 --- a/cli_helpers/tabular_output/tabulate_adapter.py +++ b/cli_helpers/tabular_output/tabulate_adapter.py @@ -63,6 +63,28 @@ with_header_hide=None, ) +tabulate._table_formats["mysql"] = tabulate.TableFormat( + lineabove=tabulate.Line("+", "-", "+", "+"), + linebelowheader=tabulate.Line("+", "-", "+", "+"), + linebetweenrows=None, + linebelow=tabulate.Line("+", "-", "+", "+"), + headerrow=tabulate.DataRow("|", "|", "|"), + datarow=tabulate.DataRow("|", "|", "|"), + padding=1, + with_header_hide=None, +) + +tabulate._table_formats["mysql_unicode"] = tabulate.TableFormat( + lineabove=tabulate.Line("┌", "─", "┬", "┐"), + linebelowheader=tabulate.Line("├", "─", "┼", "┤"), + linebetweenrows=None, + linebelow=tabulate.Line("└", "─", "┴", "┘"), + headerrow=tabulate.DataRow("│", "│", "│"), + datarow=tabulate.DataRow("│", "│", "│"), + padding=1, + with_header_hide=None, +) + # "minimal" is the same as "plain", but without headers tabulate._table_formats["minimal"] = tabulate._table_formats["plain"] @@ -70,6 +92,8 @@ tabulate.multiline_formats["double"] = "double" tabulate.multiline_formats["ascii"] = "ascii" tabulate.multiline_formats["minimal"] = "minimal" +tabulate.multiline_formats["mysql"] = "mysql" +tabulate.multiline_formats["mysql_unicode"] = "mysql_unicode" supported_markup_formats = ( "mediawiki", @@ -95,6 +119,8 @@ "rst", "github", "double", + "mysql", + "mysql_unicode", ) supported_formats = supported_markup_formats + supported_table_formats @@ -102,6 +128,8 @@ default_kwargs = { "ascii": {"numalign": "left"}, "ascii_escaped": {"numalign": "left"}, + "mysql": {"numalign": "right"}, + "mysql_unicode": {"numalign": "right"}, } headless_formats = ("minimal",) diff --git a/tests/tabular_output/test_output_formatter.py b/tests/tabular_output/test_output_formatter.py index a76ca7c..4bce9e4 100644 --- a/tests/tabular_output/test_output_formatter.py +++ b/tests/tabular_output/test_output_formatter.py @@ -83,6 +83,78 @@ def test_tabular_output_escaped(): ) +def test_tabular_output_mysql(): + """Test the mysql output format.""" + headers = ["text", "numeric"] + data = [ + ["abc", Decimal(1)], + ["defg", Decimal("11.1")], + ["hi", Decimal("1.1")], + ["Pablo\rß\n", 0], + ] + expected = dedent( + """\ + +-------+---------+ + | text | numeric | + +-------+---------+ + | abc | 1 | + | defg | 11.1 | + | hi | 1.1 | + | Pablo | 0 | + | ß | | + +-------+---------+""" + ) + + print(expected) + print( + "\n".join( + TabularOutputFormatter().format_output( + iter(data), headers, format_name="mysql" + ) + ) + ) + assert expected == "\n".join( + TabularOutputFormatter().format_output(iter(data), headers, format_name="mysql") + ) + + +def test_tabular_output_mysql_unicode(): + """Test the mysql_unicode output format.""" + headers = ["text", "numeric"] + data = [ + ["abc", Decimal(1)], + ["defg", Decimal("11.1")], + ["hi", Decimal("1.1")], + ["Pablo\rß\n", 0], + ] + expected = dedent( + """\ + ┌───────┬─────────┐ + │ text │ numeric │ + ├───────┼─────────┤ + │ abc │ 1 │ + │ defg │ 11.1 │ + │ hi │ 1.1 │ + │ Pablo │ 0 │ + │ ß │ │ + └───────┴─────────┘""" + ) + + print(expected) + print( + "\n".join( + TabularOutputFormatter().format_output( + iter(data), headers, format_name="mysql_unicode" + ) + ) + ) + assert expected == "\n".join( + TabularOutputFormatter().format_output( + iter(data), headers, format_name="mysql_unicode" + ) + ) + + def test_tabular_format_output_wrapper(): """Test the format_output wrapper.""" data = [["1", None], ["2", "Sam"], ["3", "Joe"]] From c34ae9fc68c4c8a74124c2cd416fc06148d71445 Mon Sep 17 00:00:00 2001 From: Roland Walker Date: Mon, 28 Jul 2025 06:32:55 -0400 Subject: [PATCH 21/21] Releasing version 2.7.0 --- cli_helpers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli_helpers/__init__.py b/cli_helpers/__init__.py index e5e59e3..2614ce9 100644 --- a/cli_helpers/__init__.py +++ b/cli_helpers/__init__.py @@ -1 +1 @@ -__version__ = "2.6.0" +__version__ = "2.7.0"