Skip to content

render

render #

Render — the context a page renders in: where its files go and where its links point.

  1. Files. stage copies a file-valued prop under the page's files/ and lib copies a shipped browser library under lib/; both return the relative href a component writes.
  2. Links. A link is a PageLink or an of entry, nothing else. A value loaded from a target has a position — the base it was read from (position); href turns a PageLink's value, and entry_href an of constructor's container entry, into the relative link from this page's at to that position's page, or None when either side has no position.

Render #

One page's render context, handed to every component's html.

at is the position of the value the page is the page of; every link is relative to it, one ../ up for the ~/ segment the server mounts the page under, then the on-disk path from at to the linked position.

Source code in capturegraph-lib/capturegraph/ui/render.py
class Render:
    """One page's render context, handed to every component's ``html``.

    ``at`` is the position of the value the page is the page of; every link is
    relative to it, one ``../`` up for the ``~/`` segment the server mounts the
    page under, then the on-disk path from ``at`` to the linked position.
    """

    def __init__(self, page: DirScalar, at: object | None) -> None:
        """A render into ``page`` for the page of ``at`` (``None`` for a page of nothing)."""
        self._page = page
        self.at = position(at) if at is not None else None
        self._staged: dict[Path, str] = {}
        self._libraries: set[str] = set()
        self._emitted: set[str] = set()
        self._counters: dict[str, int] = {}

    def stage(self, file: str | os.PathLike[str]) -> str:
        """Copy ``file`` (or a directory) under ``files/`` and return its relative href.

        Files are numbered in the order they are first staged; the same source
        staged twice is copied once.

        ```python
        assert render.stage(session.photo) == "files/0.jpeg"
        ```
        """
        source = Path(os.path.abspath(file))
        href = self._staged.get(source)
        if href is None:
            suffix = source.suffix if source.is_file() else ""
            href = f"{FILES_DIR}/{len(self._staged)}{suffix}"
            self._page.stage(source, href)
            self._staged[source] = href
        return href

    def href(self, value: object) -> str | None:
        """The relative link to ``value``'s page, or ``None`` when it or this page has no position.

        ```python
        assert render.href(sibling) == "../../0006380070917400/"
        ```
        """
        base = position(value)
        return None if base is None else self._link(base)

    def entry_href(self, container: Map | Array, key: object) -> str | None:
        """The relative link to the page of ``container``'s entry at ``key``, or ``None``.

        ``key`` is a ``Map`` key or an ``Array`` index; the entry need not exist.

        ```python
        assert render.entry_href(journal.sessions, noon) == "../sessions/0006380070917400/"
        ```

        Raises:
            TypeError: If ``container`` is an ``Array`` and ``key`` is not an ``int``.
        """
        base = position(container)
        if base is None:
            return None
        if isinstance(container, Array):
            if not isinstance(key, int):
                raise TypeError(
                    f"{type(container).__name__} entries are indexed by int, got {key!r}"
                )
            name = index_name(key)
        else:
            key_type = container._key
            if key_type is None:
                return None
            name = key_type.key_encode(key)
        return self._link(base / name)

    def _link(self, base: Path) -> str | None:
        if self.at is None:
            return None
        parts = base.relative_to(self.at, walk_up=True).parts
        return "../" + "".join(f"{part}/" for part in parts)

    def lib(self, name: str) -> str:
        """The relative href of the shipped library file ``name`` (``"leaflet/leaflet.css"``).

        The whole library folder is copied under ``lib/`` the first time one of
        its files is asked for.

        ```python
        assert render.lib("vega/vega.min.js") == "lib/vega/vega.min.js"
        ```

        Raises:
            FileNotFoundError: If no such library ships with the package.
        """
        library = name.partition("/")[0]
        if library not in self._libraries:
            copy_library(library, Path(self._page, LIB_DIR, library))
            self._libraries.add(library)
        return f"{LIB_DIR}/{name}"

    def once(self, key: str, html: str) -> str:
        """``html`` the first time ``key`` is seen in this render, ``""`` after.

        ```python
        render.once("leaflet", element("script", {"src": render.lib("leaflet/leaflet.js")}))
        ```
        """
        if key in self._emitted:
            return ""
        self._emitted.add(key)
        return html

    def identifier(self, prefix: str) -> str:
        """A fresh element id ``cg-<prefix>-<n>``, unique within this render."""
        count = self._counters.get(prefix, 0) + 1
        self._counters[prefix] = count
        return f"cg-{prefix}-{count}"

__init__(page, at) #

A render into page for the page of at (None for a page of nothing).

Source code in capturegraph-lib/capturegraph/ui/render.py
def __init__(self, page: DirScalar, at: object | None) -> None:
    """A render into ``page`` for the page of ``at`` (``None`` for a page of nothing)."""
    self._page = page
    self.at = position(at) if at is not None else None
    self._staged: dict[Path, str] = {}
    self._libraries: set[str] = set()
    self._emitted: set[str] = set()
    self._counters: dict[str, int] = {}

entry_href(container, key) #

The relative link to the page of container's entry at key, or None.

key is a Map key or an Array index; the entry need not exist.

assert render.entry_href(journal.sessions, noon) == "../sessions/0006380070917400/"

Raises:

Type Description
TypeError

If container is an Array and key is not an int.

Source code in capturegraph-lib/capturegraph/ui/render.py
def entry_href(self, container: Map | Array, key: object) -> str | None:
    """The relative link to the page of ``container``'s entry at ``key``, or ``None``.

    ``key`` is a ``Map`` key or an ``Array`` index; the entry need not exist.

    ```python
    assert render.entry_href(journal.sessions, noon) == "../sessions/0006380070917400/"
    ```

    Raises:
        TypeError: If ``container`` is an ``Array`` and ``key`` is not an ``int``.
    """
    base = position(container)
    if base is None:
        return None
    if isinstance(container, Array):
        if not isinstance(key, int):
            raise TypeError(
                f"{type(container).__name__} entries are indexed by int, got {key!r}"
            )
        name = index_name(key)
    else:
        key_type = container._key
        if key_type is None:
            return None
        name = key_type.key_encode(key)
    return self._link(base / name)

href(value) #

The relative link to value's page, or None when it or this page has no position.

assert render.href(sibling) == "../../0006380070917400/"
Source code in capturegraph-lib/capturegraph/ui/render.py
def href(self, value: object) -> str | None:
    """The relative link to ``value``'s page, or ``None`` when it or this page has no position.

    ```python
    assert render.href(sibling) == "../../0006380070917400/"
    ```
    """
    base = position(value)
    return None if base is None else self._link(base)

identifier(prefix) #

A fresh element id cg-<prefix>-<n>, unique within this render.

Source code in capturegraph-lib/capturegraph/ui/render.py
def identifier(self, prefix: str) -> str:
    """A fresh element id ``cg-<prefix>-<n>``, unique within this render."""
    count = self._counters.get(prefix, 0) + 1
    self._counters[prefix] = count
    return f"cg-{prefix}-{count}"

lib(name) #

The relative href of the shipped library file name ("leaflet/leaflet.css").

The whole library folder is copied under lib/ the first time one of its files is asked for.

assert render.lib("vega/vega.min.js") == "lib/vega/vega.min.js"

Raises:

Type Description
FileNotFoundError

If no such library ships with the package.

Source code in capturegraph-lib/capturegraph/ui/render.py
def lib(self, name: str) -> str:
    """The relative href of the shipped library file ``name`` (``"leaflet/leaflet.css"``).

    The whole library folder is copied under ``lib/`` the first time one of
    its files is asked for.

    ```python
    assert render.lib("vega/vega.min.js") == "lib/vega/vega.min.js"
    ```

    Raises:
        FileNotFoundError: If no such library ships with the package.
    """
    library = name.partition("/")[0]
    if library not in self._libraries:
        copy_library(library, Path(self._page, LIB_DIR, library))
        self._libraries.add(library)
    return f"{LIB_DIR}/{name}"

once(key, html) #

html the first time key is seen in this render, "" after.

render.once("leaflet", element("script", {"src": render.lib("leaflet/leaflet.js")}))
Source code in capturegraph-lib/capturegraph/ui/render.py
def once(self, key: str, html: str) -> str:
    """``html`` the first time ``key`` is seen in this render, ``""`` after.

    ```python
    render.once("leaflet", element("script", {"src": render.lib("leaflet/leaflet.js")}))
    ```
    """
    if key in self._emitted:
        return ""
    self._emitted.add(key)
    return html

stage(file) #

Copy file (or a directory) under files/ and return its relative href.

Files are numbered in the order they are first staged; the same source staged twice is copied once.

assert render.stage(session.photo) == "files/0.jpeg"
Source code in capturegraph-lib/capturegraph/ui/render.py
def stage(self, file: str | os.PathLike[str]) -> str:
    """Copy ``file`` (or a directory) under ``files/`` and return its relative href.

    Files are numbered in the order they are first staged; the same source
    staged twice is copied once.

    ```python
    assert render.stage(session.photo) == "files/0.jpeg"
    ```
    """
    source = Path(os.path.abspath(file))
    href = self._staged.get(source)
    if href is None:
        suffix = source.suffix if source.is_file() else ""
        href = f"{FILES_DIR}/{len(self._staged)}{suffix}"
        self._page.stage(source, href)
        self._staged[source] = href
    return href

position(value) #

The base value was loaded from in a target, or None when it has none.

A value built in memory, changed since it was loaded, or held by the recipe scratch (the result of a recipe call) has no position.

assert position(cg.load(root, Journal).sessions) == root / "sessions"
assert position(thumbnail(session.photo)) is None
Source code in capturegraph-lib/capturegraph/ui/render.py
def position(value: object) -> Path | None:
    """The base ``value`` was loaded from in a target, or ``None`` when it has none.

    A value built in memory, changed since it was loaded, or held by the
    recipe scratch (the result of a recipe call) has no position.

    ```python
    assert position(cg.load(root, Journal).sessions) == root / "sessions"
    assert position(thumbnail(session.photo)) is None
    ```
    """
    base = known_base(value)
    if base is None or pinned(value) is not None:
        return None
    return Path(os.path.abspath(base))