From 8012fbef0e7f155d39aeb0d30e60116223127f8a Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 18 Dec 2022 17:13:20 -0700 Subject: [PATCH 01/30] fix: Fix setup error occurring on Windows and added Windows ci tests (#85) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/python-test.yml | 51 ++++++++++++++++++++++--------- setup.py | 2 +- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 7694a92..45adbf5 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -13,23 +13,44 @@ on: - main jobs: - build: + test-ubuntu: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements.txt -e ".[dev]" + python -m pip install --pre tox-gh-actions + - name: Test with pytest + run: | + # remove '.' in python-version and prepend with 'py' to get the correct tox env + tox -e py$(echo ${{ matrix.python-version }} | sed 's/\.//g') + + test-windows: + runs-on: windows-latest + steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -r requirements.txt -e ".[dev]" - - name: Test with pytest - run: | - # remove '.' in python-version and prepend with 'py' to get the correct tox env - tox -e py$(echo ${{ matrix.python-version }} | sed 's/\.//g') + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.7" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements.txt -e ".[dev]" + python -m pip install --pre tox-gh-actions + - name: Test with pytest + run: tox + env: + PLATFORM: windows-latest diff --git a/setup.py b/setup.py index 0b3faa6..69b0dd0 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def long_description(): # check if README.md exists if not os.path.exists("README.md"): return "" - with open("README.md", "r") as fh: + with open("README.md", "r", encoding="utf-8") as fh: return fh.read() From d22c42301077859eae511bb5e0708d8d90f1975f Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 18 Dec 2022 17:13:51 -0700 Subject: [PATCH 02/30] chore: Bump to 1.0.3 --- table2ascii/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/table2ascii/__init__.py b/table2ascii/__init__.py index ebd9744..902a153 100644 --- a/table2ascii/__init__.py +++ b/table2ascii/__init__.py @@ -8,7 +8,7 @@ from .table_style import TableStyle from .table_to_ascii import table2ascii -__version__ = "1.0.2" +__version__ = "1.0.3" __all__ = [ "Alignment", From b969185ad8b7e8ea05e6547fde92a7f59d67bec0 Mon Sep 17 00:00:00 2001 From: Alexander Matthew Date: Tue, 20 Dec 2022 04:50:08 +0700 Subject: [PATCH 03/30] fix: make dependencies and other build arguments static (#86) Co-authored-by: Jonah Lawrence --- .github/workflows/lint.yml | 4 +-- .github/workflows/python-test.yml | 4 +-- pyproject.toml | 32 +++++++++++++++--- requirements.txt | 2 -- setup.py | 56 +------------------------------ table2ascii/__init__.py | 2 +- tox.ini | 1 - 7 files changed, 33 insertions(+), 68 deletions(-) delete mode 100644 requirements.txt diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7381f37..861e42d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,7 +49,7 @@ jobs: setup.py - name: Install dependencies - run: python -m pip install -r requirements.txt -e ".[dev]" + run: python -m pip install -e ".[dev]" - name: Set up pyright run: echo "PYRIGHT_VERSION=$(python -c 'import pyright; print(pyright.__pyright_version__)')" >> $GITHUB_ENV @@ -94,7 +94,7 @@ jobs: setup.py - name: Install dependencies - run: python -m pip install -r requirements.txt -e ".[dev]" -e ".[docs]" + run: python -m pip install -e ".[dev]" -e ".[docs]" - name: Run mypy run: mypy . diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 45adbf5..d550962 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -29,7 +29,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -r requirements.txt -e ".[dev]" + python -m pip install -e ".[dev]" python -m pip install --pre tox-gh-actions - name: Test with pytest run: | @@ -48,7 +48,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -r requirements.txt -e ".[dev]" + python -m pip install -e ".[dev]" python -m pip install --pre tox-gh-actions - name: Test with pytest run: tox diff --git a/pyproject.toml b/pyproject.toml index 49c617f..9db538c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,14 @@ [build-system] -requires = [ - "setuptools>=42", - "wheel" -] +requires = ["setuptools>=42"] build-backend = "setuptools.build_meta" [project] name = "table2ascii" authors = [{name = "Jonah Lawrence", email = "jonah@freshidea.com"}] -dynamic = ["version", "description", "readme", "dependencies", "optional-dependencies"] +dynamic = ["version"] +description = "Convert 2D Python lists into Unicode/ASCII tables" +readme = "README.md" requires-python = ">=3.7" license = {file = "LICENSE"} keywords = ["table", "ascii", "unicode", "formatter"] @@ -38,7 +37,30 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] +dependencies = [ + "typing-extensions>=3.7.4; python_version<'3.8'", + "wcwidth<1", +] +[project.optional-dependencies] +docs = [ + "enum-tools", + "sphinx", + "sphinx-autobuild", + "sphinx-toolbox", + "sphinxcontrib_trio", + "sphinxext-opengraph", + "sphinx-book-theme==0.3.3", +] +dev = [ + "mypy>=0.982,<1", + "pre-commit>=2.0.0,<3", + "pyright>=1.0.0,<2", + "pytest>=6.0.0,<8", + "slotscheck>=0.1.0,<1", + "taskipy>=1.0.0,<2", + "tox>=3.0.0,<5", +] [project.urls] documentation = "https://table2ascii.rtfd.io" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b6ab4e4..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -typing-extensions>=3.7.4; python_version<'3.8' -wcwidth<1 \ No newline at end of file diff --git a/setup.py b/setup.py index 69b0dd0..5fbf35d 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ # /usr/bin/env python -import os import re from setuptools import setup @@ -14,57 +13,4 @@ def version(): return version.group(1) -def long_description(): - # check if README.md exists - if not os.path.exists("README.md"): - return "" - with open("README.md", "r", encoding="utf-8") as fh: - return fh.read() - - -def requirements(): - # check if requirements.txt exists - if not os.path.exists("requirements.txt"): - return [] - with open("requirements.txt") as f: - return f.read().splitlines() - - -extras_require = { - "docs": [ - "enum-tools", - "sphinx", - "sphinx-autobuild", - "sphinx-toolbox", - "sphinxcontrib_trio", - "sphinxext-opengraph", - "sphinx-book-theme==0.3.3", - ], - "dev": [ - "mypy>=0.982,<1", - "pre-commit>=2.0.0,<3", - "pyright>=1.0.0,<2", - "pytest>=6.0.0,<8", - "slotscheck>=0.1.0,<1", - "taskipy>=1.0.0,<2", - "tox>=3.0.0,<5", - ], -} - -setup( - name="table2ascii", - version=version(), - author="Jonah Lawrence", - author_email="jonah@freshidea.com", - description="Convert 2D Python lists into Unicode/Ascii tables", - long_description=long_description(), - long_description_content_type="text/markdown", - url="https://github.com/DenverCoder1/table2ascii", - packages=["table2ascii"], - install_requires=requirements(), - extras_require=extras_require, - setup_requires=[], - tests_require=[ - "pytest>=6.2,<8", - ], -) +setup(name="table2ascii", version=version()) diff --git a/table2ascii/__init__.py b/table2ascii/__init__.py index 902a153..19fa31a 100644 --- a/table2ascii/__init__.py +++ b/table2ascii/__init__.py @@ -8,7 +8,7 @@ from .table_style import TableStyle from .table_to_ascii import table2ascii -__version__ = "1.0.3" +__version__ = "1.0.4" __all__ = [ "Alignment", diff --git a/tox.ini b/tox.ini index d505b52..448f7b6 100644 --- a/tox.ini +++ b/tox.ini @@ -4,5 +4,4 @@ envlist = py37, py38, py39, py310, py311 [testenv] deps = pytest - -rrequirements.txt commands = pytest tests -s From 3ff00e4a4a402ca770f9b67b0c90443f043fa717 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Dec 2022 18:49:03 -0700 Subject: [PATCH 04/30] [pre-commit.ci] pre-commit autoupdate (#88) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c6b8e0..c07673c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: name: Running black in all files. - repo: https://github.com/pycqa/isort - rev: 5.11.1 + rev: v5.11.3 hooks: - id: isort args: ["--profile", "black", "--extend-skip", "table2ascii"] From 8d799b5cf9dd7d820502868dd11e7ac78907572a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Dec 2022 19:08:45 -0700 Subject: [PATCH 05/30] [pre-commit.ci] pre-commit autoupdate (#89) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c07673c..293d837 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: name: Running black in all files. - repo: https://github.com/pycqa/isort - rev: v5.11.3 + rev: 5.11.4 hooks: - id: isort args: ["--profile", "black", "--extend-skip", "table2ascii"] From 12bab03ebe9c67712a13ff56e2758e2ff71d0107 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 14:11:25 -0700 Subject: [PATCH 06/30] feat: Added decimal alignment option (#90) --- README.md | 50 ++++---- docs/source/usage.rst | 208 +++++++++++++++++----------------- table2ascii/alignment.py | 32 ++++-- table2ascii/table_to_ascii.py | 83 ++++++++++++-- tests/test_alignments.py | 25 +++- 5 files changed, 254 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index ffc0261..de9e288 100644 --- a/README.md +++ b/README.md @@ -69,22 +69,26 @@ print(output) from table2ascii import table2ascii, Alignment output = table2ascii( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - first_col_heading=True, - column_widths=[5, 5, 5, 5, 5], - alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4, + header=["Product", "Category", "Price", "Rating"], + body=[ + ["Milk", "Dairy", "$2.99", "6.283"], + ["Cheese", "Dairy", "$10.99", "8.2"], + ["Apples", "Produce", "$0.99", "10.00"], + ], + column_widths=[12, 12, 12, 12], + alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL], ) print(output) """ -╔═════╦═══════════════════════╗ -║ # ║ G H R S ║ -╟─────╫───────────────────────╢ -║ 1 ║ 30 40 35 30 ║ -║ 2 ║ 30 40 35 30 ║ -╚═════╩═══════════════════════╝ +╔═══════════════════════════════════════════════════╗ +║ Product Category Price Rating ║ +╟───────────────────────────────────────────────────╢ +║ Milk Dairy $2.99 6.283 ║ +║ Cheese Dairy $10.99 8.2 ║ +║ Apples Produce $0.99 10.00 ║ +╚═══════════════════════════════════════════════════╝ """ ``` @@ -199,18 +203,18 @@ All parameters are optional. At least one of `header`, `body`, and `footer` must Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.html#table2ascii) for more information. -| Option | Type | Default | Description | -| :-----------------: | :----------------------------: | :-------------------: | :-------------------------------------------------------------------------------: | -| `header` | `Sequence[SupportsStr]` | `None` | First table row seperated by header row separator. Values should support `str()` | -| `body` | `Sequence[Sequence[Sequence]]` | `None` | 2D List of rows for the main section of the table. Values should support `str()` | -| `footer` | `Sequence[Sequence]` | `None` | Last table row seperated by header row separator. Values should support `str()` | -| `column_widths` | `Sequence[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column | -| `alignments` | `Sequence[Alignment]` | `None` (all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) | -| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* | -| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column | -| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column | -| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border | -| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | +| Option | Type | Default | Description | +| :-----------------: | :----------------------------: | :-------------------: | :--------------------------------------------------------------------------------------------------: | +| `header` | `Sequence[SupportsStr]` | `None` | First table row seperated by header row separator. Values should support `str()` | +| `body` | `Sequence[Sequence[Sequence]]` | `None` | 2D List of rows for the main section of the table. Values should support `str()` | +| `footer` | `Sequence[Sequence]` | `None` | Last table row seperated by header row separator. Values should support `str()` | +| `column_widths` | `Sequence[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column | +| `alignments` | `Sequence[Alignment]` | `None` (all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | +| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* | +| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column | +| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column | +| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border | +| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | [wcwidth]: https://pypi.org/project/wcwidth/ diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 70f8487..823c23e 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -6,115 +6,119 @@ Convert lists to ASCII tables .. code:: py - from table2ascii import table2ascii - - output = table2ascii( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - ) - - print(output) - - """ - ╔═════════════════════════════╗ - ║ # G H R S ║ - ╟─────────────────────────────╢ - ║ 1 30 40 35 30 ║ - ║ 2 30 40 35 30 ║ - ╟─────────────────────────────╢ - ║ SUM 130 140 135 130 ║ - ╚═════════════════════════════╝ - """ + from table2ascii import table2ascii + + output = table2ascii( + header=["#", "G", "H", "R", "S"], + body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], + footer=["SUM", "130", "140", "135", "130"], + ) + + print(output) + + """ + ╔═════════════════════════════╗ + ║ # G H R S ║ + ╟─────────────────────────────╢ + ║ 1 30 40 35 30 ║ + ║ 2 30 40 35 30 ║ + ╟─────────────────────────────╢ + ║ SUM 130 140 135 130 ║ + ╚═════════════════════════════╝ + """ Set first or last column headings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: py - from table2ascii import table2ascii + from table2ascii import table2ascii - output = table2ascii( - body=[["Assignment", "30", "40", "35", "30"], ["Bonus", "10", "20", "5", "10"]], - first_col_heading=True, - ) + output = table2ascii( + body=[["Assignment", "30", "40", "35", "30"], ["Bonus", "10", "20", "5", "10"]], + first_col_heading=True, + ) - print(output) + print(output) - """ - ╔════════════╦═══════════════════╗ - ║ Assignment ║ 30 40 35 30 ║ - ║ Bonus ║ 10 20 5 10 ║ - ╚════════════╩═══════════════════╝ - """ + """ + ╔════════════╦═══════════════════╗ + ║ Assignment ║ 30 40 35 30 ║ + ║ Bonus ║ 10 20 5 10 ║ + ╚════════════╩═══════════════════╝ + """ Set column widths and alignments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: py - from table2ascii import table2ascii, Alignment + from table2ascii import table2ascii, Alignment - output = table2ascii( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - first_col_heading=True, - column_widths=[5, 5, 5, 5, 5], - alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4, - ) + output = table2ascii( + header=["Product", "Category", "Price", "Rating"], + body=[ + ["Milk", "Dairy", "$2.99", "6.283"], + ["Cheese", "Dairy", "$10.99", "8.2"], + ["Apples", "Produce", "$0.99", "10.00"], + ], + column_widths=[12, 12, 12, 12], + alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL], + ) - print(output) + print(output) - """ - ╔═════╦═══════════════════════╗ - ║ # ║ G H R S ║ - ╟─────╫───────────────────────╢ - ║ 1 ║ 30 40 35 30 ║ - ║ 2 ║ 30 40 35 30 ║ - ╚═════╩═══════════════════════╝ - """ + """ + ╔═══════════════════════════════════════════════════╗ + ║ Product Category Price Rating ║ + ╟───────────────────────────────────────────────────╢ + ║ Milk Dairy $2.99 6.283 ║ + ║ Cheese Dairy $10.99 8.2 ║ + ║ Apples Produce $0.99 10.00 ║ + ╚═══════════════════════════════════════════════════╝ + """ Use a preset style ~~~~~~~~~~~~~~~~~~ .. code:: py - from table2ascii import table2ascii, Alignment, PresetStyle - - output = table2ascii( - header=["First", "Second", "Third", "Fourth"], - body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], - column_widths=[10, 10, 10, 10], - style=PresetStyle.ascii_box - ) - - print(output) - - """ - +----------+----------+----------+----------+ - | First | Second | Third | Fourth | - +----------+----------+----------+----------+ - | 10 | 30 | 40 | 35 | - +----------+----------+----------+----------+ - | 20 | 10 | 20 | 5 | - +----------+----------+----------+----------+ - """ - - output = table2ascii( - header=["First", "Second", "Third", "Fourth"], - body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], - style=PresetStyle.plain, - cell_padding=0, - alignments=[Alignment.LEFT] * 4, - ) - - print(output) - - """ - First Second Third Fourth - 10 30 40 35 - 20 10 20 5 - """ + from table2ascii import table2ascii, Alignment, PresetStyle + + output = table2ascii( + header=["First", "Second", "Third", "Fourth"], + body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], + column_widths=[10, 10, 10, 10], + style=PresetStyle.ascii_box + ) + + print(output) + + """ + +----------+----------+----------+----------+ + | First | Second | Third | Fourth | + +----------+----------+----------+----------+ + | 10 | 30 | 40 | 35 | + +----------+----------+----------+----------+ + | 20 | 10 | 20 | 5 | + +----------+----------+----------+----------+ + """ + + output = table2ascii( + header=["First", "Second", "Third", "Fourth"], + body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], + style=PresetStyle.plain, + cell_padding=0, + alignments=[Alignment.LEFT] * 4, + ) + + print(output) + + """ + First Second Third Fourth + 10 30 40 35 + 20 10 20 5 + """ Define a custom style ~~~~~~~~~~~~~~~~~~~~~ @@ -123,27 +127,27 @@ Check :ref:`TableStyle` for more info. .. code:: py - from table2ascii import table2ascii, TableStyle + from table2ascii import table2ascii, TableStyle - my_style = TableStyle.from_string("*-..*||:+-+:+ *''*") + my_style = TableStyle.from_string("*-..*||:+-+:+ *''*") - output = table2ascii( - header=["First", "Second", "Third"], - body=[["10", "30", "40"], ["20", "10", "20"], ["30", "20", "30"]], - style=my_style, - ) + output = table2ascii( + header=["First", "Second", "Third"], + body=[["10", "30", "40"], ["20", "10", "20"], ["30", "20", "30"]], + style=my_style, + ) - print(output) + print(output) - """ - *-------.--------.-------* - | First : Second : Third | - +-------:--------:-------+ - | 10 : 30 : 40 | - | 20 : 10 : 20 | - | 30 : 20 : 30 | - *-------'--------'-------* - """ + """ + *-------.--------.-------* + | First : Second : Third | + +-------:--------:-------+ + | 10 : 30 : 40 | + | 20 : 10 : 20 | + | 30 : 20 : 30 | + *-------'--------'-------* + """ Merge adjacent cells ~~~~~~~~~~~~~~~~~~~~ diff --git a/table2ascii/alignment.py b/table2ascii/alignment.py index 0a8e5f7..2ae487f 100644 --- a/table2ascii/alignment.py +++ b/table2ascii/alignment.py @@ -9,26 +9,40 @@ class Alignment(IntEnum): from table2ascii import Alignment, table2ascii table2ascii( - header=["Product", "Category", "Price", "In Stock"], + header=["Product", "Category", "Price", "Rating"], body=[ - ["Milk", "Dairy", "$2.99", "Yes"], - ["Cheese", "Dairy", "$10.99", "No"], - ["Apples", "Produce", "$0.99", "Yes"], + ["Milk", "Dairy", "$2.99", "6.28318"], + ["Cheese", "Dairy", "$10.99", "8.2"], + ["Apples", "Produce", "$0.99", "10.00"], ], - alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.LEFT], + alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL], ) \"\"\" ╔════════════════════════════════════════╗ - ║ Product Category Price In Stock ║ + ║ Product Category Price Rating ║ ╟────────────────────────────────────────╢ - ║ Milk Dairy $2.99 Yes ║ - ║ Cheese Dairy $10.99 No ║ - ║ Apples Produce $0.99 Yes ║ + ║ Milk Dairy $2.99 6.28318 ║ + ║ Cheese Dairy $10.99 8.2 ║ + ║ Apples Produce $0.99 10.00 ║ ╚════════════════════════════════════════╝ \"\"\" + + .. note:: + + If the :attr:`DECIMAL` alignment type is used, any cell values that are + not valid decimal numbers will be aligned to the center. Decimal numbers + include integers, floats, and strings containing only + :meth:`decimal ` characters and at most one decimal point. + + .. versionchanged:: 1.1.0 + + Added :attr:`DECIMAL` alignment -- align decimal numbers such that + the decimal point is aligned with the decimal point of all other numbers + in the same column. """ LEFT = 0 CENTER = 1 RIGHT = 2 + DECIMAL = 3 diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 8116467..0510bd1 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -67,15 +67,20 @@ def __init__( if not header and not body and not footer: raise NoHeaderBodyOrFooterError() - # calculate or use given column widths - self.__column_widths = self.__calculate_column_widths(options.column_widths) - self.__alignments = options.alignments or [Alignment.CENTER] * self.__columns # check if alignments specified have a different number of columns if options.alignments and len(options.alignments) != self.__columns: raise AlignmentCountMismatchError(options.alignments, self.__columns) + # keep track of the number widths and positions of the decimal points for decimal alignment + decimal_widths, decimal_positions = self.__calculate_decimal_widths_and_positions() + self.__decimal_widths: list[int] = decimal_widths + self.__decimal_positions: list[int] = decimal_positions + + # calculate or use given column widths + self.__column_widths = self.__calculate_column_widths(options.column_widths) + # check if the cell padding is valid if self.__cell_padding < 0: raise InvalidCellPaddingError(self.__cell_padding) @@ -125,10 +130,45 @@ def get_column_width(row: Sequence[SupportsStr], column: int) -> int: header_size = get_column_width(self.__header, i) if self.__header else 0 body_size = max(get_column_width(row, i) for row in self.__body) if self.__body else 0 footer_size = get_column_width(self.__footer, i) if self.__footer else 0 + min_text_width = max(header_size, body_size, footer_size, self.__decimal_widths[i]) # get the max and add 2 for padding each side with a space depending on cell padding - column_widths.append(max(header_size, body_size, footer_size) + self.__cell_padding * 2) + column_widths.append(min_text_width + self.__cell_padding * 2) return column_widths + def __calculate_decimal_widths_and_positions(self) -> tuple[list[int], list[int]]: + """Calculate the positions of the decimal points for decimal alignment. + + Returns: + A tuple of the widths of the decimal numbers in each column + and the positions of the decimal points in each column + """ + decimal_widths: list[int] = [0] * self.__columns + decimal_positions: list[int] = [0] * self.__columns + for i in range(self.__columns): + if self.__alignments[i] != Alignment.DECIMAL: + continue + # list all values in the i-th column of header, body, and footer + values = [str(self.__header[i])] if self.__header else [] + values += [str(row[i]) for row in self.__body] if self.__body else [] + values += [str(self.__footer[i])] if self.__footer else [] + # filter out values that are not numbers and split at the decimal point + split_values = [ + self.__split_decimal(value) for value in values if self.__is_number(value) + ] + # skip if there are no decimal values + if len(split_values) == 0: + continue + # get the max number of digits before and after the decimal point + max_before_decimal = max(self.__str_width(parts[0]) for parts in split_values) + max_after_decimal = max(self.__str_width(parts[1]) for parts in split_values) + # add 1 for the decimal point if there are any decimal point values + has_decimal = any(self.__is_number(value) and "." in value for value in values) + # store the total width of the decimal numbers in the column + decimal_widths[i] = max_before_decimal + max_after_decimal + int(has_decimal) + # store the max digits before the decimal point for decimal alignment + decimal_positions[i] = max_before_decimal + return decimal_widths, decimal_positions + def __calculate_column_widths( self, user_column_widths: Sequence[int | None] | None ) -> list[int]: @@ -169,30 +209,42 @@ def __fix_rows_beginning_with_merge(self) -> None: if self.__footer and self.__footer[0] == Merge.LEFT: self.__footer[0] = "" - def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> str: + def __pad(self, cell_value: SupportsStr, width: int, col_index: int) -> str: """Pad a string of text to a given width with specified alignment Args: cell_value: The text in the cell to pad width: The width in characters to pad to - alignment: The alignment to use + col_index: The index of the column Returns: The padded text """ + alignment = self.__alignments[col_index] text = str(cell_value) + # if using decimal alignment, pad such that the decimal point + # is aligned to the column's decimal position + if alignment == Alignment.DECIMAL and self.__is_number(text): + decimal_position = self.__decimal_positions[col_index] + decimal_max_width = self.__decimal_widths[col_index] + text_before_decimal = self.__split_decimal(text)[0] + before = " " * (decimal_position - self.__str_width(text_before_decimal)) + after = " " * (decimal_max_width - self.__str_width(text) - len(before)) + text = f"{before}{text}{after}" + # add minimum cell padding around the text padding = " " * self.__cell_padding padded_text = f"{padding}{text}{padding}" text_width = self.__str_width(padded_text) + # pad the text based on the alignment if alignment == Alignment.LEFT: # pad with spaces on the end return padded_text + (" " * (width - text_width)) - if alignment == Alignment.CENTER: + elif alignment in (Alignment.CENTER, Alignment.DECIMAL): # pad with spaces, half on each side before = " " * floor((width - text_width) / 2) after = " " * ceil((width - text_width) / 2) return before + padded_text + after - if alignment == Alignment.RIGHT: + elif alignment == Alignment.RIGHT: # pad with spaces at the beginning return (" " * (width - text_width)) + padded_text raise InvalidAlignmentError(alignment) @@ -403,7 +455,7 @@ def __get_padded_cell_line_content( return self.__pad( cell_value=col_content, width=pad_width, - alignment=self.__alignments[col_index], + col_index=col_index, ) def __top_edge_to_ascii(self) -> str: @@ -530,6 +582,19 @@ def __str_width(self, text: str) -> int: # if use_wcwidth is False or wcswidth fails, fall back to len return width if width >= 0 else len(text) + @staticmethod + def __is_number(text: str) -> bool: + """Returns True if the string is a number, with or without a decimal point""" + return text.replace(".", "", 1).isdecimal() + + @staticmethod + def __split_decimal(text: str) -> tuple[str, str]: + """Splits a string into a tuple of the integer and decimal parts""" + if "." in text: + before, after = text.split(".", 1) + return before, after + return text, "" + def to_ascii(self) -> str: """Generates a formatted ASCII table diff --git a/tests/test_alignments.py b/tests/test_alignments.py index 3e8b874..107c32b 100644 --- a/tests/test_alignments.py +++ b/tests/test_alignments.py @@ -1,6 +1,6 @@ import pytest -from table2ascii import Alignment, table2ascii as t2a +from table2ascii import Alignment, PresetStyle, table2ascii as t2a from table2ascii.exceptions import AlignmentCountMismatchError, InvalidAlignmentError @@ -97,3 +97,26 @@ def test_alignments_multiline_data(): "╚═══════════════════════════════════════════╝" ) assert text == expected + + +def test_decimal_alignment(): + text = t2a( + header=["1.1.1", "G", "Long Header", "H", "R", "3.8"], + body=[[100.00001, 2, 3.14, 33, "AB", "1.5"], [10.0001, 22.0, 2.718, 3, "CD", "3.03"]], + footer=[10000.01, "123", 10.0, 0, "E", "A"], + alignments=[Alignment.DECIMAL] * 6, + first_col_heading=True, + style=PresetStyle.double_thin_box, + ) + expected = ( + "╔═════════════╦═══════╤═════════════╤════╤════╤═════════╗\n" + "║ 1.1.1 ║ G │ Long Header │ H │ R │ 3.8 ║\n" + "╠═════════════╬═══════╪═════════════╪════╪════╪═════════╣\n" + "║ 100.00001 ║ 2 │ 3.14 │ 33 │ AB │ 1.5 ║\n" + "╟─────────────╫───────┼─────────────┼────┼────┼─────────╢\n" + "║ 10.0001 ║ 22.0 │ 2.718 │ 3 │ CD │ 3.03 ║\n" + "╠═════════════╬═══════╪═════════════╪════╪════╪═════════╣\n" + "║ 10000.01 ║ 123 │ 10.0 │ 0 │ E │ A ║\n" + "╚═════════════╩═══════╧═════════════╧════╧════╧═════════╝" + ) + assert text == expected From 3e8b7ca53b2f8b6ffd2ecc6fb1c201de4a657eeb Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 14:47:49 -0700 Subject: [PATCH 07/30] chore: Make version static in pyproject.toml (#87) --- .github/workflows/lint.yml | 2 +- CONTRIBUTING.md | 12 ++++++++++++ pyproject.toml | 3 ++- setup.py | 14 +------------- table2ascii/__init__.py | 9 ++++++++- table2ascii/annotations.py | 5 +---- 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 861e42d..01ee7f7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -94,7 +94,7 @@ jobs: setup.py - name: Install dependencies - run: python -m pip install -e ".[dev]" -e ".[docs]" + run: python -m pip install -e ".[dev,docs]" - name: Run mypy run: mypy . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91b025b..d99b2e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,18 @@ Install documentation dependencies with: pip install -e ".[docs]" ``` +Install runtime dependencies with: + +```bash +pip install -e . +``` + +All dependencies can be installed at once with: + +```bash +pip install -e ".[dev,docs]" +``` + ### Running the Tests Run the following command to run the [Tox](https://github.com/tox-dev/tox) test script which will verify that the tested functionality is still working. diff --git a/pyproject.toml b/pyproject.toml index 9db538c..7b8a95a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,8 @@ build-backend = "setuptools.build_meta" [project] name = "table2ascii" +version = "1.1.0" authors = [{name = "Jonah Lawrence", email = "jonah@freshidea.com"}] -dynamic = ["version"] description = "Convert 2D Python lists into Unicode/ASCII tables" readme = "README.md" requires-python = ">=3.7" @@ -39,6 +39,7 @@ classifiers = [ ] dependencies = [ "typing-extensions>=3.7.4; python_version<'3.8'", + "importlib-metadata<5,>=1; python_version<'3.8'", "wcwidth<1", ] diff --git a/setup.py b/setup.py index 5fbf35d..d32b777 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,4 @@ # /usr/bin/env python -import re - from setuptools import setup - -def version(): - version = "" - with open("table2ascii/__init__.py") as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE) - if not version: - raise RuntimeError("version is not set") - return version.group(1) - - -setup(name="table2ascii", version=version()) +setup() diff --git a/table2ascii/__init__.py b/table2ascii/__init__.py index 19fa31a..a86dc02 100644 --- a/table2ascii/__init__.py +++ b/table2ascii/__init__.py @@ -1,6 +1,8 @@ """ table2ascii - Library for converting 2D Python lists to fancy ASCII/Unicode tables """ +import sys +from typing import TYPE_CHECKING from .alignment import Alignment from .merge import Merge @@ -8,7 +10,12 @@ from .table_style import TableStyle from .table_to_ascii import table2ascii -__version__ = "1.0.4" +if TYPE_CHECKING or sys.version_info >= (3, 8): + from importlib import metadata +else: + import importlib_metadata as metadata + +__version__ = metadata.version(__name__) __all__ = [ "Alignment", diff --git a/table2ascii/annotations.py b/table2ascii/annotations.py index 241e787..60a4e3d 100644 --- a/table2ascii/annotations.py +++ b/table2ascii/annotations.py @@ -2,14 +2,11 @@ from abc import abstractmethod from typing import TYPE_CHECKING -if sys.version_info >= (3, 8): +if TYPE_CHECKING or sys.version_info >= (3, 8): from typing import Protocol, runtime_checkable else: from typing_extensions import Protocol, runtime_checkable -if TYPE_CHECKING: - from typing import Protocol - @runtime_checkable class SupportsStr(Protocol): From dfbca8c35e66c9b370ffe39991a4af1578229ef5 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 15:48:47 -0700 Subject: [PATCH 08/30] feat: Ability to align all columns with a single Alignment (#91) --- README.md | 26 +++++++++++++------------- docs/source/usage.rst | 2 +- table2ascii/alignment.py | 25 ++++++++++++++++++++++++- table2ascii/options.py | 2 +- table2ascii/table_to_ascii.py | 30 +++++++++++++++++++++--------- tests/test_alignments.py | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index de9e288..ca0e1c6 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ output = table2ascii( body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], style=PresetStyle.plain, cell_padding=0, - alignments=[Alignment.LEFT] * 4, + alignments=Alignment.LEFT, ) print(output) @@ -203,18 +203,18 @@ All parameters are optional. At least one of `header`, `body`, and `footer` must Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.html#table2ascii) for more information. -| Option | Type | Default | Description | -| :-----------------: | :----------------------------: | :-------------------: | :--------------------------------------------------------------------------------------------------: | -| `header` | `Sequence[SupportsStr]` | `None` | First table row seperated by header row separator. Values should support `str()` | -| `body` | `Sequence[Sequence[Sequence]]` | `None` | 2D List of rows for the main section of the table. Values should support `str()` | -| `footer` | `Sequence[Sequence]` | `None` | Last table row seperated by header row separator. Values should support `str()` | -| `column_widths` | `Sequence[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column | -| `alignments` | `Sequence[Alignment]` | `None` (all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | -| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* | -| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column | -| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column | -| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border | -| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | +| Option | Supported Types | Description | +| :-----------------: | :-----------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------: | +| `header` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | First table row seperated by header row separator. Values should support `str()` | +| `body` | `Sequence[Sequence[SupportsStr]]`, `None`
(Default: `None`) | 2D List of rows for the main section of the table. Values should support `str()` | +| `footer` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | Last table row seperated by header row separator. Values should support `str()` | +| `column_widths` | `Sequence[Optional[int]]`, `None`
(Default: `None` / automatic) | List of column widths in characters for each column | +| `alignments` | `Sequence[Alignment]`, `Alignment`, `None`
(Default: `None` / all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | +| `style` | `TableStyle`
(Default: `double_thin_compact`) | Table style to use for the table\* | +| `first_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator after the first column | +| `last_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator before the last column | +| `cell_padding` | `int`
(Default: `1`) | The minimum number of spaces to add between the cell content and the cell border | +| `use_wcwidth` | `bool`
(Default: `True`) | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | [wcwidth]: https://pypi.org/project/wcwidth/ diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 823c23e..d1eb820 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -109,7 +109,7 @@ Use a preset style body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], style=PresetStyle.plain, cell_padding=0, - alignments=[Alignment.LEFT] * 4, + alignments=Alignment.LEFT, ) print(output) diff --git a/table2ascii/alignment.py b/table2ascii/alignment.py index 2ae487f..041d167 100644 --- a/table2ascii/alignment.py +++ b/table2ascii/alignment.py @@ -4,7 +4,7 @@ class Alignment(IntEnum): """Enum for text alignment types within a table cell - Example:: + A list of alignment types can be used to align each column individually:: from table2ascii import Alignment, table2ascii @@ -15,6 +15,8 @@ class Alignment(IntEnum): ["Cheese", "Dairy", "$10.99", "8.2"], ["Apples", "Produce", "$0.99", "10.00"], ], + # Align the first column to the left, the second to the center, + # the third to the right, and the fourth to the decimal point alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL], ) @@ -28,6 +30,27 @@ class Alignment(IntEnum): ╚════════════════════════════════════════╝ \"\"\" + A single alignment type can be used for all columns:: + + table2ascii( + header=["First Name", "Last Name", "Age"], + body=[ + ["John", "Smith", 30], + ["Jane", "Doe", 28], + ], + # Align all columns to the left + alignments=Alignment.LEFT, + ) + + \"\"\" + ╔══════════════════════════════╗ + ║ First Name Last Name Age ║ + ╟──────────────────────────────╢ + ║ John Smith 30 ║ + ║ Jane Doe 28 ║ + ╚══════════════════════════════╝ + \"\"\" + .. note:: If the :attr:`DECIMAL` alignment type is used, any cell values that are diff --git a/table2ascii/options.py b/table2ascii/options.py index 36f6ee0..e88bd44 100644 --- a/table2ascii/options.py +++ b/table2ascii/options.py @@ -19,7 +19,7 @@ class Options: first_col_heading: bool last_col_heading: bool column_widths: Sequence[int | None] | None - alignments: Sequence[Alignment] | None + alignments: Sequence[Alignment] | Alignment | None cell_padding: int style: TableStyle use_wcwidth: bool diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 0510bd1..46603d7 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -67,11 +67,16 @@ def __init__( if not header and not body and not footer: raise NoHeaderBodyOrFooterError() - self.__alignments = options.alignments or [Alignment.CENTER] * self.__columns + alignments = options.alignments if options.alignments is not None else Alignment.CENTER + + # if alignments is a single Alignment, convert it to a list of that Alignment + self.__alignments: list[Alignment] = ( + [alignments] * self.__columns if isinstance(alignments, Alignment) else list(alignments) + ) # check if alignments specified have a different number of columns - if options.alignments and len(options.alignments) != self.__columns: - raise AlignmentCountMismatchError(options.alignments, self.__columns) + if len(self.__alignments) != self.__columns: + raise AlignmentCountMismatchError(self.__alignments, self.__columns) # keep track of the number widths and positions of the decimal points for decimal alignment decimal_widths, decimal_positions = self.__calculate_decimal_widths_and_positions() @@ -634,16 +639,13 @@ def table2ascii( first_col_heading: bool = False, last_col_heading: bool = False, column_widths: Sequence[int | None] | None = None, - alignments: Sequence[Alignment] | None = None, + alignments: Sequence[Alignment] | Alignment | None = None, cell_padding: int = 1, style: TableStyle = PresetStyle.double_thin_compact, use_wcwidth: bool = True, ) -> str: """Convert a 2D Python table to ASCII text - .. versionchanged:: 1.0.0 - Added the ``use_wcwidth`` parameter defaulting to :py:obj:`True`. - Args: header: List of column values in the table's header row. All values should be :class:`str` or support :class:`str` conversion. If not specified, the table will not have a header row. @@ -660,8 +662,10 @@ def table2ascii( is passed instead of a :class:`~collections.abc.Sequence`, all columns will be automatically sized. Defaults to :py:obj:`None`. alignments: List of alignments for each column - (ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]``). If not specified or set to - :py:obj:`None`, all columns will be center-aligned. Defaults to :py:obj:`None`. + (ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]``) + or a single alignment to apply to all columns (ex. ``Alignment.LEFT``). + If not specified or set to :py:obj:`None`, all columns will be center-aligned. + Defaults to :py:obj:`None`. cell_padding: The minimum number of spaces to add between the cell content and the column separator. If set to ``0``, the cell content will be flush against the column separator. Defaults to ``1``. @@ -673,6 +677,14 @@ def table2ascii( zero-width space, etc.), whereas :func:`len` determines the width solely based on the number of characters in the string. Defaults to :py:obj:`True`. + .. versionchanged:: 1.1.0 + + ``alignments`` can now also be specified as a single :class:`Alignment` value to apply to all columns. + + .. versionchanged:: 1.0.0 + + Added the ``use_wcwidth`` parameter defaulting to :py:obj:`True`. + Returns: The generated ASCII table """ diff --git a/tests/test_alignments.py b/tests/test_alignments.py index 107c32b..ff684e1 100644 --- a/tests/test_alignments.py +++ b/tests/test_alignments.py @@ -120,3 +120,37 @@ def test_decimal_alignment(): "╚═════════════╩═══════╧═════════════╧════╧════╧═════════╝" ) assert text == expected + + +def test_single_decimal_alignment(): + text = t2a( + header=["1.1.1", "G", "Long Header"], + body=[[100.00001, 2, 3.14], [10.0001, 22.0, 2.718]], + alignments=Alignment.DECIMAL, + ) + expected = ( + "╔════════════════════════════════╗\n" + "║ 1.1.1 G Long Header ║\n" + "╟────────────────────────────────╢\n" + "║ 100.00001 2 3.14 ║\n" + "║ 10.0001 22.0 2.718 ║\n" + "╚════════════════════════════════╝" + ) + assert text == expected + + +def test_single_left_alignment(): + text = t2a( + header=["1.1.1", "G", "Long Header"], + body=[[100.00001, 2, 3.14], [10.0001, 22.0, 2.718]], + alignments=Alignment.LEFT, + ) + expected = ( + "╔════════════════════════════════╗\n" + "║ 1.1.1 G Long Header ║\n" + "╟────────────────────────────────╢\n" + "║ 100.00001 2 3.14 ║\n" + "║ 10.0001 22.0 2.718 ║\n" + "╚════════════════════════════════╝" + ) + assert text == expected From 1a9d24d1c6551b0ea3760ed629dc7597edbd384f Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 17:21:09 -0700 Subject: [PATCH 09/30] feat: Support for aligning numbers separately from strings (#92) --- docs/source/_static/css/custom.css | 5 ++ table2ascii/alignment.py | 20 ++++--- table2ascii/options.py | 5 ++ table2ascii/table_to_ascii.py | 88 +++++++++++++++++++++--------- tests/test_alignments.py | 38 ++++++++++++- 5 files changed, 120 insertions(+), 36 deletions(-) diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index c0c2c34..a3475f8 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -18,4 +18,9 @@ /* Change code block font */ :root { --pst-font-family-monospace: "Hack", "Source Code Pro", "SFMono-Regular", "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", "Courier", monospace; +} + +/* Adjust margin on version directives within parameter lists */ +div.versionchanged p, div.versionadded p { + margin-bottom: 10px; } \ No newline at end of file diff --git a/table2ascii/alignment.py b/table2ascii/alignment.py index 041d167..f11fa39 100644 --- a/table2ascii/alignment.py +++ b/table2ascii/alignment.py @@ -30,7 +30,7 @@ class Alignment(IntEnum): ╚════════════════════════════════════════╝ \"\"\" - A single alignment type can be used for all columns:: + A single alignment type can be used to align all columns:: table2ascii( header=["First Name", "Last Name", "Age"], @@ -38,25 +38,27 @@ class Alignment(IntEnum): ["John", "Smith", 30], ["Jane", "Doe", 28], ], - # Align all columns to the left - alignments=Alignment.LEFT, + alignments=Alignment.LEFT, # Align all columns to the left + number_alignments=Alignment.RIGHT, # Align all numeric values to the right ) \"\"\" ╔══════════════════════════════╗ ║ First Name Last Name Age ║ ╟──────────────────────────────╢ - ║ John Smith 30 ║ - ║ Jane Doe 28 ║ + ║ John Smith 30 ║ + ║ Jane Doe 28 ║ ╚══════════════════════════════╝ \"\"\" .. note:: - If the :attr:`DECIMAL` alignment type is used, any cell values that are - not valid decimal numbers will be aligned to the center. Decimal numbers - include integers, floats, and strings containing only - :meth:`decimal ` characters and at most one decimal point. + If :attr:`DECIMAL` is used in the ``number_alignments`` argument to :func:`table2ascii`, + all non-numeric values will be aligned according to the ``alignments`` argument. + If the :attr:`DECIMAL` alignment type is used in the ``alignments`` argument, + all non-numeric values will be aligned to the center. + Numeric values include integers, floats, and strings containing only :meth:`decimal ` + characters and at most one decimal point. .. versionchanged:: 1.1.0 diff --git a/table2ascii/options.py b/table2ascii/options.py index e88bd44..28779c2 100644 --- a/table2ascii/options.py +++ b/table2ascii/options.py @@ -11,6 +11,10 @@ class Options: """Class for storing options that the user sets + .. versionchanged:: 1.1.0 + + Added ``number_alignments`` option + .. versionchanged:: 1.0.0 Added ``use_wcwidth`` option @@ -20,6 +24,7 @@ class Options: last_col_heading: bool column_widths: Sequence[int | None] | None alignments: Sequence[Alignment] | Alignment | None + number_alignments: Sequence[Alignment] | Alignment | None cell_padding: int style: TableStyle use_wcwidth: bool diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 46603d7..c4359a8 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -67,16 +67,12 @@ def __init__( if not header and not body and not footer: raise NoHeaderBodyOrFooterError() - alignments = options.alignments if options.alignments is not None else Alignment.CENTER - - # if alignments is a single Alignment, convert it to a list of that Alignment - self.__alignments: list[Alignment] = ( - [alignments] * self.__columns if isinstance(alignments, Alignment) else list(alignments) + self.__alignments = self.__determine_alignments( + options.alignments, default=Alignment.CENTER + ) + self.__number_alignments = self.__determine_alignments( + options.number_alignments, default=self.__alignments ) - - # check if alignments specified have a different number of columns - if len(self.__alignments) != self.__columns: - raise AlignmentCountMismatchError(self.__alignments, self.__columns) # keep track of the number widths and positions of the decimal points for decimal alignment decimal_widths, decimal_positions = self.__calculate_decimal_widths_and_positions() @@ -107,6 +103,33 @@ def __count_columns(self) -> int: return len(self.__body[0]) return 0 + def __determine_alignments( + self, + user_alignments: Sequence[Alignment] | Alignment | None, + *, + default: Sequence[Alignment] | Alignment, + ) -> list[Alignment]: + """Determine the alignments for each column based on the user provided alignments option. + + Args: + user_alignments: The alignments specified by the user + default: The default alignments to use if user_alignments is None + + Returns: + The alignments for each column in the table + """ + alignments = user_alignments if user_alignments is not None else default + + # if alignments is a single Alignment, convert it to a list of that Alignment + if isinstance(alignments, Alignment): + alignments = [alignments] * self.__columns + + # check if alignments specified have a different number of columns + if len(alignments) != self.__columns: + raise AlignmentCountMismatchError(alignments, self.__columns) + + return list(alignments) + def __auto_column_widths(self) -> list[int]: """Get the minimum number of characters needed for the values in each column in the table with 1 space of padding on each side. @@ -150,7 +173,8 @@ def __calculate_decimal_widths_and_positions(self) -> tuple[list[int], list[int] decimal_widths: list[int] = [0] * self.__columns decimal_positions: list[int] = [0] * self.__columns for i in range(self.__columns): - if self.__alignments[i] != Alignment.DECIMAL: + # skip if the column is not decimal aligned + if self.__number_alignments[i] != Alignment.DECIMAL: continue # list all values in the i-th column of header, body, and footer values = [str(self.__header[i])] if self.__header else [] @@ -227,15 +251,20 @@ def __pad(self, cell_value: SupportsStr, width: int, col_index: int) -> str: """ alignment = self.__alignments[col_index] text = str(cell_value) - # if using decimal alignment, pad such that the decimal point - # is aligned to the column's decimal position - if alignment == Alignment.DECIMAL and self.__is_number(text): - decimal_position = self.__decimal_positions[col_index] - decimal_max_width = self.__decimal_widths[col_index] - text_before_decimal = self.__split_decimal(text)[0] - before = " " * (decimal_position - self.__str_width(text_before_decimal)) - after = " " * (decimal_max_width - self.__str_width(text) - len(before)) - text = f"{before}{text}{after}" + # set alignment for numeric values + if self.__is_number(text): + # if the number alignment is decimal, pad such that the decimal point + # is aligned to the column's decimal position and use the default alignment + if self.__number_alignments[col_index] == Alignment.DECIMAL: + decimal_position = self.__decimal_positions[col_index] + decimal_max_width = self.__decimal_widths[col_index] + text_before_decimal = self.__split_decimal(text)[0] + before = " " * (decimal_position - self.__str_width(text_before_decimal)) + after = " " * (decimal_max_width - self.__str_width(text) - len(before)) + text = f"{before}{text}{after}" + # otherwise use the number alignment as the alignment for the cell + else: + alignment = self.__number_alignments[col_index] # add minimum cell padding around the text padding = " " * self.__cell_padding padded_text = f"{padding}{text}{padding}" @@ -640,6 +669,7 @@ def table2ascii( last_col_heading: bool = False, column_widths: Sequence[int | None] | None = None, alignments: Sequence[Alignment] | Alignment | None = None, + number_alignments: Sequence[Alignment] | Alignment | None = None, cell_padding: int = 1, style: TableStyle = PresetStyle.double_thin_compact, use_wcwidth: bool = True, @@ -666,6 +696,17 @@ def table2ascii( or a single alignment to apply to all columns (ex. ``Alignment.LEFT``). If not specified or set to :py:obj:`None`, all columns will be center-aligned. Defaults to :py:obj:`None`. + + .. versionchanged:: 1.1.0 + ``alignments`` can now also be specified as a single :class:`Alignment` value to apply to all columns. + number_alignments: List of alignments for numeric values in each column or a single alignment + to apply to all columns. This argument can be used to override the alignment of numbers and + is ignored for non-numeric values. Numeric values include integers, floats, and strings containing only + :meth:`decimal ` characters and at most one decimal point. + If not specified or set to :py:obj:`None`, numbers will be aligned based on the ``alignments`` argument. + Defaults to :py:obj:`None`. + + .. versionadded:: 1.1.0 cell_padding: The minimum number of spaces to add between the cell content and the column separator. If set to ``0``, the cell content will be flush against the column separator. Defaults to ``1``. @@ -677,13 +718,7 @@ def table2ascii( zero-width space, etc.), whereas :func:`len` determines the width solely based on the number of characters in the string. Defaults to :py:obj:`True`. - .. versionchanged:: 1.1.0 - - ``alignments`` can now also be specified as a single :class:`Alignment` value to apply to all columns. - - .. versionchanged:: 1.0.0 - - Added the ``use_wcwidth`` parameter defaulting to :py:obj:`True`. + .. versionadded:: 1.0.0 Returns: The generated ASCII table @@ -697,6 +732,7 @@ def table2ascii( last_col_heading=last_col_heading, column_widths=column_widths, alignments=alignments, + number_alignments=number_alignments, cell_padding=cell_padding, style=style, use_wcwidth=use_wcwidth, diff --git a/tests/test_alignments.py b/tests/test_alignments.py index ff684e1..67aa5d3 100644 --- a/tests/test_alignments.py +++ b/tests/test_alignments.py @@ -25,7 +25,7 @@ def test_first_left_four_right(): assert text == expected -def test_wrong_number_alignments(): +def test_wrong_number_of_alignments(): with pytest.raises(AlignmentCountMismatchError): t2a( header=["#", "G", "H", "R", "S"], @@ -154,3 +154,39 @@ def test_single_left_alignment(): "╚════════════════════════════════╝" ) assert text == expected + + +def test_number_alignments(): + text = t2a( + header=["1.1.1", "G", "Long Header", "Another Long Header"], + body=[[100.00001, 2, 3.14, 6.28], [10.0001, 22.0, 2.718, 1.618]], + alignments=[Alignment.LEFT, Alignment.RIGHT, Alignment.CENTER, Alignment.RIGHT], + number_alignments=[Alignment.DECIMAL, Alignment.LEFT, Alignment.RIGHT, Alignment.DECIMAL], + ) + expected = ( + "╔══════════════════════════════════════════════════════╗\n" + "║ 1.1.1 G Long Header Another Long Header ║\n" + "╟──────────────────────────────────────────────────────╢\n" + "║ 100.00001 2 3.14 6.28 ║\n" + "║ 10.0001 22.0 2.718 1.618 ║\n" + "╚══════════════════════════════════════════════════════╝" + ) + assert text == expected + + +def test_single_number_alignments(): + text = t2a( + header=["1.1.1", "G", "Long Header", "S"], + body=[[100.00001, 2, 3.14, 6.28], [10.0001, 22.0, 2.718, 1.618]], + alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.CENTER, Alignment.RIGHT], + number_alignments=Alignment.RIGHT, + ) + expected = ( + "╔════════════════════════════════════════╗\n" + "║ 1.1.1 G Long Header S ║\n" + "╟────────────────────────────────────────╢\n" + "║ 100.00001 2 3.14 6.28 ║\n" + "║ 10.0001 22.0 2.718 1.618 ║\n" + "╚════════════════════════════════════════╝" + ) + assert text == expected From c07698951e2c81050af5aa4eb44d05b5a0500ed8 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 17:32:12 -0700 Subject: [PATCH 10/30] docs(readme): Add numeric_aligments to readme (#93) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ca0e1c6..b9ff0a7 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.ht | `footer` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | Last table row seperated by header row separator. Values should support `str()` | | `column_widths` | `Sequence[Optional[int]]`, `None`
(Default: `None` / automatic) | List of column widths in characters for each column | | `alignments` | `Sequence[Alignment]`, `Alignment`, `None`
(Default: `None` / all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | +| `number_alignments` | `Sequence[Alignment]`, `Alignment`, `None`
(Default: `None`) | Column alignments for numeric values. `alignments` will be used if not specified. | | `style` | `TableStyle`
(Default: `double_thin_compact`) | Table style to use for the table\* | | `first_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator after the first column | | `last_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator before the last column | From 68982e976f74345eff52a9b353241860bd87a983 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Thu, 29 Dec 2022 00:29:09 -0700 Subject: [PATCH 11/30] docs: Fix unlinked references and updated docstrings (#94) --- docs/source/api.rst | 33 +++++++++++++++++----------- table2ascii/__init__.py | 30 +++++++++++++++++++++++++ table2ascii/annotations.py | 3 ++- table2ascii/exceptions.py | 41 ++++++++++++++++++++--------------- table2ascii/table_style.py | 2 +- table2ascii/table_to_ascii.py | 16 +++++++++----- 6 files changed, 86 insertions(+), 39 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 170e480..6cb1a29 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -39,31 +39,38 @@ TableStyle Exceptions ~~~~~~~~~~ -.. autoexception:: table2ascii.exceptions.Table2AsciiError +.. autoexception:: Table2AsciiError -.. autoexception:: table2ascii.exceptions.TableOptionError +.. autoexception:: TableOptionError -.. autoexception:: table2ascii.exceptions.ColumnCountMismatchError +.. autoexception:: ColumnCountMismatchError -.. autoexception:: table2ascii.exceptions.FooterColumnCountMismatchError +.. autoexception:: FooterColumnCountMismatchError -.. autoexception:: table2ascii.exceptions.BodyColumnCountMismatchError +.. autoexception:: BodyColumnCountMismatchError -.. autoexception:: table2ascii.exceptions.AlignmentCountMismatchError +.. autoexception:: AlignmentCountMismatchError -.. autoexception:: table2ascii.exceptions.InvalidCellPaddingError +.. autoexception:: InvalidCellPaddingError -.. autoexception:: table2ascii.exceptions.ColumnWidthsCountMismatchError +.. autoexception:: ColumnWidthsCountMismatchError -.. autoexception:: table2ascii.exceptions.ColumnWidthTooSmallError +.. autoexception:: ColumnWidthTooSmallError -.. autoexception:: table2ascii.exceptions.InvalidColumnWidthError +.. autoexception:: InvalidColumnWidthError -.. autoexception:: table2ascii.exceptions.InvalidAlignmentError +.. autoexception:: InvalidAlignmentError -.. autoexception:: table2ascii.exceptions.TableStyleTooLongError +.. autoexception:: TableStyleTooLongError Warnings ~~~~~~~~ -.. autoclass:: table2ascii.exceptions.TableStyleTooShortWarning +.. autoclass:: TableStyleTooShortWarning + +Annotations +~~~~~~~~~~~ + +.. autoclass:: SupportsStr + + .. automethod:: SupportsStr.__str__ diff --git a/table2ascii/__init__.py b/table2ascii/__init__.py index a86dc02..bc53a37 100644 --- a/table2ascii/__init__.py +++ b/table2ascii/__init__.py @@ -5,6 +5,22 @@ from typing import TYPE_CHECKING from .alignment import Alignment +from .annotations import SupportsStr +from .exceptions import ( + AlignmentCountMismatchError, + BodyColumnCountMismatchError, + ColumnCountMismatchError, + ColumnWidthsCountMismatchError, + ColumnWidthTooSmallError, + FooterColumnCountMismatchError, + InvalidAlignmentError, + InvalidCellPaddingError, + InvalidColumnWidthError, + Table2AsciiError, + TableOptionError, + TableStyleTooLongError, + TableStyleTooShortWarning, +) from .merge import Merge from .preset_style import PresetStyle from .table_style import TableStyle @@ -23,4 +39,18 @@ "PresetStyle", "TableStyle", "table2ascii", + "AlignmentCountMismatchError", + "BodyColumnCountMismatchError", + "ColumnCountMismatchError", + "ColumnWidthsCountMismatchError", + "ColumnWidthTooSmallError", + "FooterColumnCountMismatchError", + "InvalidAlignmentError", + "InvalidCellPaddingError", + "InvalidColumnWidthError", + "Table2AsciiError", + "TableOptionError", + "TableStyleTooLongError", + "TableStyleTooShortWarning", + "SupportsStr", ] diff --git a/table2ascii/annotations.py b/table2ascii/annotations.py index 60a4e3d..4434ced 100644 --- a/table2ascii/annotations.py +++ b/table2ascii/annotations.py @@ -10,8 +10,9 @@ @runtime_checkable class SupportsStr(Protocol): - """An ABC with one abstract method __str__.""" + """An abstract base class (ABC) with one abstract method :meth:`__str__`""" @abstractmethod def __str__(self) -> str: + """Return a string representation of the object""" pass diff --git a/table2ascii/exceptions.py b/table2ascii/exceptions.py index 0c57134..b4b6314 100644 --- a/table2ascii/exceptions.py +++ b/table2ascii/exceptions.py @@ -40,8 +40,9 @@ class FooterColumnCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - footer (Sequence[SupportsStr]): The footer that caused the error - expected_columns (int): The number of columns that were expected + footer (:class:`Sequence `\ [:class:`SupportsStr`]): + The footer that caused the error + expected_columns (:class:`int`): The number of columns that were expected """ def __init__(self, footer: Sequence[SupportsStr], expected_columns: int): @@ -63,9 +64,11 @@ class BodyColumnCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - body (Sequence[Sequence[SupportsStr]]): The body that caused the error - expected_columns (int): The number of columns that were expected - first_invalid_row (Sequence[SupportsStr]): The first row with an invalid column count + body (:class:`Sequence `\ [\ :class:`Sequence `\ [:class:`SupportsStr`]]): + The body that caused the error + expected_columns (:class:`int`): The number of columns that were expected + first_invalid_row (:class:`Sequence `\ [:class:`SupportsStr`]): + The first row with an invalid column count """ def __init__(self, body: Sequence[Sequence[SupportsStr]], expected_columns: int): @@ -90,8 +93,9 @@ class AlignmentCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - alignments (Sequence[Alignment]): The alignments that caused the error - expected_columns (int): The number of columns that were expected + alignments (:class:`Sequence `\ [:class:`Alignment`]): + The alignments that caused the error + expected_columns (:class:`int`): The number of columns that were expected """ def __init__(self, alignments: Sequence[Alignment], expected_columns: int): @@ -113,8 +117,9 @@ class ColumnWidthsCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - column_widths (Sequence[Optional[int]]): The column widths that caused the error - expected_columns (int): The number of columns that were expected + column_widths (:class:`Sequence `\ [:data:`Optional `\ [:class:`int`]]): + The column widths that caused the error + expected_columns (:class:`int`): The number of columns that were expected """ def __init__(self, column_widths: Sequence[int | None], expected_columns: int): @@ -148,7 +153,7 @@ class InvalidCellPaddingError(TableOptionError): This class is a subclass of :class:`TableOptionError`. Attributes: - padding (int): The padding that caused the error + padding (:class:`int`): The padding that caused the error """ def __init__(self, padding: int): @@ -169,9 +174,9 @@ class ColumnWidthTooSmallError(TableOptionError): This class is a subclass of :class:`TableOptionError`. Attributes: - column_index (int): The index of the column that caused the error - column_width (int): The column width that caused the error - min_width (int): The minimum width that is allowed + column_index (:class:`int`): The index of the column that caused the error + column_width (:class:`int`): The column width that caused the error + min_width (:class:`int`): The minimum width that is allowed """ def __init__(self, column_index: int, column_width: int, min_width: int | None = None): @@ -208,7 +213,7 @@ class InvalidAlignmentError(TableOptionError): This class is a subclass of :class:`TableOptionError`. Attributes: - alignment (Any): The alignment value that caused the error + alignment (:data:`Any `): The alignment value that caused the error """ def __init__(self, alignment: Any): @@ -230,8 +235,8 @@ class TableStyleTooLongError(Table2AsciiError, ValueError): This class is a subclass of :class:`Table2AsciiError` and :class:`ValueError`. Attributes: - string (str): The string that caused the error - max_characters (int): The maximum number of characters that are allowed + string (:class:`str`): The string that caused the error + max_characters (:class:`int`): The maximum number of characters that are allowed """ def __init__(self, string: str, max_characters: int): @@ -256,8 +261,8 @@ class TableStyleTooShortWarning(UserWarning): It can be silenced using :func:`warnings.filterwarnings`. Attributes: - string (str): The string that caused the warning - max_characters (int): The number of characters that :class:`TableStyle` accepts + string (:class:`str`): The string that caused the warning + max_characters (:class:`int`): The number of characters that :class:`TableStyle` accepts """ def __init__(self, string: str, max_characters: int): diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 8ecc12e..23db0ac 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -145,7 +145,7 @@ def set(self, **kwargs: str) -> "TableStyle": Example:: - TableStyle().set(top_left_corner="╔", top_and_bottom_edge="═") + TableStyle.from_string("~" * 30).set(left_and_right_edge="", col_sep="") """ for key, value in kwargs.items(): setattr(self, key, value) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index c4359a8..dd092e2 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -677,23 +677,27 @@ def table2ascii( """Convert a 2D Python table to ASCII text Args: - header: List of column values in the table's header row. All values should be :class:`str` + header (:data:`Optional `\ [:class:`Sequence `\ [:class:`SupportsStr`]]): + List of column values in the table's header row. All values should be :class:`str` or support :class:`str` conversion. If not specified, the table will not have a header row. - body: 2-dimensional list of values in the table's body. All values should be :class:`str` + body (:data:`Optional `\ [:class:`Sequence `\ [:class:`Sequence `\ [:class:`SupportsStr`]]]): + 2-dimensional list of values in the table's body. All values should be :class:`str` or support :class:`str` conversion. If not specified, the table will not have a body. - footer: List of column values in the table's footer row. All values should be :class:`str` + footer (:data:`Optional `\ [:class:`Sequence `\ [:class:`SupportsStr`]]): + List of column values in the table's footer row. All values should be :class:`str` or support :class:`str` conversion. If not specified, the table will not have a footer row. first_col_heading: Whether to add a header column separator after the first column. Defaults to :py:obj:`False`. last_col_heading: Whether to add a header column separator before the last column. Defaults to :py:obj:`False`. - column_widths: List of widths in characters for each column. Any value of :py:obj:`None` + column_widths (:data:`Optional `\ [:class:`Sequence `\ [:data:`Optional `\ [:class:`int`]]]): + List of widths in characters for each column. Any value of :py:obj:`None` indicates that the column width should be determined automatically. If :py:obj:`None` is passed instead of a :class:`~collections.abc.Sequence`, all columns will be automatically sized. Defaults to :py:obj:`None`. alignments: List of alignments for each column - (ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]``) - or a single alignment to apply to all columns (ex. ``Alignment.LEFT``). + (ex. [:attr:`Alignment.LEFT`, :attr:`Alignment.CENTER`, :attr:`Alignment.RIGHT`, :attr:`Alignment.DECIMAL`]) + or a single alignment to apply to all columns (ex. :attr:`Alignment.LEFT`). If not specified or set to :py:obj:`None`, all columns will be center-aligned. Defaults to :py:obj:`None`. From 5a0c5070b36d993a3fae2b45ce95efb755ea592c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 12:54:47 -0700 Subject: [PATCH 12/30] chore(deps-dev): update pre-commit requirement from <3,>=2.0.0 to >=2.0.0,<4 (#96) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7b8a95a..092d0ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ docs = [ ] dev = [ "mypy>=0.982,<1", - "pre-commit>=2.0.0,<3", + "pre-commit>=2.0.0,<4", "pyright>=1.0.0,<2", "pytest>=6.0.0,<8", "slotscheck>=0.1.0,<1", From d462f0ff72866220a146faf316ec92b8f52f3d54 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 20:49:54 -0700 Subject: [PATCH 13/30] [pre-commit.ci] pre-commit autoupdate (#97) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jonah Lawrence --- .github/workflows/lint.yml | 4 ++-- .pre-commit-config.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 01ee7f7..97c81e4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,10 +16,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Set up python 3.7 + - name: Set up python 3.8 uses: actions/setup-python@v4 with: - python-version: 3.7 + python-version: 3.8 cache: pip cache-dependency-path: | setup.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 293d837..edf8e31 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: name: Running black in all files. - repo: https://github.com/pycqa/isort - rev: 5.11.4 + rev: 5.12.0 hooks: - id: isort args: ["--profile", "black", "--extend-skip", "table2ascii"] From e21ecde0d1d59bd0263b39f171efe1faa4d8a166 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:29:01 -0700 Subject: [PATCH 14/30] chore(deps-dev): update mypy requirement from <1,>=0.982 to >=0.982,<2 (#99) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 092d0ba..62c014f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ docs = [ "sphinx-book-theme==0.3.3", ] dev = [ - "mypy>=0.982,<1", + "mypy>=0.982,<2", "pre-commit>=2.0.0,<4", "pyright>=1.0.0,<2", "pytest>=6.0.0,<8", From 14eb6dc43a923085de5156890278ba6df076b778 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:29:51 -0700 Subject: [PATCH 15/30] [pre-commit.ci] pre-commit autoupdate (#98) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index edf8e31..3828cce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: repos: - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black name: Running black in all files. From 9adec7a24e6c0ea824c4e0b96d92ca75c33e4ae7 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Tue, 28 Feb 2023 14:05:13 +0200 Subject: [PATCH 16/30] fix: Include py.typed in package data for mypy (#100) --- pyproject.toml | 2 +- setup.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 62c014f..fd814c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "table2ascii" -version = "1.1.0" +version = "1.1.1" authors = [{name = "Jonah Lawrence", email = "jonah@freshidea.com"}] description = "Convert 2D Python lists into Unicode/ASCII tables" readme = "README.md" diff --git a/setup.py b/setup.py index d32b777..b21e3f6 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,7 @@ # /usr/bin/env python from setuptools import setup -setup() +setup( + packages=["table2ascii"], + package_data={"table2ascii": ["py.typed"]}, +) From 11f6146e24fccbc2c836390624733e29078408c5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 09:33:41 -0600 Subject: [PATCH 17/30] [pre-commit.ci] pre-commit autoupdate (#103) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3828cce..0a587b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black name: Running black in all files. From 92b90b4af2a95162dc15d10dd2dd7310bc643900 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 23:53:07 -0600 Subject: [PATCH 18/30] [pre-commit.ci] pre-commit autoupdate (#107) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a587b2..9cea686 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black name: Running black in all files. From c4ef7192f2a2e3f604c25ba1897b0551267c92b9 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Tue, 29 Aug 2023 06:47:50 -0600 Subject: [PATCH 19/30] fix: Add name and version to setup.py for older version of pip (#109) --- pyproject.toml | 2 +- setup.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fd814c1..e7343bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "table2ascii" -version = "1.1.1" +version = "1.1.2" authors = [{name = "Jonah Lawrence", email = "jonah@freshidea.com"}] description = "Convert 2D Python lists into Unicode/ASCII tables" readme = "README.md" diff --git a/setup.py b/setup.py index b21e3f6..9467adb 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,51 @@ # /usr/bin/env python +import re + from setuptools import setup -setup( - packages=["table2ascii"], - package_data={"table2ascii": ["py.typed"]}, -) + +def get_name(): + name = "" + with open("pyproject.toml") as f: + name = re.search(r'^name = ["\']([^"\']*)["\']', f.read(), re.M) + if not name: + raise RuntimeError("name is not set") + return name.group(1) + + +def get_version(): + version = "" + with open("pyproject.toml") as f: + version = re.search(r'^version = ["\']([^"\']*)["\']', f.read(), re.M) + if not version: + raise RuntimeError("version is not set") + return version.group(1) + + +def get_dependencies(): + with open("pyproject.toml") as f: + dependency_match = re.search(r"^dependencies = \[([\s\S]*?)\]", f.read(), re.M) + if not dependency_match or not dependency_match.group(1): + return [] + return [ + dependency.strip().strip(",").strip('"') + for dependency in dependency_match.group(1).split("\n") + if dependency + ] + + +try: + # check if pyproject.toml can be used to install dependencies and set the version + setup( + packages=[get_name()], + package_data={get_name(): ["py.typed"]}, + ) +except Exception: + # fallback for old versions of pip/setuptools + setup( + name=get_name(), + packages=[get_name()], + package_data={get_name(): ["py.typed"]}, + version=get_version(), + install_requires=get_dependencies(), + ) From 278785209f64ee585687d3553ffe9ba445711479 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 13:54:37 -0600 Subject: [PATCH 20/30] [pre-commit.ci] pre-commit autoupdate (#110) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9cea686..b669533 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black name: Running black in all files. From c6735c13799cbab3edabd7915f8313221476b681 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:36:05 -0600 Subject: [PATCH 21/30] [pre-commit.ci] pre-commit autoupdate (#111) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b669533..66571a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: name: Running isort in all files. - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-ast name: Check if python files are valid syntax for the ast parser From 32a7b6f2cf985bfcb7c774fa3c12f479b4a08016 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Fri, 20 Oct 2023 10:37:09 -0600 Subject: [PATCH 22/30] fix: Prevent text wrapping when already within width (#113) --- table2ascii/table_to_ascii.py | 19 ++++++++++++------- tests/test_convert.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index dd092e2..65dfb8d 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -130,6 +130,11 @@ def __determine_alignments( return list(alignments) + def __widest_line(self, value: SupportsStr) -> int: + """Returns the width of the longest line in a multi-line string""" + text = str(value) + return max(self.__str_width(line) for line in text.splitlines()) if len(text) else 0 + def __auto_column_widths(self) -> list[int]: """Get the minimum number of characters needed for the values in each column in the table with 1 space of padding on each side. @@ -138,18 +143,13 @@ def __auto_column_widths(self) -> list[int]: The minimum number of characters needed for each column """ - def widest_line(value: SupportsStr) -> int: - """Returns the width of the longest line in a multi-line string""" - text = str(value) - return max(self.__str_width(line) for line in text.splitlines()) if len(text) else 0 - def get_column_width(row: Sequence[SupportsStr], column: int) -> int: """Get the width of a cell in a column""" value = row[column] next_value = row[column + 1] if column < self.__columns - 1 else None if value is Merge.LEFT or next_value is Merge.LEFT: return 0 - return widest_line(value) + return self.__widest_line(value) column_widths = [] # get the width necessary for each column @@ -306,7 +306,12 @@ def __wrap_long_lines_in_merged_cells( if row[other_col_index] is not Merge.LEFT: break merged_width += self.__column_widths[other_col_index] + len(column_separator) - cell = textwrap.fill(str(cell), merged_width - self.__cell_padding * 2) + cell = str(cell) + # if the text is too wide, wrap it + inner_cell_width = merged_width - self.__cell_padding * 2 + if self.__widest_line(cell) > inner_cell_width: + cell = textwrap.fill(cell, inner_cell_width) + # add the wrapped cell to the row wrapped_row.append(cell) return wrapped_row diff --git a/tests/test_convert.py b/tests/test_convert.py index 3030cd6..d510a1a 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -305,3 +305,22 @@ def test_east_asian_wide_characters_and_zero_width_no_wcwidth(): "╚════╩═══════════════╝" ) assert text == expected + + +def test_multiline_cells_with_wrappable_lines(): + text = t2a( + header=["Test"], + body=[["Line One...\nSecond Line...\nLineNumThree\nLineFour\nFive FinalLine"]], + ) + expected = ( + "╔════════════════╗\n" + "║ Test ║\n" + "╟────────────────╢\n" + "║ Line One... ║\n" + "║ Second Line... ║\n" + "║ LineNumThree ║\n" + "║ LineFour ║\n" + "║ Five FinalLine ║\n" + "╚════════════════╝" + ) + assert text == expected From 3fe8b5c732eb65047c9547ee2ce4361701480091 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Fri, 20 Oct 2023 10:42:54 -0600 Subject: [PATCH 23/30] chore: bump to version 1.1.3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e7343bd..267f35a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "table2ascii" -version = "1.1.2" +version = "1.1.3" authors = [{name = "Jonah Lawrence", email = "jonah@freshidea.com"}] description = "Convert 2D Python lists into Unicode/ASCII tables" readme = "README.md" From ed3f1425282b1e431a3381f5c6e87b7503276d30 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 23:43:38 -0600 Subject: [PATCH 24/30] [pre-commit.ci] pre-commit autoupdate (#114) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 66571a9..0c735a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black name: Running black in all files. From d2107a1065eee960ea1a9c5f386c3bf5def2a367 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:40:15 -0800 Subject: [PATCH 25/30] [pre-commit.ci] pre-commit autoupdate (#115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c735a2..9421020 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.10.1 + rev: 23.11.0 hooks: - id: black name: Running black in all files. From a0d5d1fe0d56f3f4e6eaac284d434bc5b79b519d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:27:34 +0200 Subject: [PATCH 26/30] [pre-commit.ci] pre-commit autoupdate (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.11.0 → 23.12.1](https://github.com/psf/black/compare/23.11.0...23.12.1) - [github.com/pycqa/isort: 5.12.0 → 5.13.2](https://github.com/pycqa/isort/compare/5.12.0...5.13.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9421020..0a4da44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,13 +6,13 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.11.0 + rev: 23.12.1 hooks: - id: black name: Running black in all files. - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort args: ["--profile", "black", "--extend-skip", "table2ascii"] From 66ecd7f3423179754890f3c77ff9bc8c272243d3 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 7 Aug 2024 15:13:26 +0300 Subject: [PATCH 27/30] docs: Lock <5 dependent sphinx extension versions (#119) --- pyproject.toml | 7 ++++++- table2ascii/exceptions.py | 10 +++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 267f35a..c76ef8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,12 +46,17 @@ dependencies = [ [project.optional-dependencies] docs = [ "enum-tools", - "sphinx", + "sphinx>=4.0.0,<5", "sphinx-autobuild", "sphinx-toolbox", "sphinxcontrib_trio", "sphinxext-opengraph", "sphinx-book-theme==0.3.3", + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", ] dev = [ "mypy>=0.982,<2", diff --git a/table2ascii/exceptions.py b/table2ascii/exceptions.py index b4b6314..cefecf9 100644 --- a/table2ascii/exceptions.py +++ b/table2ascii/exceptions.py @@ -40,7 +40,7 @@ class FooterColumnCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - footer (:class:`Sequence `\ [:class:`SupportsStr`]): + footer (:class:`Sequence ` [:class:`SupportsStr`]): The footer that caused the error expected_columns (:class:`int`): The number of columns that were expected """ @@ -64,10 +64,10 @@ class BodyColumnCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - body (:class:`Sequence `\ [\ :class:`Sequence `\ [:class:`SupportsStr`]]): + body (:class:`Sequence ` [ :class:`Sequence ` [:class:`SupportsStr`]]): The body that caused the error expected_columns (:class:`int`): The number of columns that were expected - first_invalid_row (:class:`Sequence `\ [:class:`SupportsStr`]): + first_invalid_row (:class:`Sequence ` [:class:`SupportsStr`]): The first row with an invalid column count """ @@ -93,7 +93,7 @@ class AlignmentCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - alignments (:class:`Sequence `\ [:class:`Alignment`]): + alignments (:class:`Sequence ` [:class:`Alignment`]): The alignments that caused the error expected_columns (:class:`int`): The number of columns that were expected """ @@ -117,7 +117,7 @@ class ColumnWidthsCountMismatchError(ColumnCountMismatchError): This class is a subclass of :class:`ColumnCountMismatchError`. Attributes: - column_widths (:class:`Sequence `\ [:data:`Optional `\ [:class:`int`]]): + column_widths (:class:`Sequence ` [:data:`Optional ` [:class:`int`]]): The column widths that caused the error expected_columns (:class:`int`): The number of columns that were expected """ From eab1097778f565b81a8c35d2aac3f8835463ea39 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:15:14 +0300 Subject: [PATCH 28/30] [pre-commit.ci] pre-commit autoupdate (#118) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jonah Lawrence --- .pre-commit-config.yaml | 4 ++-- table2ascii/__init__.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a4da44..f7bfe56 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.12.1 + rev: 24.8.0 hooks: - id: black name: Running black in all files. @@ -19,7 +19,7 @@ repos: name: Running isort in all files. - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-ast name: Check if python files are valid syntax for the ast parser diff --git a/table2ascii/__init__.py b/table2ascii/__init__.py index bc53a37..f521f0e 100644 --- a/table2ascii/__init__.py +++ b/table2ascii/__init__.py @@ -1,6 +1,7 @@ """ table2ascii - Library for converting 2D Python lists to fancy ASCII/Unicode tables """ + import sys from typing import TYPE_CHECKING From 1b016d6638e37a43270222f442da772078ecec22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:16:22 +0300 Subject: [PATCH 29/30] chore(deps-dev): update pytest requirement from <8,>=6.0.0 to >=6.0.0,<9 (#117) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c76ef8f..f98d644 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ dev = [ "mypy>=0.982,<2", "pre-commit>=2.0.0,<4", "pyright>=1.0.0,<2", - "pytest>=6.0.0,<8", + "pytest>=6.0.0,<9", "slotscheck>=0.1.0,<1", "taskipy>=1.0.0,<2", "tox>=3.0.0,<5", From fb4f33c868974798df90d199d0495fe2ab72d428 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:25:23 +0300 Subject: [PATCH 30/30] chore(deps): update pre-commit requirement from <4,>=2.0.0 to >=2.0.0,<5 (#126) Updates the requirements on [pre-commit](https://github.com/pre-commit/pre-commit) to permit the latest version. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.0.0...v4.0.0) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f98d644..132658f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ docs = [ ] dev = [ "mypy>=0.982,<2", - "pre-commit>=2.0.0,<4", + "pre-commit>=2.0.0,<5", "pyright>=1.0.0,<2", "pytest>=6.0.0,<9", "slotscheck>=0.1.0,<1",