#!/usr/bin/env dub
/+ dub.sdl:
name "prompts"
dependency "sparkles:core-cli" path="../../.."
targetPath "build"
// Optimised, assertions live, `debug {}` blocks out — the build every nix
// artifact uses. Neither `debug` (which compiles those blocks in) nor
// `release` (which deletes assert *expressions*, side effects included).
buildType "checked" {
buildOptions "optimize" "inline" "debugInfo"
}
+/
// ci: build-only
// Line-based interactive prompts (`sparkles.core_cli.prompts`): a numbered
// `select` (default marked, re-prompt on invalid input), a y/N `confirm`, and
// a validated `textInput` — each carrying a `PromptPolicy` so `--auto` runs
// and piped stdin resolve uniformly (take the default / fail) instead of every
// app hand-rolling it. Interactive, so `ci` only builds this; run it yourself.
module (module) prompts_exampleprompts_example;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) prompts_example.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, (alias template) prompts_example.writeln = std.stdio.writeln(T...)(T args)Equivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln;
import (package) sparklessparkles.(package) sparkles.core_clicore_cli.(module) sparkles.core_cli.promptsBlocking, line-based interactive prompts: a numbered select, a yes/no
confirm, and a validated free-text textInput.
Every prompt takes a PromptPolicy so non-interactive callers (--auto
flags, piped stdin) are handled uniformly instead of per call site: interactive
asks (re-prompting on invalid input), takeDefault resolves silently to the
default, and fail returns an error. I/O goes through an injectable
PromptIo pair, so prompts are unit-testable with canned input; EOF on
stdin is an error, never an accidental default.
Line-based by default (works over any pipe/ssh, and is what confirm/
textInput always use); select additionally layers a cursor-driven
mode on top when PromptIo.beginKeySession is available (a real terminal —
see stdioKeySession), falling back to the
numbered flow otherwise.
prompts;
import (package) sparklessparkles.(package) sparkles.basebase.(module) sparkles.base.term_capsTerminal capability probing: the synchronous size query (terminalSize),
tty and color detection (detectTermCaps), and resize notifications
(setTermWindowSizeHandler).
This is the single place the "what can this terminal do" decision is made;
renderers stay pure producers that take explicit widths/flags. It lives in
sparkles:base rather than a UI package because it is an environment query,
not a presentation concern — a logger, a CLI tool and a full-screen UI all need
it, and none of them should pull in a UI stack to ask.
TermSize is deliberately a plain POD rather than a
Vector specialization: base sits below
sparkles:math, and a capability snapshot never does vector arithmetic. The
terminal's geometry types — positions you add offsets to — live in
sparkles:tui (TermPosition), which is free to specialize Vector.
term_caps : (alias) prompts_example.isTerminal = bool sparkles.base.term_caps.isTerminal(sparkles.base.term_caps.StdStream stream = StdStream.stdout) nothrow @nogc @trustedIs stream attached to a terminal? POSIX: isatty; Windows: GetConsoleMode
succeeds (it fails when the handle is redirected — the non-tty check).
isTerminal, (enum) sparkles.base.term_caps.StdStreamA standard stream, for tty queries.
StdStream;
import (package) sparklessparkles.(package) sparkles.uiui.(package) sparkles.ui.componentscomponents.(module) sparkles.ui.components.themeTheme layer: one border-charset selector shared by every framed component
(drawBox / drawHeader / drawTable), the status-glyph vocabulary with ASCII
fallbacks, semantic styles, and a Theme snapshot derived from
TermCaps.
Components stay pure producers — the theme only selects glyphs/styles once at
the edge (typically makeTheme(detectTermCaps()) at app startup); callers thread
the resulting values through the existing BoxProps/TableProps/colored
parameters.
theme : (alias) prompts_example.makeTheme = sparkles.ui.components.theme.Theme sparkles.ui.components.theme.makeTheme(in sparkles.base.term_caps.OutputCapabilities caps) pure nothrow @nogc @safeThe theme for a target's output affordances: ASCII borders + fallback
glyphs without Unicode, colors per the color tier. A TermCaps converts
implicitly (its output is alias this), so ``makeTheme(detectTermCaps())
reads as before; a GUI target passes its own declaration.
makeTheme, (struct) sparkles.ui.components.theme.ThemeA resolved theme snapshot: the color/unicode decisions plus the selected
charsets. Build one with makeTheme and thread it (or its fields)
through the app.
Theme;
import (package) sparklessparkles.(package) sparkles.basebase.(module) sparkles.base.term_capsTerminal capability probing: the synchronous size query (terminalSize),
tty and color detection (detectTermCaps), and resize notifications
(setTermWindowSizeHandler).
This is the single place the "what can this terminal do" decision is made;
renderers stay pure producers that take explicit widths/flags. It lives in
sparkles:base rather than a UI package because it is an environment query,
not a presentation concern — a logger, a CLI tool and a full-screen UI all need
it, and none of them should pull in a UI stack to ask.
TermSize is deliberately a plain POD rather than a
Vector specialization: base sits below
sparkles:math, and a capability snapshot never does vector arithmetic. The
terminal's geometry types — positions you add offsets to — live in
sparkles:tui (TermPosition), which is free to specialize Vector.
term_caps : (alias) prompts_example.detectTermCaps = sparkles.base.term_caps.TermCaps sparkles.base.term_caps.detectTermCaps(bool noColors = false) @safeDetects capabilities and prepares the console.
Colors are on only when stdout is a terminal and neither noColors,
$NO_COLOR, nor TERM=dumb disables them; a non-empty, non-"0"
$CLICOLOR_FORCE forces them on for a non-tty (but never overrides an
explicit disable). colorDepth is the tier
(classifyColorDepth over $COLORTERM/$TERM)
when colors are on, else none. On Windows this additionally sets the output code page to
UTF-8 (so ✓ ✗ ⚙ render even without colors) and enables virtual-terminal
processing (so ANSI escapes are interpreted rather than printed literally);
colors stay off when stdout is redirected or VT can't be enabled.
detectTermCaps;
void void D main()main()
{
const (local variable) const(sparkles.ui.components.theme.Theme) themetheme = sparkles.ui.components.theme.Theme sparkles.ui.components.theme.makeTheme(in sparkles.base.term_caps.OutputCapabilities caps) pure nothrow @nogc @safeThe theme for a target's output affordances: ASCII borders + fallback
glyphs without Unicode, colors per the color tier. A TermCaps converts
implicitly (its output is alias this), so ``makeTheme(detectTermCaps())
reads as before; a GUI target passes its own declaration.
makeTheme(sparkles.base.term_caps.TermCaps sparkles.base.term_caps.detectTermCaps(bool noColors = false) @safeDetects capabilities and prepares the console.
Colors are on only when stdout is a terminal and neither noColors,
$NO_COLOR, nor TERM=dumb disables them; a non-empty, non-"0"
$CLICOLOR_FORCE forces them on for a non-tty (but never overrides an
explicit disable). colorDepth is the tier
(classifyColorDepth over $COLORTERM/$TERM)
when colors are on, else none. On Windows this additionally sets the output code page to
UTF-8 (so ✓ ✗ ⚙ render even without colors) and enables virtual-terminal
processing (so ANSI escapes are interpreted rather than printed literally);
colors stay off when stdout is redirected or VT can't be enabled.
detectTermCaps());
// Piped stdin? Resolve every prompt to its default instead of blocking.
const (local variable) const(sparkles.core_cli.prompts.PromptPolicy) policypolicy = bool sparkles.base.term_caps.isTerminal(sparkles.base.term_caps.StdStream stream = StdStream.stdout) nothrow @nogc @trustedIs stream attached to a terminal? POSIX: isatty; Windows: GetConsoleMode
succeeds (it fails when the handle is redirected — the non-tty check).
Examples
The query never throws and is @nogc; the value is environment-dependent.
cast(void) isTerminal();
cast(void) isTerminal(StdStream.stderr);
isTerminal((enum) sparkles.base.term_caps.StdStreamA standard stream, for tty queries.
StdStream.(enum value) sparkles.base.term_caps.StdStream.stdin = 0stdin)
? (enum) sparkles.core_cli.prompts.PromptPolicyHow a prompt resolves when it cannot, or should not, actually ask.
PromptPolicy.(enum value) sparkles.core_cli.prompts.PromptPolicy.interactive = 0Ask on the terminal, re-prompting on invalid input.
interactive
: (enum) sparkles.core_cli.prompts.PromptPolicyHow a prompt resolves when it cannot, or should not, actually ask.
PromptPolicy.(enum value) sparkles.core_cli.prompts.PromptPolicy.takeDefault = 1Do not ask; resolve to the default (e.g. --auto runs).
takeDefault;
auto (local variable) sparkles.core_cli.prompts.PromptIo ioio = sparkles.core_cli.prompts.PromptIo sparkles.core_cli.prompts.stdioPromptIo()The real stdin/stdout PromptIo.
stdioPromptIo();
auto (local variable) expected.Expected!(ulong, string, Abort) bumpbump = expected.Expected!(ulong, string, Abort) sparkles.core_cli.prompts.select(string question, const(sparkles.core_cli.prompts.SelectOption)[] options, ulong defaultIndex, sparkles.core_cli.prompts.PromptPolicy policy, sparkles.core_cli.prompts.PromptIo io, const(sparkles.ui.components.theme.Theme) theme = Theme(false, true, BorderStyle.rounded, StatusGlyphs("\xe2\x9c\x94", "\xe2\x9c\x96", "\xe2\x9a\xa0", "\xe2\x80\xa2", "\xe2\x97\x8b", "\xe2\x97\x90", "\xe2\x94\x84", "\xe2\x80\xa6")), bool wrapAround = true)Ask the user to pick one of options by number. Renders the list (default
marked and painted accent), then loops until a valid number or an empty line
(= the default). Returns the chosen index; errors on EOF or a non-
interactive fail policy.
When io`.beginKeySession` is available (a real terminal), arrow keys move
the highlighted row instead: Up/Down move, Enter confirms, Esc/Ctrl+C/
Ctrl+D cancel. wrapAround`` (on by default) makes Up from the first row
jump to the last (and Down from the last jump to the first); pass false
to clamp at the ends instead.
select("Version bump:", [
(struct) sparkles.core_cli.prompts.SelectOptionOne choice of a select.
SelectOption("patch", "v0.5.0 → v0.5.1"),
(struct) sparkles.core_cli.prompts.SelectOptionOne choice of a select.
SelectOption("minor", "v0.5.0 → v0.6.0 (suggested: 4 feat)"),
(struct) sparkles.core_cli.prompts.SelectOptionOne choice of a select.
SelectOption("major", "v0.5.0 → v1.0.0"),
], 1, (local variable) const(sparkles.core_cli.prompts.PromptPolicy) policypolicy, (local variable) sparkles.core_cli.prompts.PromptIo ioio, (local variable) const(sparkles.ui.components.theme.Theme) themetheme);
if ((local variable) expected.Expected!(ulong, string, Abort) bumpbump.bool expected.Expected!(ulong, string, expected.Abort).hasError!()() const pure nothrow @nogc @property @safeChecks if Expected has error
hasError)
return void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln((local variable) expected.Expected!(ulong, string, Abort) bumpbump.inout(string) expected.Expected!(ulong, string, expected.Abort).error() inout nothrow @nogc @property ref @safeReturns the error value. May only be called when hasValue returns false.
If there is no error value, it calls hook's onAccessEmptyError.
It returns E.init when hook doesn't provide onAccessEmptyError.
error);
void std.stdio.writefln!("picked option %s", ulong)(ulong __param_0) @safeEquivalent to writef(fmt, args, '\n').
writefln!"picked option %s"((local variable) expected.Expected!(ulong, string, Abort) bumpbump.inout(ulong) expected.Expected!(ulong, string, expected.Abort).value!()() inout pure nothrow @nogc @property ref @safeReturns the expected value if there is one.
With default Abort hook, it asserts when there is no value.
It calls hook's onAccessEmptyValue otherwise.
It returns T.init when hook doesn't provide onAccessEmptyValue.
value + 1);
auto (local variable) expected.Expected!(bool, string, Abort) gogo = expected.Expected!(bool, string, Abort) sparkles.core_cli.prompts.confirm(string question, bool defaultYes, sparkles.core_cli.prompts.PromptPolicy policy, sparkles.core_cli.prompts.PromptIo io, const(sparkles.ui.components.theme.Theme) theme = Theme(false, true, BorderStyle.rounded, StatusGlyphs("\xe2\x9c\x94", "\xe2\x9c\x96", "\xe2\x9a\xa0", "\xe2\x80\xa2", "\xe2\x97\x8b", "\xe2\x97\x90", "\xe2\x94\x84", "\xe2\x80\xa6")))Ask a yes/no question (y/yes/n/no, case-insensitive; empty = the
default, shown as [Y/n]/[y/N]). Errors on EOF or the fail policy.
confirm("Push to origin?", defaultYes: true, (local variable) const(sparkles.core_cli.prompts.PromptPolicy) policypolicy, (local variable) sparkles.core_cli.prompts.PromptIo ioio, (local variable) const(sparkles.ui.components.theme.Theme) themetheme);
if ((local variable) expected.Expected!(bool, string, Abort) gogo.bool expected.Expected!(bool, string, expected.Abort).hasError!()() const pure nothrow @nogc @property @safeChecks if Expected has error
hasError)
return void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln((local variable) expected.Expected!(bool, string, Abort) gogo.inout(string) expected.Expected!(bool, string, expected.Abort).error() inout nothrow @nogc @property ref @safeReturns the error value. May only be called when hasValue returns false.
If there is no error value, it calls hook's onAccessEmptyError.
It returns E.init when hook doesn't provide onAccessEmptyError.
error);
void std.stdio.writefln!("confirmed: %s", bool)(bool __param_0) @safeEquivalent to writef(fmt, args, '\n').
writefln!"confirmed: %s"((local variable) expected.Expected!(bool, string, Abort) gogo.inout(bool) expected.Expected!(bool, string, expected.Abort).value!()() inout pure nothrow @nogc @property ref @safeReturns the expected value if there is one.
With default Abort hook, it asserts when there is no value.
It calls hook's onAccessEmptyValue otherwise.
It returns T.init when hook doesn't provide onAccessEmptyValue.
value);
auto (local variable) expected.Expected!(string, string, Abort) namename = expected.Expected!(string, string, Abort) sparkles.core_cli.prompts.textInput(string question, string defaultValue, string delegate(string answer) validate, sparkles.core_cli.prompts.PromptPolicy policy, sparkles.core_cli.prompts.PromptIo io)Ask for free text, re-prompting until validate accepts it (returns null)
or the user submits an empty line with a defaultValue present. validate
returns the error message to show for a rejected answer.
textInput("Release codename", "sparkling",
((alias) object.string = stringstring (parameter) string ss) => (parameter) string ss.(field) ulong string.lengthlength >= 3 ? null : "at least 3 characters, please",
(local variable) const(sparkles.core_cli.prompts.PromptPolicy) policypolicy, (local variable) sparkles.core_cli.prompts.PromptIo ioio);
if ((local variable) expected.Expected!(string, string, Abort) namename.bool expected.Expected!(string, string, expected.Abort).hasError!()() const pure nothrow @nogc @property @safeChecks if Expected has error
hasError)
return void std.stdio.writeln!string(string __param_0) @safeEquivalent to write(args, '\n'). Calling writeln without
arguments is valid and just prints a newline to the standard
output.
Example
Reads stdin and writes it to stdout with an argument
counter.
import std.stdio;
void main()
{
string line;
for (size_t count = 0; (line = readln) !is null; count++)
{
writeln("Input ", count, ": ", line);
}
}
writeln((local variable) expected.Expected!(string, string, Abort) namename.inout(string) expected.Expected!(string, string, expected.Abort).error() inout nothrow @nogc @property ref @safeReturns the error value. May only be called when hasValue returns false.
If there is no error value, it calls hook's onAccessEmptyError.
It returns E.init when hook doesn't provide onAccessEmptyError.
error);
void std.stdio.writefln!("codename: %s", string)(string __param_0) @safeEquivalent to writef(fmt, args, '\n').
writefln!"codename: %s"((local variable) expected.Expected!(string, string, Abort) namename.inout(string) expected.Expected!(string, string, expected.Abort).value!()() inout pure nothrow @nogc @property ref @safeReturns the expected value if there is one.
With default Abort hook, it asserts when there is no value.
It calls hook's onAccessEmptyValue otherwise.
It returns T.init when hook doesn't provide onAccessEmptyValue.
value);
}