sextile¶
Sextile: a framework for Prestel-style Viewdata services.
What a service is made of:
from sextile import Page, PageRequest, PageRoute, Sextile
- async def main(request: PageRequest) -> Page:
…
app = Sextile(pages=[PageRoute(“1”, main, name=”main”, keywords=(“MAIN”,))])
and sextile serve your_module:app answers calls on it. A page is a value and a service is a list of them, so everything about a page is stated in one place and registration order does not matter. Sessions, frames, control codes, page numbering and the wire are the framework’s; what the pages say is yours.
Named after the star key on a viewdata keypad.
- class sextile.Neighbours(previous=None, next=None)[source]¶
Bases:
objectThe pages either side of this one, in the sequence being read.
Which sequence depends on how the reader got here: a page reached through one menu has that menu’s pages either side of it, and through another has that one’s. A page reached by keying its number has neither, and should be offered neither.
- previous¶
The page before this one in the sequence, or None.
- next¶
The page after this one in the sequence, or None.
- Parameters:
previous (
PageAddress|None)next (
PageAddress|None)
- class sextile.Converter(*, field_pattern, width=None, parse=None, format=None)[source]¶
Bases:
objectHow one field of a page number is read and written.
field_patternmatches the field’s digits;widthis how many digits it always takes, or None when it varies.parsemay raiseValueErrorto reject digits its pattern could not exclude – the 31st of February passes any regex worth writing.- Parameters:
- to_digits(value)[source]¶
How this value is written, or raise
NoSuchRouteError.The result is checked against the field’s own pattern, so a value that would build something which is not a page number – a negative id, a date where a number belongs – fails here rather than producing an address that names nothing.
- class sextile.GuideRow(key='', does='')[source]¶
Bases:
objectOne row of the guide: a key a reader may press, and what it does.
- key¶
What the reader presses, as the guide writes it: 1-9, *95#, A-Z. Empty continues the row above, which is how a meaning too long for the column is carried on to a second line.
- does¶
What pressing it does, in the framework’s own words. Empty alongside an empty key leaves a blank row, for grouping.
A row rather than a key, because a service passing these to Sextile.guide is describing its own additions to a table and not naming a keypress the session will answer.
- class sextile.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.
- draw_entry(canvas, row, entry)[source]¶
Draw one entry in the rows_per_entry rows beginning at row.
- Parameters:
- Return type:
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.Align(*values)[source]¶
Bases:
EnumWhere to put something along an axis, without counting cells.
START and END are spelled for the axis: the left or right edge where the axis is a row’s columns, the top or bottom where it is the frame’s rows. Naming them for the axis rather than LEFT/RIGHT lets one enum place a thing across a row and down a frame, without LEFT having to mean “top”.
Passed instead of a coordinate because placing at the middle depends on what the row’s attributes cost in cells, which the composition works out; a caller computing the coordinate would have to find that cost first.
- class sextile.MenuItem(text='', detail='', destination=None)[source]¶
Bases:
objectAn 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:
text (
str)detail (
str)destination (
PageAddress|None)
- exception sextile.NoSuchRouteError[source]¶
Bases:
LookupErrorAn address was asked for that cannot be built.
- class sextile.Page(frames, hang_up=False, next_page=None)[source]¶
Bases:
objectOne page, in one or more frames.
- hang_up: bool = False¶
Whether the line should drop once this page has been shown. Which page ends the call is the application’s choice, not the framework’s.
- next_page: PageAddress | None = None¶
Where # leads once this page’s frames have run out.
A title frame or the last page of a guide is an invitation to press #; without this they would be dead ends under that key.
- property destinations: tuple[PageAddress, ...]¶
Everything this page offers to go to, in the order it offers it.
Across all frames, so a menu’s ninth choice is followed by the first of its next frame. This is the sequence the next and previous keys walk.
Only the digit keys, and not 0: the way back to the index is on every page, and including it would make next mean something other than what the menu offered.
- class sextile.PageAddress(digits)[source]¶
Bases:
objectA page number, as keyed.
Frozen and hashable because addresses are used as keys throughout: what a digit on this frame leads to, where the reader has been, and the run of pages a menu offered.
- Parameters:
digits (
str)
- class sextile.PageFrame(frame, choices=<factory>, moves=frozenset({}), form=None)[source]¶
Bases:
objectOne screen of a page, and what a key does while it is showing.
- Parameters:
- choices: Mapping[str, PageAddress]¶
Keys that lead to another page, keyed by character rather than digit so a page can offer N for next or R for reply.
- moves: frozenset[str] = frozenset({})¶
Keys that move to another frame of this page. Separate from choices because they name no destination; the session stays on the page already showing.
- form: Form | None = None¶
A field on this frame that the reader types into, or None.
A form answers a keypress by redrawing part of the frame rather than leading elsewhere. Its digits change as the reader types, so destination consults it before the frame’s fixed choices; the session then treats the result as it treats a digit on a menu.
- destination(key)[source]¶
Where a key leads, or None if it leads nowhere here.
A form’s choices are consulted first and shadow the frame’s fixed choices, since they reflect what the reader has just typed. A frame carrying a form should not also offer a fixed digit; if it does, the form’s choice wins while it has one.
- Parameters:
key (
str)- Return type:
- class sextile.PageRequest(address, app, params=<factory>, neighbours=Neighbours(previous=None, next=None), session=<factory>, history=(), state=<factory>)[source]¶
Bases:
objectOne page, asked for.
- Parameters:
address (
PageAddress)app (
Sextile)neighbours (
Neighbours)session (
State)history (
tuple[PageAddress,...])state (
StateReader)
- app: Sextile¶
The service this page belongs to.
Starlette’s request.app. It lets a handler be an ordinary function declared beside its fellows rather than a closure built in a factory: a page that offers another page looks up where that page is through the service.
- params: dict[str, object]¶
What the route’s pattern captured. Also passed to the handler as keyword arguments, so a handler need not unpack them.
- neighbours: Neighbours = Neighbours(previous=None, next=None)¶
The pages either side of this one in the sequence the reader is following, or a Neighbours of two Nones for a page reached by keying its number.
- session: State¶
What this caller has accumulated over their connection, keyed by StateKey. The connection is the session – the terminal keeps nothing but the frame on screen – so this is where anything outlasting a single page belongs.
Writable, where state is read-only: this is one caller’s own store, so a page writing request.session[KEY] changes nothing another caller can see.
- history: tuple[PageAddress, ...] = ()¶
Where this caller has been, oldest first, as far back as the session keeps. The terminal keeps only the frame on screen, so a service offering a way back through the call reads it from here.
- state: StateReader¶
What the service opened, for as long as it is running – an archive, a client, an index – read back through the StateKey it was written under.
The counterpart of session: session is one caller’s and lasts as long as the line, state is shared and lasts as long as the process. A read-only view here, because a change would reach every other caller at once; the lifespan writes it through app.state.
- class sextile.PageRoute(pattern, handler, name=None, title='', detail='', keywords=(), label=None, keyed='')[source]¶
Bases:
objectOne page of a service, declared as a value rather than as a decoration.
Everything about a page is here: its place in the numbering, what builds it, what to call it where it is listed, and the words that reach it. The service is a list of these. Declaring pages as data makes registration order unobservable: converters, pages, middleware and lifespan all arrive in one constructor call, so no step has to run before another.
A route also carries what the service reads back about the page it declared: Sextile.route and Sextile.routes return these, with keyed filled in. On a route a caller constructs, keyed is empty until a service registers it: there is no numbering to read it against yet.
- Parameters:
- handler: Handler¶
What builds the page. None rather than a page means there is no such page, which the session shows differently from one it could not build.
- title: str = ''¶
What to call this page where it is listed rather than shown – in a menu, in the history, in the contents. A page with no title is not advertised, which is how a title frame stays off the contents.
- label: str | Callable[[...], str] | None = None¶
What to call the page in a list of visited pages, where the listed title is wrong because the number carried a field: the title names the kind of page, the label names the one a reader was on. A str.format template over the route’s captured fields (label=”Item {n}”), or a callable taking them as keyword arguments (label=lambda n: title_of(n)). None leaves Sextile.label_for to build one from the title and the fields.
- class sextile.PageRouter[source]¶
Bases:
objectPages declared together, in a module of their own, for one service.
A handler that lives apart from the Sextile it serves is declared with @router.page(…), the same call as @app.page, and the module’s pages reach the service in one spread. Iterating a router yields each PageRoute in the order they were declared, so a service reads the way its source does.
Example
A module’s pages, gathered and spread into a service:
router = PageRouter() @router.page("3", title="By day", keywords=("WHO",)) async def days(request: PageRequest) -> Page: ... app = Sextile(pages=[*router, *standard_pages(history="92")])
- page(pattern, *, name=None, title='', detail='', keywords=(), label=None)[source]¶
Declare a page beside the function that builds it.
The same call as Sextile.page, for a handler in a module that has no application object to hang it on. The route takes the handler’s own name unless given one.
- Parameters:
pattern (
str) – The page numbers this answers: literal digits and fields.name (
str|None) – What address_for calls it, or None for the handler’s name.title (
str) – What to call it where it is listed. Untitled is unadvertised.detail (
str) – A second line, wherever the title gets one.keywords (
Sequence[str]) – Words a reader may key instead of the number.label (
str|Callable[...,str] |None) – What to call it in a list of visited pages, when the title is wrong because the number carried a field. See PageRoute.label.
- Return type:
Callable[[TypeVar(H, bound=Callable[...,Awaitable[Page|None]])],TypeVar(H, bound=Callable[...,Awaitable[Page|None]])]- Returns:
A decorator that collects its handler’s route and returns it unchanged, so a service checked strictly stays so past the decorator.
- class sextile.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.
- draw_entry(canvas, row, entry)[source]¶
Draw one entry in the rows_per_entry rows beginning at row.
- Parameters:
- Return type:
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.
- exception sextile.RouteError[source]¶
Bases:
ValueErrorA route, keyword or converter that could not be registered.
- class sextile.Sextile(*, name='', home='1', index=None, converters=None, pages=(), middleware=(), lifespan=None)[source]¶
Bases:
objectAn application that answers by routing page numbers to handlers.
- Parameters:
name (
str)home (
str|PageAddress)index (
str|PageAddress|None)converters (
Mapping[str,Converter|Callable[[str|None],Converter]] |None)middleware (
Sequence[Callable[[PageRequest,Callable[[PageRequest],Awaitable[Page|None]]],Awaitable[Page|None]]])lifespan (
Callable[[Sextile],AbstractAsyncContextManager[None]] |None)
- property home: PageAddress¶
The page a caller arrives on when the line opens.
- property index: PageAddress¶
The page 0 leads to from every frame, home unless set apart.
- page(pattern, *, name=None, title='', detail='', keywords=(), label=None)[source]¶
Register the decorated handler for every page number pattern matches.
- Parameters:
pattern (
str) – The page numbers this answers: literal digits and named fields.name (
str|None) – What address_for calls the route. The handler’s own name where none is given.title (
str) – What to call the page where it is listed rather than shown – in a menu, in the history, in the contents. A page with no title is not advertised, which keeps a title frame off the contents.detail (
str) – A second line, wherever the title gets one.keywords (
Sequence[str]) – Words a reader may key instead of the number.label (
str|Callable[...,str] |None) – What to call the page in a list of visited pages, where the title is wrong because the number carried a field. See PageRoute.label.
- Return type:
Callable[[TypeVar(H, bound=Callable[...,Awaitable[Page|None]])],TypeVar(H, bound=Callable[...,Awaitable[Page|None]])]- Returns:
A decorator that registers its handler and returns it unchanged.
Generic in the handler so a strictly checked service stays strictly checked past the decorator.
A menu entry for a registered page, from what it said about itself.
The text and detail are the page’s own, registered words, so a menu offering the page and a listing naming it cannot drift apart.
- Parameters:
name (
str) – The name the page was registered under.- Return type:
- Returns:
An item carrying the page’s title and detail, leading to its address.
- Raises:
ValueError – If no page is registered under name.
- add_keyword(keyword, address)[source]¶
Let a word be keyed in place of a page number: *MAIN# for *1#.
- Parameters:
keyword (
str)address (
str|PageAddress)
- Return type:
- on_not_found(handler)[source]¶
Register the handler that builds the page for a number naming nothing.
- on_timed_out(handler)[source]¶
Register the handler that builds the frame shown as an idle line drops.
- on_busy(handler)[source]¶
Register the handler that builds the frame shown when the board is full.
- Parameters:
handler (
TypeVar(H, bound=Callable[[PageRequest],Awaitable[Page]]))- Return type:
TypeVar(H, bound=Callable[[PageRequest],Awaitable[Page]])
- on_unresolved(handler)[source]¶
Register a resolver for a keyed target the numbering does not name.
- Parameters:
handler (
TypeVar(H, bound=Callable[[str],PageAddress|None])) – Given the target a reader keyed, it returns the address the target means or None where it means nothing.- Return type:
TypeVar(H, bound=Callable[[str],PageAddress|None])- Returns:
The handler, so it may be used as a decorator.
Tried after the numbering, never before it, so a registered keyword keeps meaning what it was registered to mean.
- async respond(request)[source]¶
Answer a request, through the middleware and then the router.
- Parameters:
request (
PageRequest) – The request to answer.- Return type:
- Returns:
The page built for it, or None where no route matches its address.
- params_for(address)[source]¶
Read the fields a page number’s route captured, or None.
- Parameters:
address (
PageAddress) – The page number to read.- Return type:
- Returns:
The captured fields by name, the same reading that served the page, or None where no route matches.
- async not_found(request, target)[source]¶
Build the page shown for a target that names nothing here.
- Parameters:
request (
PageRequest) – The page the reader is on, which they stay on.target (
str) – What they keyed that led nowhere.
- Return type:
- Returns:
The page an on_not_found handler builds, or the framework’s own notice. A notice rather than silence, which on a service that answers slowly is not distinguishable from a line fault.
- async timed_out(request, frame_index)[source]¶
Build the frame shown as an idle line is released for want of a reply.
- Parameters:
request (
PageRequest) – The page the reader was on.frame_index (
int) – Which frame of it they were on.
- Return type:
- Returns:
The page an on_timed_out handler builds, or the framework’s own. A whole frame, not a line over what was showing: a message overprinting a frame is hard to pick out from it.
- async busy(request)[source]¶
Build the frame shown to a caller the board has no room for.
- Parameters:
request (
PageRequest) – The page the caller would have arrived on.- Return type:
- Returns:
The page an on_busy handler builds, or the framework’s own. A whole frame and then the line drops: a busy signal a caller can read, rather than a line that opens and dies without a word.
- async failed(request, error)[source]¶
Build the frame shown when a handler raises building a page that exists.
- Parameters:
request (
PageRequest) – The page the reader asked for.error (
Exception) – What the handler raised, for an on_failed handler that reads it. The framework’s own notice does not.
- Return type:
- Returns:
The page an on_failed handler builds, or the framework’s own. Distinct from not_found: that number names nothing, this one names a page the service could not build.
- address_for(name, **params)[source]¶
The address a named route answers, built from its own pattern.
- Parameters:
- Return type:
- title_for(address)[source]¶
Read the title a page was registered with, as registered, or None.
- Parameters:
address (
PageAddress) – The page number to read the title of.- Return type:
- Returns:
The registered title, or None where the address is unrouted or its route was given no title. Not upper-cased: the layout upper-cases it when it draws a heading, and a listing reads it as it is.
- label_for(address)[source]¶
Name a page for a list of pages a reader has visited.
- Parameters:
address (
PageAddress) – The page number to label.- Return type:
- Returns:
The route’s label where it set one, so a page whose number carried a field reads as the one page rather than the kind of page: a str.format template filled from the captured fields, or a callable passed them by keyword. With no label, the registered title followed by the field values, or the keyed number where the address is unrouted or untitled.
- property state: State¶
What the service holds while running, written by the lifespan.
A page is given the read-only view of this as request.state; the writable store is the lifespan’s, which sets a StateKey on it before yielding.
- async fetch(target, *, neighbours=None, session=None, history=())[source]¶
Fetch a page by its number, in process, as a session would request it.
A request carries more than the number: what the service holds, and the service itself. Assembling one by hand at each call site is easy to get subtly wrong, so the assembly lives here once. render_page uses it, and so should anything else that wants a page without a socket.
- Parameters:
target (
str|PageAddress)neighbours (
Neighbours|None)history (
tuple[PageAddress,...])
- Return type:
- class sextile.StateKey(name)[source]¶
Bases:
Generic[_T]A typed key into a service’s state.
- name¶
What the key is called, used in repr and in the error a missing key raises. It is not how the value is stored: the key’s own identity is, so two keys of the same name are distinct.
- Parameters:
name (
str)
- exception sextile.UnknownPageError[source]¶
Bases:
ValueErrorA request that names no page here.
- sextile.draw_form(frame, form)[source]¶
Redraw a form’s own rows onto a frame, leaving the rest of it alone.
Its rows are blanked first, so what a shorter suggestion vacates does not stay behind under a digit that now means something else.
- sextile.farewell_page(request, title, *lines, hang_up=True)[source]¶
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.
- Parameters:
request (
PageRequest) – The request this page answers.title (
str) – The heading, drawn in cyan on the first row.*lines (
str) – What to say, one string a row, beginning two rows below the title.hang_up (
bool) – Whether the line drops once shown. False for the involuntary parting, where the session drops the line itself.
- Return type:
- Returns:
A page of a single frame, offering no keys.
- sextile.fixed_integer(width)[source]¶
A whole number written in exactly
widthdigits, padded with zeros.The leading-zero rule inverts here. A variable-width field refuses a leading zero because 0042 and 42 would be two numbers for one page; a fixed-width field requires the padding, because with the width settled each value again has one spelling.
Fixed widths are what let fields sit next to one another. A page number has no separators, so two fields can only be told apart if all but the last is of a width known in advance.
- sextile.keyed(address)[source]¶
A page number as a reader keys it: *91#.
Defined here rather than in each place that shows a page number, so a service does not spell *91# differently in different places. Takes a keyword as readily as a number: *MAIN# is keyed the same way.
- Parameters:
address (
PageAddress|str)- Return type:
A menu: a list of choices, nine to a frame, each numbered 1-9.
- Parameters:
request (
PageRequest) – The request this page answers.items (
Sequence[Entry]) – The choices, anything satisfying Entry; MenuItem is the ready-made one. A service with a richer type of its own passes it directly.title (
str|None) – The header, or None to take the registered title of the page.home (
PageAddress|Shortcut|None|DefaultHome) – Where 0 leads; unset takes request.app.index, None offers no way home.preamble (
Sequence[str]) – Lines shown once on the first frame, above the entries, with a blank row between.empty (
str|Sequence[str] |None) – Said in place of the entries where there are none, so an empty menu explains itself rather than looking like a fault. A string is one row, a sequence its rows as given. None leaves the frame blank.shortcuts (
Sequence[Shortcut]) – Keys offered on every frame besides the digits and 0.neighbours (
Neighbours|None) – The pages either side of this one, wiring A/D; pass request.neighbours.item_noun (
str) – What A and D move between, as the footer names it.
- Return type:
- Returns:
The page, of as many frames as the entries needed.
- sextile.notice_page(request, *lines, title=None, home=<sextile.layout.page.DefaultHome object>, numbered=True, shortcuts=(), hang_up=False, furniture=(Header(colour=<Colour.CYAN: 6>, numbered=<Colour.WHITE: 7>, edge=<Edge.TOP: 1>, rows=1, _ATTRIBUTES=2, _GAP=1), Rule(edge=<Edge.TOP: 1>, colour=<Colour.BLUE: 4>, rows=1), Rule(edge=<Edge.BOTTOM: 2>, colour=<Colour.BLUE: 4>, rows=1), Footer(colour=<Colour.YELLOW: 3>, edge=<Edge.BOTTOM: 2>, rows=1)))[source]¶
A page that simply says something.
- Parameters:
request (
PageRequest) – The request this page answers.*lines (
str) – What to say, one string a row; empty strings leave a blank row.title (
str|None) – The header, or None to take the registered title of the page.home (
PageAddress|Shortcut|None|DefaultHome) – Where 0 leads; unset takes request.app.index, None offers no way home.numbered (
bool) – Whether the header shows the page number. False for a notice answering a request that names no page of its own.shortcuts (
Sequence[Shortcut]) – Keys offered on every frame besides the digits and 0.hang_up (
bool) – Whether the line drops once the page has been shown.furniture (
Sequence[Furnishing]) – 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.
- Return type:
- Returns:
The page, of as many frames as the lines needed.
- sextile.prose_page(request, *paragraphs, title=None, home=<sextile.layout.page.DefaultHome object>, shortcuts=())[source]¶
A page of running text, the line breaks left to the framework.
- Parameters:
request (
PageRequest) – The request this page answers.*paragraphs (
str) – 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 (
str|None) – The header, or None to take the registered title of the page.home (
PageAddress|Shortcut|None|DefaultHome) – Where 0 leads; unset takes request.app.index, None offers no way home.shortcuts (
Sequence[Shortcut]) – Keys offered on every frame besides the digits and 0.
- Return type:
- Returns:
The page, of as many frames as the text needed.
- sextile.standard_pages(*, history=None, contents=None, keywords=None, recent=None, popular=None, callers=None, visits=None)[source]¶
Routes for the framework’s own pages, at whatever numbers a service gives.
Each argument is the page number to route that page at, or None to leave it out. The route carries the framework’s own title, detail and keywords, so a service names the page once, by its number, and retypes nothing.
- Parameters:
- Return type:
- Returns:
A PageRoute for each page given a number, in the order named above.
- Raises:
ValueError – If recent, popular or callers is given without visits to read the log from.
- sextile.title_page(request, *, draw, next_page=None, shortcuts=())[source]¶
The whole-frame title a service opens on, drawn by the caller.
- Parameters:
request (
PageRequest) – The request this page answers.draw (
Callable[[Canvas],None]) – Draws the frame, given the canvas; it has every row, from 0.next_page (
PageAddress|None) – 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 (
Sequence[Shortcut]) – Keys offered on the frame besides #.
- Returns:
a masthead is the whole frame, and there is no index to send a reader back to that they are not already at.
- Return type:
- sextile.transliterate(text)[source]¶
Reduce text to characters the G0 set can display.
Whitespace other than a plain space becomes a space: line structure belongs to the block model, and by the time a run of text arrives here it should already be a single line.
Custom, Flow, OnOneFrame, PageLayout and Shortcut are documented on the
sextile.layout page, and Form and TypeAhead on the
sextile.forms page — their home modules — rather than a second time
here.
Handler is a type alias whose home module, sextile.routing, has no reference
page of its own, so it is documented here:
- type sextile.routing.Handler¶
A page handler:
async (request: PageRequest) -> Page | None.Nonerather than a page means there is no such page, which the session shows differently from one it could not build.