plesty.sdk.tui
A checkable list for the terminal, with no dependency and no assumptions.
plesty init field-test asks a developer to choose which of a device’s
operations are safe to run unsupervised. Typing comma-separated numbers works
and reads badly: the choices scroll away above the prompt, there is no
feedback until the whole answer is submitted, and a mistyped digit selects
something the developer never looked at — on a question whose wrong answer
moves a stage.
So the list is checkable. Arrow keys move, space toggles, Enter confirms, and what is selected is visible the whole time.
Three constraints shaped this:
No new dependency. A TUI library for one command is a large amount of surface for a small amount of interaction, and every dependency is another thing gate 10 audits and someone upgrades.
Every platform a module is developed or run on. The instrument PC is
often Windows; development and CI are Linux; some benches are macOS. Console
key reading is the one part that genuinely differs — Windows reads through
msvcrt, Linux and macOS put the terminal into raw mode through termios
— so it is isolated in read_key() and nothing else has to care.
A pipe is not a terminal. Scripted runs, CI, and anything reading from a
file have no keyboard. interactive() says so and the caller falls back
to a prompt, rather than this blocking forever on a read that never returns.
Colours follow the PLESTY palette: cyan for what is being asked, mint for what is chosen, amber for a consequence, dim for detail.
Attributes
Classes
One selectable line. |
Functions
|
Frame body under a bold title, so it is read rather than scrolled past. |
|
Return |
|
Block for one keypress and name it. |
|
Read one key through the Windows console API. |
|
Read one key from a POSIX terminal put briefly into raw mode. |
|
Name the sequence that follows an Escape already read. |
|
Read one byte from descriptor, or |
|
Name a plain character key. |
|
Draw a checkable list and return what was selected. |
|
Select or deselect the item under the cursor. |
|
Redraw the list in place and return how many lines it occupies. |
|
Erase lines previously drawn, so the list redraws in place. |
Module Contents
- plesty.sdk.tui.ASK = 'bright_cyan'
- plesty.sdk.tui.CHOSEN = 'bright_green'
- plesty.sdk.tui.WARN = 'yellow'
- plesty.sdk.tui._LEGEND = '↑↓ move space select a all n none enter confirm'
- plesty.sdk.tui._SEQUENCE_WAIT = 0.05
- class plesty.sdk.tui.Choice
One selectable line.
- Variables:
name – The value returned when chosen.
summary – What it is, shown under the name in dim text. Choosing from bare names is how the wrong operation gets picked.
- name: str
- summary: str = ''
- selected: bool = False
- plesty.sdk.tui.panel(title: str, body: str, colour: str = ASK) str
Frame body under a bold title, so it is read rather than scrolled past.
The interview ends with the only text that tells a developer what to actually do next, and it arrived as one more paragraph after twenty prompts. A frame is the cheapest way to say “this part is not more interview”.
Prose is wrapped to the frame; an indented line is left alone, because those are commands to copy and breaking one makes it not run. The frame widens for them instead, up to the terminal.
- Parameters:
title (str) – Shown bold in the top rule.
body (str) – The text to frame.
colour (str) – Colour of the frame and title.
- Returns:
The framed block, ready to echo.
- Return type:
str
- plesty.sdk.tui.interactive() bool
Return
Truewhen there is a keyboard and a screen to draw on.A scripted run pipes its answers in, and a list that waits for a keypress would hang there forever. Callers use this to fall back to a prompt.
- Return type:
bool
- plesty.sdk.tui.read_key() str
Block for one keypress and name it.
- Returns:
up,down,space,enter,escapefor a lone Escape,otherfor any other control sequence, or the character typed, lowercased. Ctrl-C raisesKeyboardInterruptas it would at a normal prompt — a developer who wants out of a question about laser operations should not have to hunt for the exit.- Raises:
KeyboardInterrupt – On Ctrl-C.
- Return type:
str
- plesty.sdk.tui._read_key_windows() str
Read one key through the Windows console API.
Arrow keys arrive as two reads: a
0x00or0xe0prefix, then a code.The body sits under a
sys.platformtest rather than starting with a bare import.msvcrtexists only on Windows, so a type checker running anywhere else reports every one of its attributes as missing; this is the form mypy narrows on, which keeps the module checkable on the machine it is written on without silencing the checker on the machine it runs on. A genuine mistake in one of these calls is still reported there.- Raises:
RuntimeError – If called off Windows, which only a caller bypassing
read_key()could arrange.- Return type:
str
- plesty.sdk.tui._read_key_posix() str
Read one key from a POSIX terminal put briefly into raw mode.
Arrow keys arrive as an escape sequence,
ESC [ A, and asESC O Afrom a terminal in application-cursor mode. The bytes are taken straight from the file descriptor, not throughsys.stdin: the text wrapper would pull the whole sequence into its own buffer on the first read, so aselecton the descriptor would then see nothing waiting and report a lone Escape — which is how one arrow press used to answer the question (sdk#32). A lone Escape is still told apart by whether more input follows within a few milliseconds.- Return type:
str
- plesty.sdk.tui._decode_escape(descriptor: int) str
Name the sequence that follows an Escape already read.
Every byte of a control sequence is consumed, whether or not it names a key this list uses. Leaving a tail behind is worse than misreading one press: the
AclosingESC [ 1 ; 5 Awould be read next as the select-all key.- Parameters:
descriptor (int) – The terminal, in raw mode.
- Returns:
up,down,escapefor a lone Escape, orother.- Return type:
str
- plesty.sdk.tui._next_byte(descriptor: int, timeout: float | None = None) str
Read one byte from descriptor, or
""if none arrives in timeout.- Parameters:
descriptor (int) – The terminal, in raw mode.
timeout (float | None) – Seconds to wait, or
Noneto block.
- Return type:
str
- plesty.sdk.tui._name_key(char: str) str
Name a plain character key.
- Parameters:
char (str) – The character read.
- Raises:
KeyboardInterrupt – When it is Ctrl-C.
- Return type:
str
- plesty.sdk.tui.checkbox(choices: collections.abc.Sequence[Choice], limit: int | None = None, single: bool = False, when_empty: str = 'nothing selected') list[str]
Draw a checkable list and return what was selected.
Choices marked
selectedstart ticked, and Enter confirms whatever is ticked when it is pressed. The cheapest keypress therefore gives the most conservative answer, which is the whole safety property: this asks which operations may be run unsupervised, and a list arriving pre-ticked would invite confirming rather than deciding.aselects everything, but it has to be typed.- Parameters:
choices (collections.abc.Sequence[Choice]) – The lines to offer.
limit (int | None) – Most items selectable, or
Nonefor no limit. Selecting past the limit drops the oldest choice rather than refusing the new one, so the list never becomes unresponsive.single (bool) – Radio behaviour — one selection at a time.
when_empty (str) – What an empty selection means for this question, shown while it is empty. Empty is restrictive on one question and permissive on another — no operation is called, against every setting is written back — and a developer cannot be expected to remember which is which.
- Returns:
The chosen names, in the order they appear in choices.
- Return type:
list[str]
- plesty.sdk.tui._toggle(selected: list[int], cursor: int, limit: int | None, single: bool) None
Select or deselect the item under the cursor.
- Parameters:
selected (list[int]) – Indices currently selected, mutated in place.
cursor (int) – Index under the cursor.
limit (int | None) – Most items selectable.
single (bool) – Radio behaviour.
- Return type:
None
- plesty.sdk.tui._draw(choices: collections.abc.Sequence[Choice], selected: collections.abc.Sequence[int], cursor: int, previous: int, limit: int | None, single: bool, when_empty: str = 'nothing selected') int
Redraw the list in place and return how many lines it occupies.
- Parameters:
choices (collections.abc.Sequence[Choice]) – The lines offered.
selected (collections.abc.Sequence[int]) – Indices currently selected.
cursor (int) – Index under the cursor.
previous (int) – Lines drawn last time, erased before redrawing.
limit (int | None) – Most items selectable, shown in the counter.
single (bool) – Radio behaviour, which changes the counter’s wording.
when_empty (str) – What an empty selection means for this question.
- Returns:
The number of lines drawn.
- Return type:
int
- plesty.sdk.tui._clear(lines: int) None
Erase lines previously drawn, so the list redraws in place.
- Parameters:
lines (int) – How many lines to erase.
- Return type:
None