Source code for click_extra.sphinx.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.
"""Style the two-axis tables of a Sphinx build.

A table whose top-left cell names both of its axes, as
{func}`~click_extra.table.corner_header` spells them, is read down its first
column and across its headers. On a narrow screen, a browser wraps that first
column wherever it can, including after the hyphen of `click-extra` or of a
date, which splits every label over several lines. The labels of such a table
are short, so this module keeps each one whole: the table scrolls sideways
instead, inside the `table-wrapper` Furo puts around every table.

A compatibility matrix is such a table, and each of its cells holds the glyph of
a state (see {mod}`click_extra.sphinx.matrix`). This module tints those cells
green, red or grey by state, so the shape of the support reads at a glance.

The extension marks every table whose first header cell holds
{data}`~click_extra.table.CORNER_GLYPH`, and each of its body cells holding a
state glyph alone, then ships the stylesheet the marks hook into. The
stylesheet is written into the build rather than packaged as a data file.
"""

from __future__ import annotations

from pathlib import Path

from docutils import nodes

from ..table import CORNER_GLYPH
from .matrix import FORBIDDEN_CELL, SUPPORTED_CELL, UNDECLARED_CELL

TYPE_CHECKING = False
if TYPE_CHECKING:
    from sphinx.application import Sphinx


CORNER_TABLE_CLASS = "corner-table"
"""HTML class of a table whose top-left cell names both of its axes."""

SUPPORTED_CELL_CLASS = "matrix-supported"
"""HTML class of a cell holding {data}`~click_extra.sphinx.matrix.SUPPORTED_CELL`."""

FORBIDDEN_CELL_CLASS = "matrix-forbidden"
"""HTML class of a cell holding {data}`~click_extra.sphinx.matrix.FORBIDDEN_CELL`."""

UNDECLARED_CELL_CLASS = "matrix-undeclared"
"""HTML class of a cell holding {data}`~click_extra.sphinx.matrix.UNDECLARED_CELL`."""

STATE_CELL_CLASSES: dict[str, str] = {
    SUPPORTED_CELL: SUPPORTED_CELL_CLASS,
    FORBIDDEN_CELL: FORBIDDEN_CELL_CLASS,
    UNDECLARED_CELL: UNDECLARED_CELL_CLASS,
}
"""The HTML class of each state glyph, for a body cell holding that glyph alone."""

CORNER_TABLE_CSS = f"""\
table.{CORNER_TABLE_CLASS} th:first-child,
table.{CORNER_TABLE_CLASS} td:first-child {{
  white-space: nowrap;
}}

/* State tints are translucent, so they read on light and dark themes alike.
 * Each is drawn as a background image, which layers over the background color
 * the crosshair sets on hover instead of hiding it. */
table.{CORNER_TABLE_CLASS} td.{SUPPORTED_CELL_CLASS} {{
  --matrix-tint: rgba(46, 160, 67, 0.15);
}}
table.{CORNER_TABLE_CLASS} td.{FORBIDDEN_CELL_CLASS} {{
  --matrix-tint: rgba(248, 81, 73, 0.15);
}}
table.{CORNER_TABLE_CLASS} td.{UNDECLARED_CELL_CLASS} {{
  --matrix-tint: rgba(128, 128, 128, 0.1);
}}
table.{CORNER_TABLE_CLASS} td.{SUPPORTED_CELL_CLASS},
table.{CORNER_TABLE_CLASS} td.{FORBIDDEN_CELL_CLASS},
table.{CORNER_TABLE_CLASS} td.{UNDECLARED_CELL_CLASS} {{
  background-image: linear-gradient(var(--matrix-tint), var(--matrix-tint));
}}
"""
"""Stylesheet of corner-labeled tables.

Keeps their first column on one line, and tints the state cells of a matrix.
"""

STATIC_DIR = "_click_extra_static"
"""Output folder of the stylesheets and scripts the extension writes.

Sphinx copies it into `_static` with the other static files, as it does for
every folder listed in `html_static_path`. See `_write_static_file`.
"""

STYLESHEET = "click-extra.css"
"""File name of the stylesheet, served from the `_static` folder."""


[docs] def mark_corner_tables(app: Sphinx, doctree: nodes.document) -> None: """Add {data}`CORNER_TABLE_CLASS` to every table with a corner label. Each body cell of such a table holding a state glyph alone also gets the class of that state (see {data}`STATE_CELL_CLASSES`). """ for table in doctree.findall(nodes.table): header = next(table.findall(nodes.thead), None) if header is None: continue corner = next(header.findall(nodes.entry), None) if corner is None or CORNER_GLYPH not in corner.astext(): continue table["classes"].append(CORNER_TABLE_CLASS) for body in table.findall(nodes.tbody): for entry in body.findall(nodes.entry): state = STATE_CELL_CLASSES.get(entry.astext().strip()) if state: entry["classes"].append(state)
def _write_static_file(app: Sphinx, name: str, content: str) -> None: """Write `content` to the file `name` of {data}`STATIC_DIR` in the build. The folder joins `html_static_path` with its first file, whichever module writes it, so Sphinx serves every file of the folder from `_static`. """ static_dir = Path(app.outdir) / STATIC_DIR static_dir.mkdir(parents=True, exist_ok=True) (static_dir / name).write_text(content, encoding="UTF-8") if str(static_dir) not in app.config.html_static_path: app.config.html_static_path.append(str(static_dir))
[docs] def add_stylesheet(app: Sphinx) -> None: """Write {data}`CORNER_TABLE_CSS` into the build and link it from each page. Only an HTML builder serves stylesheets, so other builders skip it. """ if app.builder.format != "html": return _write_static_file(app, STYLESHEET, CORNER_TABLE_CSS) app.add_css_file(STYLESHEET)
[docs] def setup(app: Sphinx) -> None: """Register the table marker and its stylesheet on `app`. Called from {func}`click_extra.sphinx.setup` so projects only need to list `"click_extra.sphinx"` in their `extensions`. """ app.connect("doctree-read", mark_corner_tables) app.connect("builder-inited", add_stylesheet)