Skip to content

Latest commit

 

History

History
950 lines (757 loc) · 28.5 KB

File metadata and controls

950 lines (757 loc) · 28.5 KB

sparkles

A small D library for CLI applications

Base utilities, terminal styling, pretty-printing, UI components, and @nogc support

Early stage (v0.0.1) -- API may change

Default branch status Merged pull requests Share of merged pull requests with green CI Dub version Dub downloads Docs BSL-1.0


Overview

sparkles is a D monorepo of utilities for building command-line applications and supporting libraries. sparkles:base provides allocation-conscious foundation modules with a focus on @safe, @nogc, pure, and nothrow compatibility; sparkles:core-cli builds on it with higher-level CLI tools.

What's Inside

  • Base -- the Buffer family, lifetime helpers, text readers/writers, terminal styling, styled templates, terminal control sequences, and logging
  • Styled Templates -- Apply ANSI styles using D's Interpolated Expression Sequences (IES) with a concise {style text} syntax
  • Pretty Printing -- Colorized, type-aware formatting for any D type via compile-time introspection
  • UI Components -- Tables (spans, alignment, titles, streaming), boxes, headers, trees, meters/progress bars, key-value lists, horizontal layout, and OSC 8 hyperlinks
  • Live Rendering -- Repaint-in-place live regions and task-list checklists with bounded output tails (nix/bazel-style), degrading to a plain transition log when piped
  • Interactive Prompts -- select/confirm/textInput with a uniform non-interactive policy for --auto flags and piped stdin
  • Terminal Capabilities -- One-shot tty/color/unicode/size detection (detectTermCaps) and a theme layer with ASCII fallbacks
  • Semantic Versioning -- SemVer parsing, normalization, and precedence comparison
  • Test Runner -- Parallel unittest runner with compile-time (@ctfe), -betterC (@betterC), WebAssembly (@wasm), and benchmark (@benchmark) modes

Quick Start

Add the package you need to your dub.sdl:

dependency "sparkles:base" version="~>0.0.1"
dependency "sparkles:core-cli" version="~>0.0.1"

Or dub.json:

"dependencies": {
    "sparkles:base": "~>0.0.1",
    "sparkles:core-cli": "~>0.0.1"
}

Modules

Base

sparkles:base contains the shared low-level modules used by the rest of the monorepo: the Buffer family, recycledInstance, recycledErrorInstance, @nogc text parsing/formatting, terminal styling, styled IES rendering, and the CoreLogger logging interface. See the base documentation for the tutorial, how-to guides, and API index.

Versions

sparkles:versions is an ecosystem-aware version library: it parses, compares, and constrains the version strings of many package ecosystems (SemVer, PEP 440/PyPI, Maven, Debian, CalVer, …) and interoperates with pURL and VERS. Each ecosystem is a hand-written struct conforming to a small compile-time concept; cross-scheme comparison does not compile.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_versions"
    dependency "sparkles:versions" version="*"
+/

import std.stdio : writeln;
import sparkles.versions.schemes.semver : SemVer;
import sparkles.versions.operations : satisfies;

void main()
{
    auto current = SemVer.parse("1.2.3").value;
    auto next = SemVer.parse("1.3.0-beta.1").value;
    writeln(current);
    writeln(next > current);

    // Loose parsing accepts a leading `v` and partial versions.
    writeln(SemVer.parseLoose("v1.2").value);

    // Range membership.
    auto range = SemVer.parseNativeRange("^1.2.0").value;
    writeln(current.satisfies(range));
}
1.2.3
true
1.2.0
true

For the full tour — comparing and sorting, ranges, VERS/pURL interop, the eleven shipped schemes, and adding your own — see the versions documentation.

Styled Templates

Apply terminal styles using D's Interpolated Expression Sequences.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_styled_templates"
    dependency "sparkles:base" version="*"
+/

import sparkles.base.styled_template;

void main()
{
    int cpu = 75;
    styledWriteln(i"CPU: {red $(cpu)%} Status: {green OK}");
    styledWriteln(i"{bold.red ERROR:} Connection refused");
    styledWriteln(i"{cyan Outer {bold.underline inner} just cyan}");
    styledWriteln(i"Press {bold.cyan q} to quit, {bold.cyan h} for help");
}
CPU: �[31m75%�[39m Status: �[32mOK�[39m
�[1;31mERROR:�[22;39m Connection refused
�[36mOuter �[1;4minner�[22;24m just cyan�[39m
Press �[1;36mq�[22;39m to quit, �[1;36mh�[22;39m for help

Syntax at a glance:

Syntax Description
{red text} Single style
{bold.red text} Chained styles
{bold outer {red nested}} Nested blocks with inheritance
{red text {~red normal}} Style negation with ~
#{ / #} Escaped literal braces

Pretty Printing

Format D values with syntax highlighting and structural indentation. Supports enums, booleans, strings, numerics, pointers, tuples, associative arrays, arrays, ranges, structs, and classes.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_pretty_printing"
    dependency "sparkles:base" version="*"
+/

import std.stdio : writeln;

import sparkles.base.prettyprint;

struct Server
{
    string name;
    string ip;
    int port;
}

struct Cluster
{
    string name;
    Server[] servers;
    bool active;
}

void main()
{
    auto cluster = Cluster(
        name: "Production",
        servers: [
            Server("web-01", "192.168.1.10", 80),
            Server("web-02", "192.168.1.11", 80),
            Server("db-01", "192.168.1.20", 5432),
        ],
        active: true,
    );

    writeln(prettyPrint(cluster, PrettyPrintOptions!void(colored: false)));
}
Cluster(
  name: "Production",
  servers: [
    Server(name: "web-01", ip: "192.168.1.10", port: 80),
    Server(name: "web-02", ip: "192.168.1.11", port: 80),
    Server(name: "db-01", ip: "192.168.1.20", port: 5432)
  ],
  active: true
)

Options via PrettyPrintOptions:

prettyPrint(value, PrettyPrintOptions!void(
    indentStep: 2,       // spaces per indent level
    maxDepth: 8,         // recursion limit
    maxItems: 32,        // max array/AA items shown
    softMaxWidth: 80,    // single-line threshold
    colored: true,     // ANSI color output
    useOscLinks: false,  // OSC 8 hyperlinks on type names
));

The SourceUriHook template parameter controls the URI scheme for OSC 8 hyperlinks. Use SchemeHook!"code" for VS Code, EditorDetectHook for auto-detection from $EDITOR/$VISUAL, or implement a custom hook via Design by Introspection.

Terminal Styling

ANSI colors and text attributes via stylize and a fluent stylizedTextBuilder. Both work at runtime and at compile time (CTFE).

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_terminal_styling"
    dependency "sparkles:base" version="*"
+/

import std.stdio : writeln;

import sparkles.base.term_style;

void main()
{
    // Runtime styling
    writeln("Error: ".stylize(Style.red) ~ "something went wrong");

    // Compile-time styling via fluent builder
    enum title = "Important".stylizedTextBuilder(true).bold.underline.red;
    writeln(title);
}
�[31mError: �[39msomething went wrong
�[31m�[4m�[1mImportant�[22m�[24m�[39m

UI Components

Tables

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_tables"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.table;

void main()
{
    drawTable([
        ["Name",    "Status",  "Load"],
        ["web-01",  "UP",      "23%"],
        ["web-02",  "UP",      "45%"],
        ["db-01",   "DOWN",    "0%" ],
    ]).writeln;
}
╭────────┬────────┬──────╮
│ Name   │ Status │ Load │
│ web-01 │ UP     │ 23%  │
│ web-02 │ UP     │ 45%  │
│ db-01  │ DOWN   │ 0%   │
╰────────┴────────┴──────╯

Cells can span columns and rows, columns can be aligned (including Align.decimal, which lines a numeric column up on its dot) with per-cell overrides, frames can carry a title/footer like drawBox's, and separators and glyphs are configurable (TableProps / the stylePresets registry) — including headerRows / headerCols for a distinct rule setting off the header rows and the stub (row-header) column. Both the dense Cell[][] form and a sparse Placement[] form are accepted, and drawTableLines / drawTableChunks / the writer overload emit the same bytes lazily for live regions and paced output:

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_table_spans"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : write;

import sparkles.ui.components.table;
import sparkles.base.text.width : Align;

void main()
{
    drawTable([
        [Cell("Quarterly Sales", colSpan: 3)],
        [Cell("Region"), Cell("Q1"), Cell("Q2")],
        [Cell("North"), Cell("1200"), Cell("1350")],
        [Cell("South"), Cell("98"), Cell("110")],
    ], TableProps(
        columnAligns: [Align.left, Align.right, Align.right],
        headerRows: 2, // banner + column-label row
        headerCols: 1, // the Region stub column
    )).write;
}
╭──────────────────────╮
│ Quarterly Sales      │
│ Region ┃   Q1 │   Q2 │
┝━━━━━━━━╋━━━━━━┿━━━━━━┥
│ North  ┃ 1200 │ 1350 │
│ South  ┃   98 │  110 │
╰────────┸──────┴──────╯

Boxes

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_boxes"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.box;

void main()
{
    drawBox(
        ["Build started at 14:32:01",
         "Compiling 42 modules...",
         "Linking executable...",
         "Build completed in 3.2s"],
        "Build Log",
        BoxProps(footer: "Success"),
    ).writeln;
}
╭──╼ Build Log ╾────────────╮
│ Build started at 14:32:01 │
│ Compiling 42 modules...   │
│ Linking executable...     │
│ Build completed in 3.2s   │
╰──╼ Success ╾──────────────╯

Headers

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_headers"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.header;

void main()
{
    // Divider style:  ── Section Title ──
    "Section Title".drawHeader.writeln;

    // Banner style
    "Main Title".drawHeader(HeaderProps(
        style: HeaderStyle.banner,
        lineChar: '',
        width: 40,
    )).writeln;
}
── Section Title ──
════════════════════════════════════════
               Main Title
════════════════════════════════════════

OSC 8 Hyperlinks

Make text clickable in terminal emulators that support OSC 8.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_osc_link"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.osc_link;
import sparkles.base.term_style : Style;

void main()
{
    // Plain clickable link
    writeln(oscLink(text: "Example", uri: "https://example.com"));

    // Styled clickable link (blue text)
    writeln(oscLink(text: "D Language", uri: "https://dlang.org", style: Style.blue));
}
�]8;;https://example.com�Example�]8;;�
�]8;;https://dlang.org��[34mD Language�[39m�]8;;�

Meters & Progress

Proportional bars with eighth-cell precision, plus composed done/total progress lines:

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_meters"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.meter : meter, ProgressBar;

void main()
{
    writeln("|", meter(0.33, 16), "|");
    writeln("|", meter(7, 9, 16), "|");
    writeln(ProgressBar(done: 5, total: 40, barWidth: 16));
}
|█████▎          |
|████████████▌   |
██                5/40

Tree Views

Trees render from flat, pre-ordered (label, depth) nodes — no recursive node objects, so any depth-first walk displays directly (and the guides compose as a table's first column):

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_tree"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.tree : renderTree, TreeNode;

void main()
{
    foreach (line; renderTree([
        TreeNode("apps", 0),
        TreeNode("ci", 1),
        TreeNode("release", 1),
        TreeNode("src", 2),
        TreeNode("libs", 0),
    ]))
        writeln(line);
}
apps
├─ ci
└─ release
   └─ src
libs

Live Task Lists

LiveRegion repaints a block of lines in place at the bottom of normal scrollback (frames wrapped in DEC 2026 synchronized-output markers, completed lines graduating into the scrollback above); TaskReporter drives a checklist through it, with each running task's child-process output streaming into a bounded tail pane (runStreaming). On piped output only the transition log remains — no escape codes. The row renderers are pure and theme-driven:

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_tasklist"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.tasklist : renderTaskList, TaskItem, TaskStatus;
import sparkles.ui.components.theme : Theme;

void main()
{
    // The pure renderer (a real app drives TaskReporter over a LiveRegion —
    // see libs/core-cli/examples/live-tasklist.d for the animated version).
    auto items = [
        TaskItem(label: "fetch dependencies", status: TaskStatus.ok),
        TaskItem(label: "build", status: TaskStatus.running,
            tail: ["compiling module 11", "compiling module 12"]),
        TaskItem(label: "publish", status: TaskStatus.pending),
    ];
    foreach (line; renderTaskList(items, Theme(colors: false)))
        writeln(line);
}
⠋ build
  compiling module 11
  compiling module 12
○ publish

Layout Helpers

kvList renders aligned label/value lines; hjoin zips pre-rendered blocks side by side (padded by visible width, so styled/CJK content lines up):

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_layout"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;

import sparkles.ui.components.box : BoxProps, drawBox;
import sparkles.ui.components.layout : hjoin, kvList;

void main()
{
    auto receipt = kvList([
        ["tag", "v0.6.0 (annotated)"],
        ["pushed", "origin ✔"],
    ]);
    writeln(hjoin([
        drawBox(receipt, "released", BoxProps(footer: "next: publish")),
        "notes:\n2 feats\n1 fix",
    ]));
}
╭──╼ released ╾──────────────╮  notes:
│ tag     v0.6.0 (annotated) │  2 feats
│ pushed  origin ✔           │  1 fix
╰──╼ next: publish ╾─────────╯

Interactive Prompts

Line-based select / confirm / textInput, each with a PromptPolicy so --auto runs and piped stdin resolve to defaults (or fail) uniformly. EOF is an error, never an accidental default:

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_prompts"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writefln;

import sparkles.core_cli.prompts;
import sparkles.base.term_caps : isTerminal, StdStream;

void main()
{
    // Interactive on a terminal; silently takes the defaults when piped
    // (which is how this example runs under CI).
    const policy = isTerminal(StdStream.stdin)
        ? PromptPolicy.interactive : PromptPolicy.takeDefault;
    auto io = stdioPromptIo();

    auto bump = select("Version bump:", [
        SelectOption("patch", "v0.5.0 → v0.5.1"),
        SelectOption("minor", "v0.5.0 → v0.6.0  (suggested)"),
        SelectOption("major", "v0.5.0 → v1.0.0"),
    ], 1, policy, io);
    auto go = confirm("Push to origin?", defaultYes: true, policy, io);
    writefln!"bump=%s push=%s"(bump.value + 1, go.value);
}
bump=2 push=true

Logger

Delta-time-prefixed logging via DeltaTimeLogger, a std.logger.Logger subclass. Each log line shows wall-clock time, elapsed time since start, and delta since the previous entry.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_logger"
    dependency "sparkles:base" version="*"
+/

import std.logger : log, LogLevel;

import sparkles.base.logger : initLogger;

void main()
{
    initLogger(LogLevel.trace);

    log(LogLevel.info, "Listening on port 8080");
    log(LogLevel.warning, "Disk usage above 80%");
    log(LogLevel.error, "Connection to database lost");
}
�[90m[ 23:23:27�[39m | Δt �[33m158.6µs�[39m | Δtᵢ �[33m158.6µs�[39m | �[32mINF�[39m | �[2mreadme_logger.d:15�[22m ]: �[1mListening on port 8080�[22m
�[90m[ 23:23:27�[39m | Δt �[33m302.4µs�[39m | Δtᵢ �[33m143.8µs�[39m | �[33mWRN�[39m | �[2mreadme_logger.d:16�[22m ]: �[1mDisk usage above 80%�[22m
�[90m[ 23:23:27�[39m | Δt �[33m349.3µs�[39m | Δtᵢ �[33m46.8µs�[39m | �[31mERR�[39m | �[2mreadme_logger.d:17�[22m ]: �[1mConnection to database lost�[22m

The colored output uses writeStyled IES for ANSI styling -- log levels are color-coded (green for info, yellow for warnings, red for errors, bold+red for critical/fatal), durations are highlighted, and file locations are dimmed.

Args Parsing

Declare a CLI with @Command / @Option UDAs and call parseCli. Options backed by enums (or carrying allowedValues) support --option=help, --option=?, and the suffix shorthand --option?: the parser short-circuits and emits a per-option help screen listing the valid values.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_args_option_help"
    dependency "sparkles:core-cli" version="*"
+/

import std.stdio : writeln;
import sparkles.core_cli.args;
import sparkles.core_cli.term_unstyle : unstyle;

enum Color { red, green, blue }

@(Command("paint"))
struct Cli
{
    @(Option("c|color", description: "Pick a color."))
    Color color;
}

void main()
{
    auto parsed = parseCli!Cli(["paint", "--color=help"]);
    if (!parsed && parsed.error.isHelp)
        writeln(parsed.error.help.unstyle);
}
-c, --color COLOR
  Pick a color.

Valid values:
  red
  green
  blue

The same screen comes from --color=?, --color?, -c=help, and -c?. For free-form options (strings, integers, paths) it describes the accepted shape instead of enumerating values; for bool options it lists the true / false / yes / no tokens.

Buffers

One @nogc container, four storage policies. The capabilities are the policy, so the alias names what the buffer may do:

Alias Storage Copying
InlineBuffer!(T, N) the inline T[N], never the heap free — plain data, no destructor
UniqueBuffer!(T, N) inline while it fits, then heap disabled — sole owner
SharedBuffer!(T, N) inline while it fits, then heap shares the block, cloned on the next write
HeapBuffer!T heap only as SharedBuffer

Reach for UniqueBuffer by default: one owner means the grow path carries no reference count. SharedBuffer is a claim that copies actually happen.

import sparkles.base.buffer;

@safe pure nothrow @nogc
unittest {
    UniqueBuffer!(char, 64) buf;
    buf ~= "Hello";
    buf ~= ' ';
    buf ~= "World";
    assert(buf[] == "Hello World");
    assert(!buf.onHeap);  // still using inline storage
}

Every policy that can allocate is an output range, so it composes with std.algorithm, prettyPrint, styled templates, and the rest of the library. InlineBuffer is deliberately not one — put promises to accept what it is given, and a fixed buffer cannot — so you write into it with tryWrite, which reports overflow rather than growing.

@nogc Utilities

recycledInstance -- Reuse thread-local static instances for throwing errors in @nogc code:

import sparkles.base.lifetime;

@nogc void validate(int x) {
    if (x < 0)
        throw recycledInstance!Error("value must be non-negative");
}

text_writers -- Write integers, floats, escaped characters, and ANSI codes without GC allocation. Includes writeValue for best-effort @nogc conversion of any type, and writeStyledValue for hook-controlled styled output.

term_unstyle -- Strip ANSI escape sequences from styled text. This lives in sparkles:core-cli.

term_caps -- Query the terminal size (terminalSize) and detect window resizes via SIGWINCH. This lives in sparkles:core-cli.

Test Runner

sparkles:test-runner runs a package's unittests in parallel (add it to configuration "unittest" and use dub test as usual), with marker attributes that opt individual tests into extra environments: @ctfe tests run while the build compiles (a failure is a compile error), @betterC and @wasm tests are additionally extracted and executed without druntime / on wasm32 (--better-c / --wasm), and @benchmark tests are measured with auto-scaling iteration counts (--bench). See the test-runner documentation.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_test_runner"
    dependency "sparkles:test-runner" version="*"
+/
import sparkles.test_runner.attributes : benchmark, betterC, ctfe;
import sparkles.test_runner.bench : blackBox, computeStats;

@("digits.parity")
@betterC @safe pure nothrow @nogc
unittest // runs under `dub test` — and without druntime via `--better-c`
{
    int parity;
    foreach (c; "12345")
        parity ^= c - '0';
    assert(parity == 1);
}

@("digits.parity.ct")
@ctfe @safe pure nothrow @nogc
unittest // runs while the test build compiles; never at runtime
{
    assert((1 ^ 2 ^ 3 ^ 4 ^ 5) == 1);
}

void main()
{
    import std.stdio : writefln;

    // The statistics `--bench` reports, over hand-made ns/iter samples so
    // this example's output is deterministic:
    const stats = computeStats("demo", 1000, [22.0, 18.0, 20.0]);
    writefln!"median=%.0fns/iter min=%.0f max=%.0f over %s samples"(
        stats.nsPerIterMedian, stats.nsPerIterMin, stats.nsPerIterMax,
        stats.samples);

    // blackBox is the optimizer barrier used inside @benchmark tests.
    assert(blackBox(21) * 2 == 42);
}
median=20ns/iter min=18 max=22 over 3 samples

Code Coverage

sparkles:code-instrumentation reads the coverage artifacts five toolchains emit — DMD -cov listings, gcov, LCOV .info, V8 block coverage, and llvm-cov export JSON — into one model. loadCoverage picks the parser, so you never name a format, and reports failure as a value rather than an empty result: "I could not read this" and "this describes nothing" are different answers.

#!/usr/bin/env dub
/+ dub.sdl:
    name "readme_coverage"
    dependency "sparkles:code-instrumentation" version="*"
+/

import std.stdio : writefln, writeln;

import sparkles.code_instrumentation;

void main()
{
    // The shape `dmd -cov` writes: a blank counter for a line that emitted no
    // code, `0000000` for code that never ran, and a trailer naming the
    // source — the only place the source path appears.
    enum listing = "       |module math;\n"
        ~ "      5|    return a + b;\n"
        ~ "0000000|    return a - b;\n"
        ~ "libs/x/src/math.d is 50% covered\n";

    auto report = loadCoverage("build/cov/math.lst", listing);
    if (!report)
    {
        writeln("unreadable at byte ", report.error.offset);
        return;
    }

    // Matched by path, not by position: a report may describe many files, and
    // the artifact's own path is not the source's.
    const file = report.value.findFile("libs/x/src/math.d");
    assert(file !is null);

    const plan = planCoverage(*file);
    writeln(plan.summaryBanner);

    foreach (item; plan.gutterItems)
        writefln("%4s | line %s", item.countText, item.lineNumber);
}
Coverage: 50.0% (1/2 lines covered)
     | line 1
   5 | line 2
   0 | line 3

Examples

Runnable examples are in libs/base/examples/, libs/build-primitives/examples/, and libs/core-cli/examples/:

dub run --single libs/base/examples/logger.d
dub run --single libs/base/examples/prettyprint.d
dub run --single libs/base/examples/text-fields.d
dub run --single libs/base/examples/term-control.d

dub run --single libs/build-primitives/examples/gitignore_listing.d

dub run --single libs/core-cli/examples/styled-template.d
dub run --single libs/core-cli/examples/table.d
dub run --single libs/core-cli/examples/streaming-table.d   # animated; --mode cell|line|race
dub run --single libs/core-cli/examples/table-leaderboard.d # animated, re-sorting live table
dub run --single libs/core-cli/examples/table-bench-ticker.d # animated benchmark results
dub run --single libs/core-cli/examples/box.d
dub run --single libs/core-cli/examples/streaming-box.d     # animated
dub run --single libs/core-cli/examples/header.d
dub run --single libs/core-cli/examples/osc-link.d
dub run --single libs/core-cli/examples/color.d
dub run --single libs/core-cli/examples/theme.d
dub run --single libs/core-cli/examples/meter.d
dub run --single libs/core-cli/examples/tree.d -- [path] # gitignore-aware tree(1) clone
dub run --single libs/core-cli/examples/layout.d
dub run --single libs/core-cli/examples/prompts.d       # interactive
dub run --single libs/core-cli/examples/live-tasklist.d # animated
dub run --single libs/base/examples/term-caps.d

Building & Testing

# Build
dub build :base
dub build :core-cli

# Run all tests
dub run :ci -- --test

# Test a specific sub-package
dub test :base
dub test :core-cli

# Run tests matching a pattern
dub test :base -- -i "Buffer"

# Verbose output with stack traces
dub test :core-cli -- -v

# Special test-runner modes (see docs/libs/test-runner/)
dub test :base -- --bench       # measure @benchmark tests
dub test :base -- --better-c    # run @betterC tests without druntime
dub test :base -- --wasm        # run @wasm tests on wasm32

The project uses a Nix development shell for reproducible builds:

nix develop -c dub build :core-cli
nix run .#ci -- --test

Documentation

Documentation (work in progress) is available at sparkles.petar-kirov.dev.

License

Boost Software License 1.0