Source code for meta_package_manager.tables

# 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.
"""Table-output vocabulary and rendering plumbing shared by the subcommands.

The :command:`mpm` subcommands render heterogeneous tables (different columns per
command) but share the same output machinery. This module owns all of it:

- :py:class:`SortableField`, the vocabulary of the global ``mpm --sort-by``
  selector. The selector itself is click-extra's field-vocabulary
  :py:class:`~click_extra.table.SortByOption`, and the per-table resolution (sort
  by the selected fields the table carries, keep the original row order when it
  carries none) happens inside :py:func:`click_extra.table.print_table`, from the
  field each header pairs with its column in the registries below.
- The per-command **column registries**, each pairing a click-extra
  :py:class:`~click_extra.table.ColumnSpec` (whose ID addresses the column from
  ``--columns``) with the :py:class:`SortableField` the column carries (``None``
  for a column that cannot drive the sort). A registry is the single source of
  truth for its command: the same tuple feeds the ``@columns_option`` declaration
  (which validates the user selection) and :py:func:`print_projected_table`
  (which projects headers and rows before rendering).
- :py:func:`print_projected_table` and :py:func:`print_serialized_and_exit`, the
  human-friendly and machine-friendly rendering paths every table-producing
  subcommand goes through.

.. note::
    The registry pairs' second element is annotated ``str | None`` rather than
    ``SortableField | None``: on Python 3.10, :py:class:`SortableField` extends
    ``backports.strenum.StrEnum``, whose stubs type the members as plain
    :py:class:`str`, so the tighter annotation only checks under 3.11+.
    ``StrEnum`` members being :py:class:`str` subclasses, the wider annotation is
    accurate on every supported version.
"""

from __future__ import annotations

import sys

from click_extra.context import COLUMNS, TABLE_FORMAT
from click_extra.table import (
    SERIALIZATION_FORMATS,
    ColumnSpec,
    print_data,
    select_columns,
    select_row,
)

if sys.version_info >= (3, 11):
    from enum import StrEnum
else:
    from backports.strenum import StrEnum  # type: ignore[import-not-found]

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Iterable, Sequence

    from click_extra import Context


[docs] class SortableField(StrEnum): """Fields IDs allowed to be sorted.""" MANAGER_ID = "manager_id" MANAGER_NAME = "manager_name" PACKAGE_ID = "package_id" PACKAGE_NAME = "package_name" VERSION = "version"
MANAGERS_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ( ( ColumnSpec("manager_id", "Manager ID", "Manager's identifier."), SortableField.MANAGER_ID, ), ( ColumnSpec("manager_name", "Name", "Manager's common name."), SortableField.MANAGER_NAME, ), ( ColumnSpec("supported", "Supported", "Support status on the current platform."), None, ), ( ColumnSpec("cli", "CLI", "Location of the manager's binary on the system."), None, ), ( ColumnSpec("executable", "Executable", "Whether the binary is executable."), None, ), ( ColumnSpec( "version", "Version", "Manager's self-reported version, and the unsatisfied requirement " "when stale.", ), SortableField.VERSION, ), ) """Columns of the ``mpm managers`` table.""" INSTALLED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ( ( ColumnSpec("package_id", "Package ID", "Package's identifier."), SortableField.PACKAGE_ID, ), ( ColumnSpec("package_name", "Name", "Package's common name."), SortableField.PACKAGE_NAME, ), ( ColumnSpec("manager_id", "Manager", "Manager reporting the package."), SortableField.MANAGER_ID, ), ( ColumnSpec( "installed_version", "Installed version", "Version currently installed." ), SortableField.VERSION, ), ) """Columns of the ``mpm installed`` table.""" OUTDATED_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ( *INSTALLED_COLUMNS, ( ColumnSpec( "latest_version", "Latest version", "Version available for upgrade." ), None, ), ) """Columns of the ``mpm outdated`` table.""" SEARCH_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ( ( ColumnSpec("package_id", "Package ID", "Package's identifier."), SortableField.PACKAGE_ID, ), ( ColumnSpec("package_name", "Name", "Package's common name."), SortableField.PACKAGE_NAME, ), ( ColumnSpec("manager_id", "Manager", "Manager reporting the match."), SortableField.MANAGER_ID, ), ( ColumnSpec("latest_version", "Latest version", "Latest version available."), SortableField.VERSION, ), ( ColumnSpec( "description", "Description", "Package description, for managers that provide one. Out of the " "default selection: select it explicitly or pass --description.", ), None, ), ) """Columns of the ``mpm search`` table. The ``description`` column exists in the registry (so ``--columns`` can select it) but stays out of the default selection unless ``--description`` (or ``--extended``, which searches descriptions) is passed. """ WHICH_COLUMNS: tuple[tuple[ColumnSpec, str | None], ...] = ( ( ColumnSpec( "manager_id", "Manager ID", "Manager whose search path found the binary." ), SortableField.MANAGER_ID, ), ( ColumnSpec( "priority", "Priority", "Rank of the match in the manager's search path." ), None, ), ( ColumnSpec("cli_path", "CLI path", "Location of the matched binary."), None, ), ( ColumnSpec( "symlink", "Symlink destination", "Resolved target when the match is a symlink.", ), None, ), ) """Columns of the ``mpm which`` table."""
[docs] def column_specs( columns: Sequence[tuple[ColumnSpec, str | None]], ) -> tuple[ColumnSpec, ...]: """Extract the bare :class:`~click_extra.table.ColumnSpec` tuple from a column registry.""" return tuple(spec for spec, _ in columns)