Source code for sextile.pages

"""One-call page shapes: the commonest Viewdata pages, each said in a call.

A notice, a menu, some prose. Each takes the request the page answers and
builds a `PageLayout`, so the title, the way home and the page number default
from the request exactly as they do for the layout underneath, and a page that
outgrows the one-call form graduates to a full `PageLayout` without changing
shape.

    async def now(request: PageRequest) -> Page:
        return notice_page(request, "It is noon.")

The framework's own notices are these too: `Sextile.not_found`, `.failed` and
`.timed_out` build a `notice_page` with `furniture=()`.
"""

from collections.abc import Callable, Sequence

from sextile.formatting import Entry, Lines, Menu, Prose
from sextile.layout import (
    DEFAULT_FURNITURE,
    DEFAULT_HOME,
    Custom,
    DefaultHome,
    Flow,
    Furnishing,
    OnOneFrame,
    PageLayout,
    Shortcut,
)
from sextile.page import Page, PageAddress
from sextile.requests import Neighbours, PageRequest
from sextile.viewdata.canvas import Canvas
from sextile.viewdata.controls import Colour
from sextile.viewdata.frame import ROWS

__all__ = [
    "farewell_page",
    "menu_page",
    "notice_page",
    "prose_page",
    "title_page",
]

#: What a page's `home` is when the caller leaves it unset: the app's index,
#: filled in by `PageLayout.build`. The same sentinel the layout uses, so a
#: page shape and a raw layout default the way home identically.
type _Home = PageAddress | Shortcut | None | DefaultHome


[docs] def notice_page( request: PageRequest, *lines: str, title: str | None = None, home: _Home = DEFAULT_HOME, numbered: bool = True, shortcuts: Sequence[Shortcut] = (), hang_up: bool = False, furniture: Sequence[Furnishing] = DEFAULT_FURNITURE, ) -> Page: """A page that simply says something. Args: request: The request this page answers. *lines: What to say, one string a row; empty strings leave a blank row. title: The header, or None to take the registered title of the page. home: Where `0` leads; unset takes `request.app.index`, `None` offers no way home. numbered: Whether the header shows the page number. `False` for a notice answering a request that names no page of its own. shortcuts: Keys offered on every frame besides the digits and `0`. hang_up: Whether the line drops once the page has been shown. furniture: The bands round the content. `()` draws a masthead-style notice with no header or footer, where the title heads the content in cyan instead; this is how the framework says things for itself. Returns: The page, of as many frames as the lines needed. """ if furniture: return PageLayout( title=title, home=home, numbered=numbered, shortcuts=shortcuts, hang_up=hang_up, furniture=furniture, parts=[Lines(lines)], ).build(request) # No header to carry the title, so it heads the content in cyan and the # lines follow a blank row down -- the plain notice the framework draws # for itself, kept to the top rows with room beneath for the cursor. heading = [OnOneFrame(Lines((title,), colour=Colour.CYAN))] if title else [] return PageLayout( home=home, shortcuts=shortcuts, hang_up=hang_up, furniture=(), parts=[*heading, OnOneFrame(Lines(("", *lines)))], ).build(request)
[docs] def prose_page( request: PageRequest, *paragraphs: str, title: str | None = None, home: _Home = DEFAULT_HOME, shortcuts: Sequence[Shortcut] = (), ) -> Page: """A page of running text, the line breaks left to the framework. Args: request: The request this page answers. *paragraphs: The paragraphs, each wrapped to the frame and spaced from the next. A long one runs on to further frames rather than being cut. title: The header, or None to take the registered title of the page. home: Where `0` leads; unset takes `request.app.index`, `None` offers no way home. shortcuts: Keys offered on every frame besides the digits and `0`. Returns: The page, of as many frames as the text needed. """ return PageLayout( title=title, home=home, shortcuts=shortcuts, parts=[Prose.of(*paragraphs)], ).build(request)
[docs] def farewell_page( request: PageRequest, title: str, *lines: str, hang_up: bool = True ) -> Page: """The page a caller sees last, after which the line drops. A `notice_page` with no furniture and no way home: a footer offering the index would mislead on a page there is no coming back from, and the rows it and the rules would take are the ones worth leaving blank, the reader being about to talk to their modem. Args: request: The request this page answers. title: The heading, drawn in cyan on the first row. *lines: What to say, one string a row, beginning two rows below the title. hang_up: Whether the line drops once shown. False for the involuntary parting, where the session drops the line itself. Returns: A page of a single frame, offering no keys. """ return notice_page( request, *lines, title=title, home=None, furniture=(), hang_up=hang_up )
[docs] def title_page( request: PageRequest, *, draw: Callable[[Canvas], None], next_page: PageAddress | None = None, shortcuts: Sequence[Shortcut] = (), ) -> Page: """The whole-frame title a service opens on, drawn by the caller. Args: request: The request this page answers. draw: Draws the frame, given the canvas; it has every row, from 0. next_page: Where `#` leads once the frame has been read, the service's index by default. `#` is the one key a viewdata reader tries without being told, and a title frame is an invitation to press it. shortcuts: Keys offered on the frame besides `#`. Returns: One frame with no furniture and no way home: a masthead is the whole frame, and there is no index to send a reader back to that they are not already at. """ return PageLayout( furniture=(), home=None, next_page=next_page if next_page is not None else request.app.index, shortcuts=shortcuts, parts=[OnOneFrame(Custom(rows=ROWS, draw=lambda canvas, row: draw(canvas)))], ).build(request)