# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Collection of table rendering utilities."""
from __future__ import annotations
import csv
from functools import partial
from gettext import gettext as _
from io import StringIO
from typing import Sequence
import tabulate
from tabulate import DataRow, Line, TableFormat
from . import Choice, Context, Parameter, echo
from .parameters import ExtraOption
tabulate.MIN_PADDING = 0
"""Neutralize spurious double-spacing in table rendering."""
tabulate._table_formats.update( # type: ignore[attr-defined]
{
"github": TableFormat(
lineabove=Line("| ", "-", " | ", " |"),
linebelowheader=Line("| ", "-", " | ", " |"),
linebetweenrows=None,
linebelow=None,
headerrow=DataRow("| ", " | ", " |"),
datarow=DataRow("| ", " | ", " |"),
padding=0,
with_header_hide=["lineabove"],
),
},
)
"""Tweak table separators to match MyST and GFM syntax.
I.e. add a space between the column separator and the dashes filling a cell:
``|---|---|---|`` β ``| --- | --- | --- |``
That way we produce a table that doesn't need any supplement linting.
This has been proposed upstream at `python-tabulate#261
<https://github.com/astanin/python-tabulate/pull/261>`_.
"""
output_formats: list[str] = sorted(
# Formats from tabulate.
list(tabulate._table_formats) # type: ignore[attr-defined]
# Formats inherited from previous legacy cli-helpers dependency.
+ ["csv", "vertical"]
# Formats derived from CSV dialects.
+ [f"csv-{d}" for d in csv.list_dialects()],
)
"""All output formats supported by click-extra."""
[docs]
def get_csv_dialect(format_id: str) -> str | None:
"""Extract, validate and normalize CSV dialect ID from format."""
assert format_id.startswith("csv")
# Defaults to excel rendering, like in Python's csv module.
dialect = "excel"
parts = format_id.split("-", 1)
assert parts[0] == "csv"
if len(parts) > 1:
dialect = parts[1]
return dialect
[docs]
def render_csv(
tabular_data: Sequence[Sequence[str]],
headers: Sequence[str] = (),
**kwargs,
) -> None:
# StringIO is used to capture CSV output in memory. Hard-coded to default to UTF-8:
# https://github.com/python/cpython/blob/9291095a746cbd266a3681a26e10989def6f8629/Lib/_pyio.py#L2652
with StringIO(newline="") as output:
writer = csv.writer(output, **kwargs)
writer.writerow(headers)
writer.writerows(tabular_data)
# Use print instead of echo to conserve CSV dialect's line termination,
# avoid extra line returns and keep ANSI coloring.
print(output.getvalue(), end="")
[docs]
def render_vertical(
tabular_data: Sequence[Sequence[str]],
headers: Sequence[str] = (),
**kwargs,
) -> None:
"""Re-implements ``cli-helpers``'s vertical table layout.
See `cli-helpers source for reference
<https://github.com/dbcli/cli_helpers/blob/v2.3.0/cli_helpers/tabular_output/vertical_table_adapter.py>`_.
"""
header_len = max(len(h) for h in headers)
padded_headers = [h.ljust(header_len) for h in headers]
for index, row in enumerate(tabular_data):
# 27 has been hardcoded in cli-helpers:
# https://github.com/dbcli/cli_helpers/blob/4e2c417/cli_helpers/tabular_output/vertical_table_adapter.py#L34
echo(f"{'*' * 27}[ {index + 1}. row ]{'*' * 27}")
for cell_label, cell_value in zip(padded_headers, row):
echo(f"{cell_label} | {cell_value}")
[docs]
def render_table(
tabular_data: Sequence[Sequence[str]],
headers: Sequence[str] = (),
**kwargs,
) -> None:
"""Render a table with tabulate and output it via echo."""
defaults = {
"disable_numparse": True,
"numalign": None,
}
defaults.update(kwargs)
echo(tabulate.tabulate(tabular_data, headers, **defaults)) # type: ignore[arg-type]