Antra

Standardbibliothek

Antra::Console

import Antra::Console

High-level interactive console I/O for shell scripting.

Antra::Console provides ask / confirm / menu on top of Antra::IO (line and key input) and Antra::Terminal (colours). For raw ANSI sequences use Antra::Terminal; for raw line/byte I/O use Antra::IO.

All entry points run against a session — a %ConsoleIO{reader, writer, is_tty} value. The default session wraps the real stdin/stdout; tests (or custom drivers) can pass their own via the io: option, which makes the package fully testable without a TTY.

import Antra::Console as Console
import Antra::Validator as V

val name = Console.ask("Name?", [default: "anon"]).^
val port = Console.ask_int("Port?", [validator: V.value_range(1, 65535)]).^

Interaktive Konsolen-Ein-/Ausgabe für Shell-Skripte: getippte Eingaben abfragen (ask), Ja/Nein-Fragen stellen (confirm) und aus einer Liste auswählen (menu) — mit Defaults, Validierung, Re-Prompt bei Fehleingabe und optionaler Passwort-Eingabe.

Import#

import Antra::Console as Console

Antra::Console sitzt auf Antra::IO (Zeilen-/Tasteneingabe) und Antra::Terminal (Farben). Für rohe ANSI-Sequenzen nimm Antra::Terminal, für rohe Zeilen-/Byte-I/O Antra::IO; oft kombiniert man Console mit Antra::Validator für die validator:-Option.

Überblick / Wann benutzen#

  • ask — eine Zeile Text einlesen, optional mit Default, Validierung und Typ-Coercion (parse:). Typisierte Kurzformen: ask_int, ask_num, ask_decimal.
  • confirm — eine Ja/Nein-Frage mit [Y/n]/[y/N]-Hinweis.
  • menu — eine nummerierte Auswahlliste; gibt das gewählte Element zurück (nicht den Index).

Jeder Aufruf gibt ein Result zurück; mit dem postfixen .! entpackst du es direkt und wirfst bei %Err (z.B. Console.ask("Name?").!). Für nicht-interaktive Konfiguration (CLI-Argumente, Config-Dateien) ist Antra::Arg der passendere Baustein — dieselben Antra::Validator-Bausteine funktionieren dort wie hier.

Konzepte#

Session (%ConsoleIO)#

Alle Einstiegspunkte laufen gegen eine Session — %ConsoleIO{reader, writer, is_tty}. Ohne io:-Option wird eine Default-Session auf die echte stdin/stdout verwendet. Tests oder eigene Treiber können über die io:-Option eine eigene Session übergeben, wodurch das Paket komplett ohne TTY testbar ist.

Re-Prompt und Defaults#

Bei leerer Eingabe greift default:, falls gesetzt (der Default wird nicht erneut validiert). Ohne Default und mit required: true (Voreinstellung) wird die Frage nach einer Fehlermeldung erneut gestellt. Ein validator, der %Err[msg] liefert, zeigt msg und fragt erneut.

Validierung und parse:#

validator arbeitet im String-Bereich (fn(String) -> Result[String]), damit ask einheitlich Result[String] liefert und Validator.all/any komponierbar bleiben. Wer einen echten Wert will, nutzt parse: (läuft nach erfolgreicher Validierung und prägt den Rückgabetyp, Result[T]) oder die typisierten Kurzformen. Ein %Err aus parse: löst ebenfalls einen Re-Prompt aus. Dieselbe validator:-Konvention gilt auch bei Antra::Arg — eine Regel, überall.

Prompt-Färbung: color#

Die color:-Option (bei ask, confirm, menu und den typisierten Shortcuts) färbt den Prompt-Text mit einem Antra::Terminal-Style (:cyan, [:bold, :cyan], …). Sie greift nur auf einem TTY; auf Nicht-TTY-Ausgabe (Pipe/Redirect) bleibt der Prompt plain, damit keine ANSI-Codes in Logs oder Dateien landen. Auf einem TTY wird zusätzlich der [default]/[Y/n]-Hinweis gedimmt. Für volle Kontrolle über das Format bleibt prompt_fn die Option, die einen beliebigen (auch gefärbten) String liefert.

Abbrechen mit Ctrl-C#

Auf einem TTY ist jeder Console-Read immer abbrechbar: ein Ctrl-C während der Eingabe liefert %Err[%Error{kind: :cancelled, message: "cancelled"}], statt den Prozess zu beenden — die Zeilenbearbeitung bleibt normal (canonical mode). Das gilt für ask, confirm, menu, die typisierten Shortcuts sowie die hidden/mask-Pfade. Aufrufer können gezielt darauf matchen:

match Console.ask("Name?") {
    %Ok[name]              -> println("Hallo $name")
    %Err[%Error{kind: :cancelled, ..}] -> println("Abgebrochen.")
    %Err[e]                -> println("Fehler: ${e.message}")
}

Wird das %Err per .^ bis in den Root-Kontext propagiert, gibt der Interpreter ERROR: cancelled auf stderr aus und endet mit Exit 1. Auf Nicht-TTY-Eingabe (Pipe/Skript) gibt es kein Ctrl-C, also auch keinen Abbruch.

Verdeckte Eingabe: hidden und mask#

Für Passwörter auf einem TTY:

  • hidden: true — Terminal-Echo komplett aus; es wird gar nichts angezeigt (wie bei sudo, verrät nicht einmal die Länge).
  • mask: "*" — echot pro Zeichen die Maske statt der echten Eingabe; Backspace/Delete löschen sichtbar, Enter beendet, UTF-8 zählt als ein Zeichen. mask impliziert hidden (man muss hidden nicht zusätzlich setzen).

Beide greifen nur auf einem TTY. Auf Nicht-TTY-stdin (Pipe/Skript) fällt die Eingabe auf einen normalen Zeilen-Read zurück, damit gescriptete Läufe funktionieren.

API#

ask(prompt) -> Result · ask(prompt, opts) -> Result#

Liest eine Zeile ein. Liefert %Ok[String] (bzw. %Ok[T], wenn parse: gesetzt ist) oder %Err[%Error{...}].

Optionen (opts ist ein Keyword-Array):

OptionDefaultBedeutung
default:undefbei leerer Eingabe zurückgegeben und im Prompt gezeigt
hidden:falseEingabe gar nicht echoen (Passwörter)
mask:undefString wie "*" pro Zeichen echoen (impliziert hidden)
required:truefalse erlaubt leere Eingabe → ""
validator:undeffn(String) -> Result[String]; bei %Err Re-Prompt
parse:undeffn(String) -> Result[T]; nach Validierung; prägt Rückgabe
trim:trueWhitespace vor Validierung/Rückgabe strippen
prompt_fn:undeffn(prompt, default, hidden) -> String; eigenes Prompt-Format
color:undefTerminal-Style (:cyan, [:bold, :cyan]) für den Prompt (nur TTY)
io:undefConsole-Session; Default = reale stdin/stdout
import Antra::Console as Console
import Antra::Validator as V

val name  = Console.ask("Your name?").^
val email = Console.ask("Email?", [validator: V.email()]).^
val token = Console.ask("Token?", [mask: "*", required: true]).^

ask_int(prompt) -> Result · ask_int(prompt, opts) -> Result#

Fragt eine ganze Zahl ab und liefert %Ok[Number]. Kombiniert intern den integer-Validator mit einer Zahl-Coercion; ein zusätzlicher validator: wird davor gehängt. Optionen: default, required, validator, prompt_fn, io.

val count = Console.ask_int("Count?", [
    default: "1",
    validator: V.value_range(1, 20)
]).^

ask_num(prompt) -> Result · ask_num(prompt, opts) -> Result#

Wie ask_int, akzeptiert aber Ganz- und Dezimalzahlen. Liefert %Ok[Number].

ask_decimal(prompt) -> Result · ask_decimal(prompt, opts) -> Result#

Fragt eine Dezimalzahl ab und liefert %Ok[Rat].

confirm(prompt) -> Result · confirm(prompt, opts) -> Result#

Stellt eine Ja/Nein-Frage und liefert %Ok[Bool] oder %Err[%Error{...}]. Akzeptiert y/yes/n/no (Groß-/Kleinschreibung egal). Eine leere Antwort nutzt default, falls gesetzt; der Default wird als [Y/n] bzw. [y/N] hervorgehoben. Optionen: default (ein Bool), prompt_fn, color, io.

if Console.confirm("Continue?", [default: false]).^ {
    println("los")
} else {
    println("abgebrochen")
}

Zeigt eine nummerierte Auswahl und liefert das gewählte Element (im Originaltyp, nicht den Index) als %Ok[T] oder %Err[%Error{...}].

Optionen:

OptionDefaultBedeutung
default:undefein Element aus options, gewählt bei leerer Eingabe
format:strfn(item) -> String zur Anzeige jedes Eintrags
limit:undefhöchstens N Einträge zeigen
from_start:truemit limit: false zeigt die letzten N statt der ersten
prompt_fn:undefeigenes Prompt-Format
color:undefTerminal-Style für die Auswahlzeile (nur TTY)
io:undefConsole-Session
val env = Console.menu(["dev", "staging", "prod"], [default: "dev"]).^
println("Ziel: $env")

Fehler#

  • Alle Funktionen liefern %Err[%Error{...}] statt zu werfen; mit .! entpackst du und wirfst den %Error (fängt man mit try/catch), mit .^ propagierst du ihn stattdessen als Wert.
  • Leere Eingabe bei required: true ohne default und ohne verfügbare weitere Eingabe (EOF/Nicht-TTY) endet in %Err mit einer „a value is required"-Meldung — interaktive Skripte, die ohne stdin laufen, sollten daher Defaults setzen oder required: false verwenden.
  • Ein am Top-Level nicht abgefangenes %Err[...] aus .^ gibt ERROR: <message> auf stderr aus und beendet mit Exit 1.

Siehe auch#

Funktionen#

fn ask(prompt)
fn ask(prompt, [
        default:   = undef,
        hidden:    = false,
        mask:      = undef,
        required:  = true,
        validator: = undef,
        parse:     = undef,
        trim:      = true,
        prompt_fn: = undef,
        color:     = undef,
        io:        = undef
    ])

Asks for a line of input. Returns %Ok[String] (or %Ok[T] when parse: is set), or %Err[%Error{...}].

Options: default, hidden, mask, required, validator, parse, trim, prompt_fn, color, io. mask (a string like "*") implies hidden and echoes the mask per character on a tty. color (a Terminal style like :cyan or [:bold, :cyan]) paints the prompt on a tty; the [default] hint is dimmed there too. On a tty, Ctrl-C during the read always yields %Err[%Error{kind: :cancelled}] instead of killing the process. See the package doc and the Console spec.

Example:

Console.ask("Your name?").^
Console.ask("Port?", [default: "8080", validator: V.integer()]).^
fn ask_decimal(prompt)
fn ask_decimal(prompt, [default: = undef, required: = true, validator: = undef, prompt_fn: = undef, color: = undef, io: = undef])

Asks for a decimal number. Returns %Ok[Number].

fn ask_int(prompt)
fn ask_int(prompt, [default: = undef, required: = true, validator: = undef, prompt_fn: = undef, color: = undef, io: = undef])

Asks for a whole number. Returns %Ok[Number]. Accepts the same options as ask (a validator is combined with the built-in integer check).

Example:

Console.ask_int("Count?", [default: "1"]).^
fn ask_num(prompt)
fn ask_num(prompt, [default: = undef, required: = true, validator: = undef, prompt_fn: = undef, color: = undef, io: = undef])

Asks for an integer or decimal number. Returns %Ok[Number].

fn confirm(prompt)
fn confirm(prompt, [default: = undef, prompt_fn: = undef, color: = undef, io: = undef])

Asks a yes/no question. Returns %Ok[Bool], or %Err[%Error{...}].

Accepts y/yes/n/no (case-insensitive). An empty answer uses default if set; the default is highlighted as [Y/n] or [y/N].

Options: default (a Bool), prompt_fn, color, io. On a tty, Ctrl-C yields %Err[%Error{kind: :cancelled}].

Example:

if Console.confirm("Continue?", [default: false]).^ { ... }

Quelle dieser Seite