sextile.formatting

Sequences formatted as parts of a page.

A sequence part is a layout.Drawable that lays out a homogeneous sequence: it takes as many entries as the room allows, says which of them can be chosen, and hands back the rest for the next frame. It deals only with the entries; titles, rules and keys are the layout’s.

The shapes ready to use, differing in how many entries a frame holds and how each one is drawn:

Menu numbered, a line of detail beneath each Listing two columns, nothing numbered, for a page that is a reference Figures a label and a figure a row, the figures aligned in one column Lines lines drawn as given, for a page that simply says something Prose running text, wrapped, in as many rows as it takes

A service needing a shape that is not here subclasses SequencePart or RowSequencePart and says how tall an entry is and how to draw one.

Example

Twelve entries, nine on the first frame and three on the second:

PageLayout(
    title="LATEST POSTS",
    parts=[Menu(entries=posts)],
).build(request)
class sextile.formatting.Entry(*args, **kwargs)[source]

Bases: Protocol

What a sequence part over Entry values requires of them.

A protocol rather than a base class, so that a service with its own richer entry type, carrying whatever it needs, can pass that value directly rather than copy it into a dataclass belonging to the framework.

property text: str

The text drawn on the entry’s first row.

property detail: str

A second line, a second column, or empty for neither.

property destination: PageAddress | None

The address choosing this entry leads to, or None if it is only read.

class sextile.formatting.Figures(*, entries, empty='', label_width=None, figure_width=None)[source]

Bases: RowSequencePart[Entry]

A label and a figure a row, the figures right-aligned in one column.

For a page that reports rather than offers: how many callers, how much is held, how long since. The entry’s text is the label and its detail the figure.

label_width

How wide the labels’ column is, in cells, and figure_width how wide the figures’. Worked out once and carried, as a Listing’s column is.

figure_width

See label_width.

Parameters:
INDENT: ClassVar[int] = 2

Two cells of margin before the label. A table of figures reads as a block rather than as a list, so it is set in from the edge.

ATTRIBUTES: ClassVar[int] = 2

One cell for the colour attribute of each column.

draw(row, entry)[source]

Write an entry’s first row.

Parameters:
Return type:

None

class sextile.formatting.SequencePart(*, entries, empty='')[source]

Bases: ABC, Generic

Abstract base for the sequence parts: a sequence, laid out as a part.

A subclass says how tall an entry is and how to draw one; this class works out how many fit in the room it is given, draws them, and hands back what is left.

The class attributes a subclass overrides to describe its shape:

  • rows_per_entry: rows one entry occupies.

  • gap: blank rows between one entry and the next, and not after the last.

  • numbered: whether entries take a digit, and so whether the reader can choose them.

  • choose_hint: what the prompt says about choosing, on frames with something to choose. Its key is a placeholder: the frame names the digits it actually offered, so a full frame says 1-9 and a short one 1-3.

entries

The values to draw, in the order they are to appear.

empty

Said in place of the entries where there are none. A string is one row; a sequence is its rows as given, an empty string among them a blank one. A service that answers slowly cannot let a frame come up empty and unexplained, because a reader cannot tell that from a fault.

Example

A part three rows tall that draws each entry by cell:

class Pictures(SequencePart[Picture]):
    rows_per_entry: ClassVar[int] = 3

    def draw_entry(self, canvas: Canvas, row: int, entry: Picture) -> None:
        entry.draw(canvas, row)
Parameters:
abstractmethod draw_entry(canvas, row, entry)[source]

Draw one entry in the rows_per_entry rows beginning at row.

Parameters:
  • canvas (Canvas) – The frame being drawn.

  • row (int) – The row the entry begins on.

  • entry (TypeVar(E)) – The value to draw.

Return type:

None

A numbered part is drawn its digit for it: NumberedRowSequencePart draws the digit column before the entry, so a subclass never sees the digit and every part but a numbered one is spared the parameter.

destination(entry)[source]

The address choosing entry leads to, or None where it leads nowhere.

Returns None for every entry unless a subclass overrides this.

Parameters:

entry (TypeVar(E))

Return type:

PageAddress | None

place(canvas, space)[source]

Draw as many entries as the room allows, and hand back the rest.

Parameters:
Return type:

Placed

class sextile.formatting.Lines(entries=(), *, empty='', colour=Colour.WHITE, align=Align.START)[source]

Bases: SequencePart[str]

Lines drawn as given, one to a row, for a page that says something.

Not prose, which wraps running text and puts a blank row between one paragraph and the next. A notice that has arranged its own lines and its own blanks means them where they are, so nothing is wrapped and nothing is moved: a line too long for the row is cut.

entries

The lines, in the order they are to appear, passed first and without a keyword. An empty one leaves a blank row.

colour

What they are drawn in. White for a notice; green for a note beneath a table, set apart from the table above it.

align

Where each line sits across the row, Align.START (the left) by default. Centre a wrapped display string by wrapping it with Breaking.DISPLAY and drawing it Align.CENTRE.

Parameters:
draw_entry(canvas, row, entry)[source]

Draw one entry in the rows_per_entry rows beginning at row.

Parameters:
  • canvas (Canvas) – The frame being drawn.

  • row (int) – The row the entry begins on.

  • entry (str) – The value to draw.

Return type:

None

A numbered part is drawn its digit for it: NumberedRowSequencePart draws the digit column before the entry, so a subclass never sees the digit and every part but a numbered one is spared the parameter.

class sextile.formatting.Listing(*, entries, empty='', column=None)[source]

Bases: RowSequencePart[Entry]

Two columns, nothing to choose, for a page that is a reference.

What a service is made of, which words it answers to. The left column is set to the width of the widest entry, so that the page reads as a table, and a detail too long for the room left over is carried on to a further row rather than being cut.

column

How wide the left column is. Worked out from the entries when the listing is first made, and carried from frame to frame thereafter – a table that set its column afresh on each frame would shift its columns partway down.

Parameters:
ATTRIBUTES: ClassVar[int] = 2

One cell for the colour attribute of each column.

classmethod widest()[source]

The greatest width the left column can take, in cells.

For a caller sizing the right-hand column, which gets whatever is left over. Computed here so a page need not repeat the arithmetic.

Return type:

int

draw(row, entry)[source]

Write an entry’s first row.

Parameters:
Return type:

None

class sextile.formatting.Menu(*, entries, empty='')[source]

Bases: NumberedRowSequencePart[Entry]

Numbered choices, each with a line of detail beneath it.

The shape most viewdata pages take. A reader chooses with a single keypress, so nine entries are the most one frame can offer and the rest go on the frames after it.

Parameters:
destination(entry)[source]

The address choosing entry leads to, or None where it leads nowhere.

Returns None for every entry unless a subclass overrides this.

Parameters:

entry (Entry)

Return type:

PageAddress | None

draw(row, entry)[source]

Write an entry’s first row.

Parameters:
Return type:

None

draw_detail(row, entry)[source]

Write an entry’s second row, where rows_per_entry allows one.

Parameters:
Return type:

None

class sextile.formatting.MenuItem(text='', detail='', destination=None)[source]

Bases: object

An Entry for a service that has nothing richer of its own.

text

The text drawn on the entry’s first row.

detail

A second line, a second column, or empty for neither.

destination

The address choosing it leads to, or None if it is only read.

Parameters:
class sextile.formatting.NumberedRowSequencePart(*, entries, empty='')[source]

Bases: RowSequencePart, Generic

A row sequence part whose entries a reader chooses by a digit.

Only a numbered part takes a digit, so only this one has it in its drawing path: it draws the digit column – a yellow 1 ` before the entry – and `draw writes the entry after it, in the room the digit has left. Menu is the one shape built on it; a service numbering its own entries subclasses this rather than reaching for the digit itself.

Example

Entries a reader chooses by digit, the digit drawn for you:

class Choices(NumberedRowSequencePart[Entry]):
    def destination(self, entry: Entry) -> PageAddress | None:
        return entry.destination

    def draw(self, row: RowWriter, entry: Entry) -> None:
        row.text(entry.text)
Parameters:
class sextile.formatting.Prose(*, entries, empty='')[source]

Bases: SequencePart[Row]

Running text, wrapped, in as many rows as it takes.

Its entries are rendered Row values rather than Entry values. Laying the text out through viewdata.typesetting gives a notice the same treatment as any long document: quotations in cyan, listings in green, nesting indented, and over-long words broken rather than dropped.

Parameters:
classmethod of(*paragraphs)[source]

Build prose from plain paragraphs, wrapping them on the way.

Parameters:

*paragraphs (str) – The text, one string a paragraph, in the order it is to be read. Empty strings are dropped; the gaps between paragraphs come from the layout.

Return type:

Prose

Returns:

The prose, ready to be laid out as a part.

draw_entry(canvas, row, entry)[source]

Draw one entry in the rows_per_entry rows beginning at row.

Parameters:
  • canvas (Canvas) – The frame being drawn.

  • row (int) – The row the entry begins on.

  • entry (Row) – The value to draw.

Return type:

None

A numbered part is drawn its digit for it: NumberedRowSequencePart draws the digit column before the entry, so a subclass never sees the digit and every part but a numbered one is spared the parameter.

class sextile.formatting.RowSequencePart(*, entries, empty='')[source]

Bases: SequencePart, Generic

Abstract base for sequence parts whose entries are written along their rows.

Implements draw_entry by calling draw for an entry’s first row and draw_detail for its second, each with a RowWriter that runs from left to right. A shape positioned by cell, a picture several rows tall among them, should subclass SequencePart and implement draw_entry itself.

Example

A one-row listing that writes each entry as text:

class Names(RowSequencePart[str]):
    def draw(self, row: RowWriter, entry: str) -> None:
        row.text(entry)
Parameters:
abstractmethod draw(row, entry)[source]

Write an entry’s first row.

Parameters:
Return type:

None

draw_detail(row, entry)[source]

Write an entry’s second row, where rows_per_entry allows one.

Parameters:
Return type:

None

draw_entry(canvas, row, entry)[source]

Draw one entry in the rows_per_entry rows beginning at row.

Parameters:
  • canvas (Canvas) – The frame being drawn.

  • row (int) – The row the entry begins on.

  • entry (TypeVar(E)) – The value to draw.

Return type:

None

A numbered part is drawn its digit for it: NumberedRowSequencePart draws the digit column before the entry, so a subclass never sees the digit and every part but a numbered one is spared the parameter.