flagstaff 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +333 -20
- package/THIRD-PARTY.md +44 -0
- package/dist/box.d.ts +22 -0
- package/dist/box.js +59 -0
- package/dist/boxen.d.ts +56 -0
- package/dist/boxen.js +251 -0
- package/dist/builtins.d.ts +13 -0
- package/dist/builtins.js +19 -0
- package/dist/cli-table3.d.ts +138 -0
- package/dist/cli-table3.js +785 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +126 -0
- package/dist/cursor.d.ts +13 -0
- package/dist/cursor.js +49 -0
- package/dist/import.d.ts +50 -0
- package/dist/import.js +20 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +8 -7
- package/dist/log-update.d.ts +29 -0
- package/dist/log-update.js +173 -0
- package/dist/loop.d.ts +30 -0
- package/dist/loop.js +67 -0
- package/dist/ora.d.ts +82 -0
- package/dist/ora.js +515 -0
- package/dist/plugin.d.ts +107 -0
- package/dist/plugin.js +163 -0
- package/dist/progress.d.ts +19 -0
- package/dist/progress.js +25 -0
- package/dist/projection.d.ts +46 -0
- package/dist/projection.js +83 -0
- package/dist/schema.json +1 -0
- package/dist/spinner.d.ts +9 -0
- package/dist/spinner.js +24 -0
- package/dist/spinners.json +1 -0
- package/dist/table.d.ts +18 -0
- package/dist/table.js +69 -0
- package/dist/tasks.d.ts +18 -0
- package/dist/tasks.js +31 -0
- package/dist/width.d.ts +13 -0
- package/dist/width.js +86 -0
- package/dist/wrap.d.ts +10 -0
- package/dist/wrap.js +447 -0
- package/package.json +77 -4
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** A border style, in cli-boxes' shape exactly, so that corpus imports unchanged. */
|
|
2
|
+
export interface BorderStyle {
|
|
3
|
+
topLeft: string;
|
|
4
|
+
top: string;
|
|
5
|
+
topRight: string;
|
|
6
|
+
left: string;
|
|
7
|
+
right: string;
|
|
8
|
+
bottomLeft: string;
|
|
9
|
+
bottom: string;
|
|
10
|
+
bottomRight: string;
|
|
11
|
+
}
|
|
12
|
+
/** A spinner style, in cli-spinners' shape plus the projection a pipe prints. */
|
|
13
|
+
export interface SpinnerDef {
|
|
14
|
+
frames: string[];
|
|
15
|
+
interval: number;
|
|
16
|
+
static: string;
|
|
17
|
+
}
|
|
18
|
+
/** What the loop hoists: a static projection, and optionally the animated form. */
|
|
19
|
+
export interface Component<S = unknown> {
|
|
20
|
+
name: string;
|
|
21
|
+
static(state: S): string;
|
|
22
|
+
frame?(t: number, state: S): string;
|
|
23
|
+
/** Milliseconds between repaints when `frame` is given; `DEFAULT_INTERVAL` otherwise. */
|
|
24
|
+
interval?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The two states this component is *shown* with by `flagstaff check` and the docs gallery.
|
|
27
|
+
* The loop never reads it: a component's real state comes from the program. Declared here
|
|
28
|
+
* because a grader that invents a state renders the wrong thing and says `ok` (#59).
|
|
29
|
+
*/
|
|
30
|
+
sample?: {
|
|
31
|
+
running: S;
|
|
32
|
+
done: S;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare const CONTRACT = 1;
|
|
36
|
+
/** The whole plugin contract. Keys another package in the family understands are kept for it. */
|
|
37
|
+
export interface Plugin {
|
|
38
|
+
name: string;
|
|
39
|
+
contract?: typeof CONTRACT;
|
|
40
|
+
tokens?: Record<string, `#${string}`>;
|
|
41
|
+
glyphs?: Record<string, string>;
|
|
42
|
+
spinners?: Record<string, SpinnerDef>;
|
|
43
|
+
borders?: Record<string, BorderStyle>;
|
|
44
|
+
components?: Record<string, Omit<Component, 'name'>>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The family's whole refusal vocabulary (plugin-contract R8). One union, and every refusal
|
|
48
|
+
* any surface of this package prints is a member of it — the registry's five, and the two
|
|
49
|
+
* `flagstaff check` adds for what only a renderer can discover: a plugin that validates but
|
|
50
|
+
* contributes nothing renderable, and a component whose `static` throws on the state it is
|
|
51
|
+
* shown with.
|
|
52
|
+
*
|
|
53
|
+
* The last two lived as bare string literals in `cli.ts` until 2026-09-09, which is exactly
|
|
54
|
+
* the hole R8 exists to close: a second host could spell `E_COMPONENT_THREW` its own way and
|
|
55
|
+
* nothing would notice. `scripts/plugin-error-vocabulary-lock.test.ts` now reads this
|
|
56
|
+
* declaration out of the source and refuses any `'E_…'` literal in a host that is not in it.
|
|
57
|
+
*
|
|
58
|
+
* It stays a *type* rather than a `const` array on purpose. R3 forbids one layer importing
|
|
59
|
+
* another, so a runtime list could not be shared with roundel or caique even if it existed —
|
|
60
|
+
* the lock has to read the source to work across the family, and it does. A `const` array
|
|
61
|
+
* would therefore buy nothing and cost ~195 B on `./spinner`, which has 82 B of headroom.
|
|
62
|
+
*/
|
|
63
|
+
export type PluginErrorCode = 'E_PLUGIN_SCHEMA' | 'E_NO_STATIC_PROJECTION' | 'E_PLUGIN_CONTRACT' | 'E_UNKNOWN_SPINNER' | 'E_UNKNOWN_BORDER' | 'E_NO_CONTRIBUTION' | 'E_COMPONENT_THREW';
|
|
64
|
+
/** A refused plugin says what is wrong, where, and what to do about it. */
|
|
65
|
+
export declare class PluginError extends Error {
|
|
66
|
+
readonly code: PluginErrorCode;
|
|
67
|
+
readonly fix: string;
|
|
68
|
+
constructor(code: PluginErrorCode, message: string, fix: string);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Refuse what the schema refuses, and say why. A missing `static` gets its own code and
|
|
72
|
+
* fix, because it is the one mistake a plugin author makes on purpose (U3).
|
|
73
|
+
*/
|
|
74
|
+
export declare function validate(plugin: unknown): asserts plugin is Plugin;
|
|
75
|
+
/** Maps, not records: a plugin's keys are its author's, and a Map cannot be polluted by one. */
|
|
76
|
+
interface Registry {
|
|
77
|
+
plugins: string[];
|
|
78
|
+
tokens: Map<string, `#${string}`>;
|
|
79
|
+
glyphs: Map<string, string>;
|
|
80
|
+
spinners: Map<string, SpinnerDef>;
|
|
81
|
+
borders: Map<string, BorderStyle>;
|
|
82
|
+
components: Map<string, Component>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The only wiring (R4, U9): validate, then keep every key this package understands. A later
|
|
86
|
+
* plugin's entry replaces an earlier one of the same name, so a user overrides a built-in
|
|
87
|
+
* by registering their own — the built-ins go through this same door first.
|
|
88
|
+
*/
|
|
89
|
+
export declare function register(plugin: unknown): void;
|
|
90
|
+
/**
|
|
91
|
+
* A copy of what has been registered — the docs gallery and `flagstaff check` are
|
|
92
|
+
* projections of this. A copy rather than the registry itself, because `Readonly<T>` freezes
|
|
93
|
+
* the property bindings and not the `Map`s behind them: handing the live registry out made
|
|
94
|
+
* `set`, `delete` and `clear` a second door beside `register()`, through which a spinner
|
|
95
|
+
* with no `static` — or a component with no projection at all — could be put in without
|
|
96
|
+
* ever meeting `validate()` (#58). U3's refusal has to be structural to mean anything, so
|
|
97
|
+
* there is one way in. The values are the frozen objects `register()` stored, so nothing
|
|
98
|
+
* reached through here writes back.
|
|
99
|
+
*/
|
|
100
|
+
export declare function registered(): Readonly<Registry>;
|
|
101
|
+
/** A spinner by name, or a refusal that lists the names that exist. */
|
|
102
|
+
export declare function lookupSpinner(name: string): SpinnerDef;
|
|
103
|
+
/** A border by name, for `box()`; every style the built-ins ship is registered like any other. */
|
|
104
|
+
export declare function lookupBorder(name: string): BorderStyle;
|
|
105
|
+
/** A glyph by meaning; the built-ins define every meaning the built-in components use. */
|
|
106
|
+
export declare function glyph(meaning: string): string;
|
|
107
|
+
export {};
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { builtins } from './builtins.js';
|
|
2
|
+
import schema from './schema.json' with { type: 'json' };
|
|
3
|
+
export const CONTRACT = 1;
|
|
4
|
+
export class PluginError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
fix;
|
|
7
|
+
constructor(code, message, fix) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.fix = fix;
|
|
11
|
+
this.name = 'PluginError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const DEFS_PREFIX = '#/$defs/';
|
|
15
|
+
const ROOT = schema;
|
|
16
|
+
function typeOf(value) {
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return 'array';
|
|
19
|
+
if (value === null)
|
|
20
|
+
return 'null';
|
|
21
|
+
if (typeof value === 'number')
|
|
22
|
+
return Number.isInteger(value) ? 'integer' : 'number';
|
|
23
|
+
return typeof value;
|
|
24
|
+
}
|
|
25
|
+
function checkScalar(value, node, path) {
|
|
26
|
+
if (node.const !== undefined && value !== node.const)
|
|
27
|
+
return `${path}: expected ${JSON.stringify(node.const)}, got ${JSON.stringify(value)}`;
|
|
28
|
+
if (typeof value === 'string') {
|
|
29
|
+
if (node.minLength !== undefined && value.length < node.minLength)
|
|
30
|
+
return `${path}: must not be empty`;
|
|
31
|
+
if (node.pattern !== undefined && !new RegExp(node.pattern).test(value))
|
|
32
|
+
return `${path}: ${JSON.stringify(value)} does not match ${node.pattern}`;
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === 'number' && node.minimum !== undefined && value < node.minimum)
|
|
35
|
+
return `${path}: must be at least ${node.minimum}`;
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
function checkArray(value, node, path) {
|
|
39
|
+
if (node.minItems !== undefined && value.length < node.minItems)
|
|
40
|
+
return `${path}: needs at least ${node.minItems} item(s)`;
|
|
41
|
+
const { items } = node;
|
|
42
|
+
if (items === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
for (const [i, item] of value.entries()) {
|
|
45
|
+
const problem = check(item, items, `${path}[${i}]`);
|
|
46
|
+
if (problem !== undefined)
|
|
47
|
+
return problem;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const isSchema = (x) => typeof x === 'object' && x !== null;
|
|
52
|
+
function checkObject(record, node, path) {
|
|
53
|
+
for (const key of node.required ?? []) {
|
|
54
|
+
if (!(key in record))
|
|
55
|
+
return `${path}.${key}: required`;
|
|
56
|
+
}
|
|
57
|
+
for (const [key, child] of Object.entries(record)) {
|
|
58
|
+
const rule = node.properties?.[key] ?? (isSchema(node.additionalProperties) ? node.additionalProperties : undefined);
|
|
59
|
+
if (rule === undefined) {
|
|
60
|
+
if (node.additionalProperties === false)
|
|
61
|
+
return `${path}.${key}: not allowed`;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const problem = check(child, rule, `${path}.${key}`);
|
|
65
|
+
if (problem !== undefined)
|
|
66
|
+
return problem;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
function check(value, node, path) {
|
|
71
|
+
if (node.$ref !== undefined) {
|
|
72
|
+
const def = ROOT.$defs[node.$ref.slice(DEFS_PREFIX.length)];
|
|
73
|
+
return def === undefined ? `${path}: unknown $ref ${node.$ref}` : check(value, def, path);
|
|
74
|
+
}
|
|
75
|
+
const actual = typeOf(value);
|
|
76
|
+
if (node.type !== undefined && actual !== node.type && !(node.type === 'number' && actual === 'integer')) {
|
|
77
|
+
return `${path}: expected ${node.type}, got ${actual}`;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(value))
|
|
80
|
+
return checkArray(value, node, path);
|
|
81
|
+
if (actual === 'object')
|
|
82
|
+
return checkObject(value, node, path);
|
|
83
|
+
return checkScalar(value, node, path);
|
|
84
|
+
}
|
|
85
|
+
const NO_STATIC = /\.(?:spinners|components)\.([^.]+)\.static: required$/;
|
|
86
|
+
export function validate(plugin) {
|
|
87
|
+
const problem = check(plugin, ROOT, 'plugin');
|
|
88
|
+
if (problem !== undefined) {
|
|
89
|
+
const missing = NO_STATIC.exec(problem);
|
|
90
|
+
if (missing) {
|
|
91
|
+
throw new PluginError('E_NO_STATIC_PROJECTION', `${missing[1]} has no static projection`, 'give it a `static`: the text a pipe, an agent or a screen reader gets instead of the animation');
|
|
92
|
+
}
|
|
93
|
+
throw new PluginError('E_PLUGIN_SCHEMA', problem, 'compare the object against flagstaff/schema.json');
|
|
94
|
+
}
|
|
95
|
+
const { contract, components = {} } = plugin;
|
|
96
|
+
if (contract !== undefined && contract > CONTRACT) {
|
|
97
|
+
throw new PluginError('E_PLUGIN_CONTRACT', `plugin declares contract ${contract}; this flagstaff knows ${CONTRACT}`, `upgrade flagstaff, or set contract: ${CONTRACT}`);
|
|
98
|
+
}
|
|
99
|
+
for (const [name, component] of Object.entries(components)) {
|
|
100
|
+
if (typeof component.static !== 'function') {
|
|
101
|
+
throw new PluginError('E_NO_STATIC_PROJECTION', `${name} has no static projection`, 'make `static` a function of the state that returns the text a pipe prints');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const registry = { plugins: [], tokens: new Map(), glyphs: new Map(), spinners: new Map(), borders: new Map(), components: new Map() };
|
|
106
|
+
function frozen(value) {
|
|
107
|
+
Object.freeze(value);
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
function deepFrozen(value, seen = new WeakMap()) {
|
|
111
|
+
if (value === null || typeof value !== 'object')
|
|
112
|
+
return value;
|
|
113
|
+
const already = seen.get(value);
|
|
114
|
+
if (already !== undefined)
|
|
115
|
+
return already;
|
|
116
|
+
const copy = (Array.isArray(value) ? [] : {});
|
|
117
|
+
seen.set(value, copy);
|
|
118
|
+
for (const [k, v] of Object.entries(value))
|
|
119
|
+
copy[k] = deepFrozen(v, seen);
|
|
120
|
+
return frozen(copy);
|
|
121
|
+
}
|
|
122
|
+
export function register(plugin) {
|
|
123
|
+
validate(plugin);
|
|
124
|
+
if (!registry.plugins.includes(plugin.name))
|
|
125
|
+
registry.plugins.push(plugin.name);
|
|
126
|
+
for (const [name, hex] of Object.entries(plugin.tokens ?? {}))
|
|
127
|
+
registry.tokens.set(name, hex);
|
|
128
|
+
for (const [name, text] of Object.entries(plugin.glyphs ?? {}))
|
|
129
|
+
registry.glyphs.set(name, text);
|
|
130
|
+
for (const [name, def] of Object.entries(plugin.spinners ?? {}))
|
|
131
|
+
registry.spinners.set(name, frozen({ ...def, frames: frozen([...def.frames]) }));
|
|
132
|
+
for (const [name, style] of Object.entries(plugin.borders ?? {}))
|
|
133
|
+
registry.borders.set(name, frozen({ ...style }));
|
|
134
|
+
for (const [name, component] of Object.entries(plugin.components ?? {}))
|
|
135
|
+
registry.components.set(name, frozen({ ...deepFrozen(component), name }));
|
|
136
|
+
}
|
|
137
|
+
export function registered() {
|
|
138
|
+
return {
|
|
139
|
+
plugins: [...registry.plugins],
|
|
140
|
+
tokens: new Map(registry.tokens),
|
|
141
|
+
glyphs: new Map(registry.glyphs),
|
|
142
|
+
spinners: new Map(registry.spinners),
|
|
143
|
+
borders: new Map(registry.borders),
|
|
144
|
+
components: new Map(registry.components),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function lookup(from, kind, code, name) {
|
|
148
|
+
const found = from.get(name);
|
|
149
|
+
if (found === undefined) {
|
|
150
|
+
throw new PluginError(code, `no ${kind} named ${JSON.stringify(name)}`, `use one of ${[...from.keys()].join(', ')}, or register a plugin that defines it`);
|
|
151
|
+
}
|
|
152
|
+
return found;
|
|
153
|
+
}
|
|
154
|
+
export function lookupSpinner(name) {
|
|
155
|
+
return lookup(registry.spinners, 'spinner', 'E_UNKNOWN_SPINNER', name);
|
|
156
|
+
}
|
|
157
|
+
export function lookupBorder(name) {
|
|
158
|
+
return lookup(registry.borders, 'border', 'E_UNKNOWN_BORDER', name);
|
|
159
|
+
}
|
|
160
|
+
export function glyph(meaning) {
|
|
161
|
+
return registry.glyphs.get(meaning) ?? '';
|
|
162
|
+
}
|
|
163
|
+
register(builtins);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Component } from './plugin.js';
|
|
2
|
+
export interface ProgressState {
|
|
3
|
+
/** Units done so far; clamped into `[0, total]` on the way out. */
|
|
4
|
+
done: number;
|
|
5
|
+
total: number;
|
|
6
|
+
/** What is being counted, e.g. `files`. Read in both projections. */
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ProgressOptions {
|
|
10
|
+
/** Cells the bar occupies on a terminal. Default 24. */
|
|
11
|
+
width?: number;
|
|
12
|
+
/** The two characters the bar is drawn with. Default `█` and `░`. */
|
|
13
|
+
glyphs?: {
|
|
14
|
+
filled: string;
|
|
15
|
+
empty: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** A progress bar: `12/30 files · 40%` off a terminal, a drawn bar on one. */
|
|
19
|
+
export declare function progress({ width, glyphs }?: ProgressOptions): Component<ProgressState>;
|
package/dist/progress.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { muted, ok } from 'roundel/tokens';
|
|
2
|
+
const DEFAULT_WIDTH = 24;
|
|
3
|
+
const PERCENT = 100;
|
|
4
|
+
const FILLED = '█';
|
|
5
|
+
const EMPTY = '░';
|
|
6
|
+
function ratio(state) {
|
|
7
|
+
if (state.total <= 0)
|
|
8
|
+
return 1;
|
|
9
|
+
return Math.min(1, Math.max(0, state.done / state.total));
|
|
10
|
+
}
|
|
11
|
+
function suffix(state) {
|
|
12
|
+
const label = state.label === undefined || state.label === '' ? '' : ` ${state.label}`;
|
|
13
|
+
return `${state.done}/${state.total}${label}`;
|
|
14
|
+
}
|
|
15
|
+
export function progress({ width = DEFAULT_WIDTH, glyphs = { filled: FILLED, empty: EMPTY } } = {}) {
|
|
16
|
+
const percentage = (state) => `${Math.round(ratio(state) * PERCENT)}%`;
|
|
17
|
+
return {
|
|
18
|
+
name: 'progress',
|
|
19
|
+
static: (state) => `${suffix(state)} · ${percentage(state)}`,
|
|
20
|
+
frame: (_t, state) => {
|
|
21
|
+
const filled = Math.round(ratio(state) * width);
|
|
22
|
+
return `${ok(glyphs.filled.repeat(filled))}${muted(glyphs.empty.repeat(Math.max(0, width - filled)))} ${suffix(state)}`;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-mode writers (R1). This is the only module in the package that emits a cursor
|
|
3
|
+
* operation, and it does so only in `tty`; every other projection is text and a newline,
|
|
4
|
+
* which is what R5 greps for. Streams and time come in as arguments — nothing here knows
|
|
5
|
+
* `process` exists. The three sequences are written by hand rather than through
|
|
6
|
+
* `node:readline`, whose helpers want a `Writable` where the loop only has a `Writer`.
|
|
7
|
+
*/
|
|
8
|
+
import { HIDE_CURSOR, SHOW_CURSOR } from './cursor.js';
|
|
9
|
+
import { type Component } from './plugin.js';
|
|
10
|
+
export interface Writer {
|
|
11
|
+
write(chunk: string): unknown;
|
|
12
|
+
}
|
|
13
|
+
/** Time as the loop sees it: burgee's `Runtime.clock` satisfies it, so does `manualClock()`. */
|
|
14
|
+
export interface Clock {
|
|
15
|
+
now(): number;
|
|
16
|
+
schedule(fn: () => void, ms: number): () => void;
|
|
17
|
+
}
|
|
18
|
+
/** One hoisted component's lifecycle, as a mode sees it. */
|
|
19
|
+
export interface Projection<S> {
|
|
20
|
+
open(state: S): void;
|
|
21
|
+
change(state: S): void;
|
|
22
|
+
close(state: S): void;
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_INTERVAL = 80;
|
|
25
|
+
export declare function ttyProjection<S>(component: Component<S>, out: Writer, clock: Clock): Projection<S>;
|
|
26
|
+
/**
|
|
27
|
+
* A pipe, CI, or a screen reader: the static projection, once per change, and nothing else.
|
|
28
|
+
*
|
|
29
|
+
* "Once per change" is finer than "when the text differs". A component whose projection is
|
|
30
|
+
* a list — `tasks`, whose static is every task that has settled — grows it a line at a
|
|
31
|
+
* time, and printing the whole list on every change repeats every line already in the log:
|
|
32
|
+
*
|
|
33
|
+
* ✔ install ← install settled
|
|
34
|
+
* ✔ install ← build settled, and install is printed again
|
|
35
|
+
* ✔ build
|
|
36
|
+
*
|
|
37
|
+
* So what is written is the lines *past the common prefix* with what was written last. A
|
|
38
|
+
* component whose text replaces itself rather than growing shares no prefix and is printed
|
|
39
|
+
* whole, which is the ordinary case; one that grows is appended to, which is what a log
|
|
40
|
+
* wants. An empty projection writes nothing at all — a component with nothing to say yet
|
|
41
|
+
* should not cost a blank line.
|
|
42
|
+
*/
|
|
43
|
+
export declare function staticProjection<S>(component: Component<S>, out: Writer): Projection<S>;
|
|
44
|
+
/** `--json`: one NDJSON event per transition on stderr, so stdout stays the envelope's (R1). */
|
|
45
|
+
export declare function jsonProjection<S>(component: Component<S>, err: Writer): Projection<S>;
|
|
46
|
+
export { HIDE_CURSOR, SHOW_CURSOR };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { HIDE_CURSOR, restoreCursorOnExit, SHOW_CURSOR } from './cursor.js';
|
|
2
|
+
export const DEFAULT_INTERVAL = 80;
|
|
3
|
+
const CSI = '\u001B[';
|
|
4
|
+
const NO_NET = () => undefined;
|
|
5
|
+
function erase(lines) {
|
|
6
|
+
if (lines === 0)
|
|
7
|
+
return '';
|
|
8
|
+
return `${CSI}1G${lines > 1 ? `${CSI}${lines - 1}A` : ''}${CSI}0J`;
|
|
9
|
+
}
|
|
10
|
+
class TtyProjection {
|
|
11
|
+
#component;
|
|
12
|
+
#out;
|
|
13
|
+
#clock;
|
|
14
|
+
#interval;
|
|
15
|
+
#started;
|
|
16
|
+
#current;
|
|
17
|
+
#lines = 0;
|
|
18
|
+
#cancel = () => undefined;
|
|
19
|
+
#dropCursorNet = NO_NET;
|
|
20
|
+
constructor(component, out, clock) {
|
|
21
|
+
this.#component = component;
|
|
22
|
+
this.#out = out;
|
|
23
|
+
this.#clock = clock;
|
|
24
|
+
this.#interval = component.interval ?? DEFAULT_INTERVAL;
|
|
25
|
+
this.#started = clock.now();
|
|
26
|
+
}
|
|
27
|
+
open(state) {
|
|
28
|
+
this.#current = state;
|
|
29
|
+
this.#out.write(HIDE_CURSOR);
|
|
30
|
+
this.#dropCursorNet = restoreCursorOnExit((s) => void this.#out.write(s));
|
|
31
|
+
this.#paint();
|
|
32
|
+
if (this.#component.frame !== undefined)
|
|
33
|
+
this.#cancel = this.#clock.schedule(() => this.#repaint(), this.#interval);
|
|
34
|
+
}
|
|
35
|
+
change(state) {
|
|
36
|
+
this.#current = state;
|
|
37
|
+
this.#paint();
|
|
38
|
+
}
|
|
39
|
+
close(state) {
|
|
40
|
+
this.#cancel();
|
|
41
|
+
this.#out.write(`${erase(this.#lines)}${this.#component.static(state)}\n${SHOW_CURSOR}`);
|
|
42
|
+
this.#dropCursorNet();
|
|
43
|
+
this.#dropCursorNet = NO_NET;
|
|
44
|
+
this.#lines = 0;
|
|
45
|
+
}
|
|
46
|
+
#paint() {
|
|
47
|
+
const { frame } = this.#component;
|
|
48
|
+
const text = frame === undefined ? this.#component.static(this.#current) : frame(this.#clock.now() - this.#started, this.#current);
|
|
49
|
+
this.#out.write(erase(this.#lines) + text);
|
|
50
|
+
this.#lines = text.split('\n').length;
|
|
51
|
+
}
|
|
52
|
+
#repaint() {
|
|
53
|
+
this.#paint();
|
|
54
|
+
this.#cancel = this.#clock.schedule(() => this.#repaint(), this.#interval);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function ttyProjection(component, out, clock) {
|
|
58
|
+
return new TtyProjection(component, out, clock);
|
|
59
|
+
}
|
|
60
|
+
export function staticProjection(component, out) {
|
|
61
|
+
let written = [];
|
|
62
|
+
const emit = (state) => {
|
|
63
|
+
const text = component.static(state);
|
|
64
|
+
if (text === '')
|
|
65
|
+
return;
|
|
66
|
+
const lines = text.split('\n');
|
|
67
|
+
let shared = 0;
|
|
68
|
+
while (shared < written.length && shared < lines.length && written[shared] === lines[shared])
|
|
69
|
+
shared += 1;
|
|
70
|
+
const fresh = lines.slice(shared);
|
|
71
|
+
written = lines;
|
|
72
|
+
if (fresh.length > 0)
|
|
73
|
+
out.write(`${fresh.join('\n')}\n`);
|
|
74
|
+
};
|
|
75
|
+
return { open: emit, change: emit, close: emit };
|
|
76
|
+
}
|
|
77
|
+
export function jsonProjection(component, err) {
|
|
78
|
+
const emit = (state) => {
|
|
79
|
+
err.write(`${JSON.stringify({ event: component.name, state })}\n`);
|
|
80
|
+
};
|
|
81
|
+
return { open: emit, change: emit, close: emit };
|
|
82
|
+
}
|
|
83
|
+
export { HIDE_CURSOR, SHOW_CURSOR };
|
package/dist/schema.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/ofri-peretz/burgee/blob/main/packages/flagstaff/src/schema.json","title":"flagstaff plugin","description":"A plugin is one plain object. Everything in it is data that can be read without running it; the only functions allowed are a component's `static` (required) and `frame` (optional). A spinner or component without a static projection is refused at register().","type":"object","required":["name"],"additionalProperties":true,"properties":{"name":{"type":"string","minLength":1,"description":"The plugin's name; also the prefix a host may use when two plugins contribute the same key."},"contract":{"type":"integer","minimum":1,"description":"The plugin contract this object follows. A host refuses a newer contract than it knows."},"tokens":{"type":"object","description":"A roundel theme: semantic token name to a hex colour, contrast-checked when flown.","additionalProperties":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"}},"glyphs":{"type":"object","description":"Symbols by meaning: `ok`, `fail`, `warn`, `info`, `running`. A plugin that ships glyphs changes every built-in that draws one.","additionalProperties":{"type":"string","minLength":1}},"spinners":{"type":"object","description":"Spinner styles by name, in cli-spinners' shape plus the static projection.","additionalProperties":{"$ref":"#/$defs/spinner"}},"borders":{"type":"object","description":"Border styles a box can be drawn with, by name.","additionalProperties":{"$ref":"#/$defs/border"}},"components":{"type":"object","description":"Components by name: `static(state)` returns the text a pipe, an agent or a screen reader gets; `frame(t, state)` is the optional animated form.","additionalProperties":{"$ref":"#/$defs/component"}}},"$defs":{"spinner":{"type":"object","required":["frames","interval","static"],"properties":{"frames":{"type":"array","items":{"type":"string"},"minItems":1},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between frames on a terminal."},"static":{"type":"string","description":"What a pipe prints instead of the animation."}}},"component":{"type":"object","required":["static"],"properties":{"static":{"description":"(state) => string. Required: the projection every non-terminal mode prints."},"frame":{"description":"(t, state) => string. Optional: the frame at t milliseconds since hoisting."},"sample":{"type":"object","required":["running","done"],"description":"Two states to *show* this component with: `flagstaff check` and the docs gallery render `running` then `done`. Omitted, they assume `{ phase: 'running' }` and `{ phase: 'done' }` and say so in the output. The loop never reads it — a running program's state comes from the program.","properties":{"running":{"description":"The state to open with."},"done":{"description":"The state to close with."}}},"interval":{"type":"integer","minimum":1,"description":"Milliseconds between repaints when `frame` is given; 80 when omitted."}}},"border":{"type":"object","required":["topLeft","top","topRight","left","right","bottomLeft","bottom","bottomRight"],"description":"cli-boxes' shape exactly, so that corpus imports unchanged.","properties":{"topLeft":{"type":"string"},"top":{"type":"string"},"topRight":{"type":"string"},"left":{"type":"string"},"right":{"type":"string"},"bottomLeft":{"type":"string"},"bottom":{"type":"string"},"bottomRight":{"type":"string"}}}}}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Component } from './plugin.js';
|
|
2
|
+
export type SpinnerStatus = 'running' | 'ok' | 'fail' | 'warn' | 'info';
|
|
3
|
+
export interface SpinnerState {
|
|
4
|
+
text: string;
|
|
5
|
+
/** `running` when omitted. */
|
|
6
|
+
status?: SpinnerStatus;
|
|
7
|
+
}
|
|
8
|
+
/** A spinner in the named style (`dots` by default); every style is a registered plugin's. */
|
|
9
|
+
export declare function spinner(style?: string): Component<SpinnerState>;
|
package/dist/spinner.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { error, hint, ok, warn } from 'roundel/tokens';
|
|
2
|
+
import { glyph, lookupSpinner } from './plugin.js';
|
|
3
|
+
const PAINT = { ok, fail: error, warn, info: hint };
|
|
4
|
+
function symbol(status, def) {
|
|
5
|
+
if (status === 'running')
|
|
6
|
+
return def.static || glyph('running');
|
|
7
|
+
return PAINT[status](glyph(status));
|
|
8
|
+
}
|
|
9
|
+
export function spinner(style = 'dots') {
|
|
10
|
+
const def = lookupSpinner(style);
|
|
11
|
+
const line = (status, text) => `${symbol(status, def)} ${text}`;
|
|
12
|
+
return {
|
|
13
|
+
name: 'spinner',
|
|
14
|
+
interval: def.interval,
|
|
15
|
+
static: (state) => line(state.status ?? 'running', state.text),
|
|
16
|
+
frame: (t, state) => {
|
|
17
|
+
const status = state.status ?? 'running';
|
|
18
|
+
if (status !== 'running')
|
|
19
|
+
return line(status, state.text);
|
|
20
|
+
const at = Math.floor(t / def.interval) % def.frames.length;
|
|
21
|
+
return `${def.frames[at] ?? ''} ${state.text}`;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"dots":{"interval":80,"frames":["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]},"dots2":{"interval":80,"frames":["⣾","⣽","⣻","⢿","⡿","⣟","⣯","⣷"]},"dots3":{"interval":80,"frames":["⠋","⠙","⠚","⠞","⠖","⠦","⠴","⠲","⠳","⠓"]},"dots4":{"interval":80,"frames":["⠄","⠆","⠇","⠋","⠙","⠸","⠰","⠠","⠰","⠸","⠙","⠋","⠇","⠆"]},"dots5":{"interval":80,"frames":["⠋","⠙","⠚","⠒","⠂","⠂","⠒","⠲","⠴","⠦","⠖","⠒","⠐","⠐","⠒","⠓","⠋"]},"dots6":{"interval":80,"frames":["⠁","⠉","⠙","⠚","⠒","⠂","⠂","⠒","⠲","⠴","⠤","⠄","⠄","⠤","⠴","⠲","⠒","⠂","⠂","⠒","⠚","⠙","⠉","⠁"]},"dots7":{"interval":80,"frames":["⠈","⠉","⠋","⠓","⠒","⠐","⠐","⠒","⠖","⠦","⠤","⠠","⠠","⠤","⠦","⠖","⠒","⠐","⠐","⠒","⠓","⠋","⠉","⠈"]},"dots8":{"interval":80,"frames":["⠁","⠁","⠉","⠙","⠚","⠒","⠂","⠂","⠒","⠲","⠴","⠤","⠄","⠄","⠤","⠠","⠠","⠤","⠦","⠖","⠒","⠐","⠐","⠒","⠓","⠋","⠉","⠈","⠈"]},"dots9":{"interval":80,"frames":["⢹","⢺","⢼","⣸","⣇","⡧","⡗","⡏"]},"dots10":{"interval":80,"frames":["⢄","⢂","⢁","⡁","⡈","⡐","⡠"]},"dots11":{"interval":100,"frames":["⠁","⠂","⠄","⡀","⢀","⠠","⠐","⠈"]},"dots12":{"interval":80,"frames":["⢀⠀","⡀⠀","⠄⠀","⢂⠀","⡂⠀","⠅⠀","⢃⠀","⡃⠀","⠍⠀","⢋⠀","⡋⠀","⠍⠁","⢋⠁","⡋⠁","⠍⠉","⠋⠉","⠋⠉","⠉⠙","⠉⠙","⠉⠩","⠈⢙","⠈⡙","⢈⠩","⡀⢙","⠄⡙","⢂⠩","⡂⢘","⠅⡘","⢃⠨","⡃⢐","⠍⡐","⢋⠠","⡋⢀","⠍⡁","⢋⠁","⡋⠁","⠍⠉","⠋⠉","⠋⠉","⠉⠙","⠉⠙","⠉⠩","⠈⢙","⠈⡙","⠈⠩","⠀⢙","⠀⡙","⠀⠩","⠀⢘","⠀⡘","⠀⠨","⠀⢐","⠀⡐","⠀⠠","⠀⢀","⠀⡀"]},"dots13":{"interval":80,"frames":["⣼","⣹","⢻","⠿","⡟","⣏","⣧","⣶"]},"dots14":{"interval":80,"frames":["⠉⠉","⠈⠙","⠀⠹","⠀⢸","⠀⣰","⢀⣠","⣀⣀","⣄⡀","⣆⠀","⡇⠀","⠏⠀","⠋⠁"]},"dots8Bit":{"interval":80,"frames":["⠀","⠁","⠂","⠃","⠄","⠅","⠆","⠇","⡀","⡁","⡂","⡃","⡄","⡅","⡆","⡇","⠈","⠉","⠊","⠋","⠌","⠍","⠎","⠏","⡈","⡉","⡊","⡋","⡌","⡍","⡎","⡏","⠐","⠑","⠒","⠓","⠔","⠕","⠖","⠗","⡐","⡑","⡒","⡓","⡔","⡕","⡖","⡗","⠘","⠙","⠚","⠛","⠜","⠝","⠞","⠟","⡘","⡙","⡚","⡛","⡜","⡝","⡞","⡟","⠠","⠡","⠢","⠣","⠤","⠥","⠦","⠧","⡠","⡡","⡢","⡣","⡤","⡥","⡦","⡧","⠨","⠩","⠪","⠫","⠬","⠭","⠮","⠯","⡨","⡩","⡪","⡫","⡬","⡭","⡮","⡯","⠰","⠱","⠲","⠳","⠴","⠵","⠶","⠷","⡰","⡱","⡲","⡳","⡴","⡵","⡶","⡷","⠸","⠹","⠺","⠻","⠼","⠽","⠾","⠿","⡸","⡹","⡺","⡻","⡼","⡽","⡾","⡿","⢀","⢁","⢂","⢃","⢄","⢅","⢆","⢇","⣀","⣁","⣂","⣃","⣄","⣅","⣆","⣇","⢈","⢉","⢊","⢋","⢌","⢍","⢎","⢏","⣈","⣉","⣊","⣋","⣌","⣍","⣎","⣏","⢐","⢑","⢒","⢓","⢔","⢕","⢖","⢗","⣐","⣑","⣒","⣓","⣔","⣕","⣖","⣗","⢘","⢙","⢚","⢛","⢜","⢝","⢞","⢟","⣘","⣙","⣚","⣛","⣜","⣝","⣞","⣟","⢠","⢡","⢢","⢣","⢤","⢥","⢦","⢧","⣠","⣡","⣢","⣣","⣤","⣥","⣦","⣧","⢨","⢩","⢪","⢫","⢬","⢭","⢮","⢯","⣨","⣩","⣪","⣫","⣬","⣭","⣮","⣯","⢰","⢱","⢲","⢳","⢴","⢵","⢶","⢷","⣰","⣱","⣲","⣳","⣴","⣵","⣶","⣷","⢸","⢹","⢺","⢻","⢼","⢽","⢾","⢿","⣸","⣹","⣺","⣻","⣼","⣽","⣾","⣿"]},"dotsCircle":{"interval":80,"frames":["⢎ ","⠎⠁","⠊⠑","⠈⠱"," ⡱","⢀⡰","⢄⡠","⢆⡀"]},"sand":{"interval":80,"frames":["⠁","⠂","⠄","⡀","⡈","⡐","⡠","⣀","⣁","⣂","⣄","⣌","⣔","⣤","⣥","⣦","⣮","⣶","⣷","⣿","⡿","⠿","⢟","⠟","⡛","⠛","⠫","⢋","⠋","⠍","⡉","⠉","⠑","⠡","⢁"]},"line":{"interval":130,"frames":["-","\\","|","/"]},"line2":{"interval":100,"frames":["⠂","-","–","—","–","-"]},"pipe":{"interval":100,"frames":["┤","┘","┴","└","├","┌","┬","┐"]},"simpleDots":{"interval":400,"frames":[". ",".. ","..."," "]},"simpleDotsScrolling":{"interval":200,"frames":[". ",".. ","..."," .."," ."," "]},"star":{"interval":70,"frames":["✶","✸","✹","✺","✹","✷"]},"star2":{"interval":80,"frames":["+","x","*"]},"flip":{"interval":70,"frames":["_","_","_","-","`","`","'","´","-","_","_","_"]},"hamburger":{"interval":100,"frames":["☱","☲","☴"]},"growVertical":{"interval":120,"frames":["▁","▃","▄","▅","▆","▇","▆","▅","▄","▃"]},"growHorizontal":{"interval":120,"frames":["▏","▎","▍","▌","▋","▊","▉","▊","▋","▌","▍","▎"]},"balloon":{"interval":140,"frames":[" ",".","o","O","@","*"," "]},"balloon2":{"interval":120,"frames":[".","o","O","°","O","o","."]},"noise":{"interval":100,"frames":["▓","▒","░"]},"bounce":{"interval":120,"frames":["⠁","⠂","⠄","⠂"]},"boxBounce":{"interval":120,"frames":["▖","▘","▝","▗"]},"boxBounce2":{"interval":100,"frames":["▌","▀","▐","▄"]},"triangle":{"interval":50,"frames":["◢","◣","◤","◥"]},"binary":{"interval":80,"frames":["010010","001100","100101","111010","111101","010111","101011","111000","110011","110101"]},"arc":{"interval":100,"frames":["◜","◠","◝","◞","◡","◟"]},"circle":{"interval":120,"frames":["◡","⊙","◠"]},"squareCorners":{"interval":180,"frames":["◰","◳","◲","◱"]},"circleQuarters":{"interval":120,"frames":["◴","◷","◶","◵"]},"circleHalves":{"interval":50,"frames":["◐","◓","◑","◒"]},"squish":{"interval":100,"frames":["╫","╪"]},"toggle":{"interval":250,"frames":["⊶","⊷"]},"toggle2":{"interval":80,"frames":["▫","▪"]},"toggle3":{"interval":120,"frames":["□","■"]},"toggle4":{"interval":100,"frames":["■","□","▪","▫"]},"toggle5":{"interval":100,"frames":["▮","▯"]},"toggle6":{"interval":300,"frames":["ဝ","၀"]},"toggle7":{"interval":80,"frames":["⦾","⦿"]},"toggle8":{"interval":100,"frames":["◍","◌"]},"toggle9":{"interval":100,"frames":["◉","◎"]},"toggle10":{"interval":100,"frames":["㊂","㊀","㊁"]},"toggle11":{"interval":50,"frames":["⧇","⧆"]},"toggle12":{"interval":120,"frames":["☗","☖"]},"toggle13":{"interval":80,"frames":["=","*","-"]},"arrow":{"interval":100,"frames":["←","↖","↑","↗","→","↘","↓","↙"]},"arrow2":{"interval":80,"frames":["⬆️ ","↗️ ","➡️ ","↘️ ","⬇️ ","↙️ ","⬅️ ","↖️ "]},"arrow3":{"interval":120,"frames":["▹▹▹▹▹","▸▹▹▹▹","▹▸▹▹▹","▹▹▸▹▹","▹▹▹▸▹","▹▹▹▹▸"]},"bouncingBar":{"interval":80,"frames":["[ ]","[= ]","[== ]","[=== ]","[====]","[ ===]","[ ==]","[ =]","[ ]","[ =]","[ ==]","[ ===]","[====]","[=== ]","[== ]","[= ]"]},"bouncingBall":{"interval":80,"frames":["( ● )","( ● )","( ● )","( ● )","( ●)","( ● )","( ● )","( ● )","( ● )","(● )"]},"smiley":{"interval":200,"frames":["😄 ","😝 "]},"monkey":{"interval":300,"frames":["🙈 ","🙈 ","🙉 ","🙊 "]},"hearts":{"interval":100,"frames":["💛 ","💙 ","💜 ","💚 ","❤️ "]},"clock":{"interval":100,"frames":["🕛 ","🕐 ","🕑 ","🕒 ","🕓 ","🕔 ","🕕 ","🕖 ","🕗 ","🕘 ","🕙 ","🕚 "]},"earth":{"interval":180,"frames":["🌍 ","🌎 ","🌏 "]},"material":{"interval":17,"frames":["█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁","██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁","███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁","████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁","██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁","██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁","███████▁▁▁▁▁▁▁▁▁▁▁▁▁","████████▁▁▁▁▁▁▁▁▁▁▁▁","█████████▁▁▁▁▁▁▁▁▁▁▁","█████████▁▁▁▁▁▁▁▁▁▁▁","██████████▁▁▁▁▁▁▁▁▁▁","███████████▁▁▁▁▁▁▁▁▁","█████████████▁▁▁▁▁▁▁","██████████████▁▁▁▁▁▁","██████████████▁▁▁▁▁▁","▁██████████████▁▁▁▁▁","▁██████████████▁▁▁▁▁","▁██████████████▁▁▁▁▁","▁▁██████████████▁▁▁▁","▁▁▁██████████████▁▁▁","▁▁▁▁█████████████▁▁▁","▁▁▁▁██████████████▁▁","▁▁▁▁██████████████▁▁","▁▁▁▁▁██████████████▁","▁▁▁▁▁██████████████▁","▁▁▁▁▁██████████████▁","▁▁▁▁▁▁██████████████","▁▁▁▁▁▁██████████████","▁▁▁▁▁▁▁█████████████","▁▁▁▁▁▁▁█████████████","▁▁▁▁▁▁▁▁████████████","▁▁▁▁▁▁▁▁████████████","▁▁▁▁▁▁▁▁▁███████████","▁▁▁▁▁▁▁▁▁███████████","▁▁▁▁▁▁▁▁▁▁██████████","▁▁▁▁▁▁▁▁▁▁██████████","▁▁▁▁▁▁▁▁▁▁▁▁████████","▁▁▁▁▁▁▁▁▁▁▁▁▁███████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████","█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████","██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███","██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███","███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███","████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██","█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█","█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█","██████▁▁▁▁▁▁▁▁▁▁▁▁▁█","████████▁▁▁▁▁▁▁▁▁▁▁▁","█████████▁▁▁▁▁▁▁▁▁▁▁","█████████▁▁▁▁▁▁▁▁▁▁▁","█████████▁▁▁▁▁▁▁▁▁▁▁","█████████▁▁▁▁▁▁▁▁▁▁▁","███████████▁▁▁▁▁▁▁▁▁","████████████▁▁▁▁▁▁▁▁","████████████▁▁▁▁▁▁▁▁","██████████████▁▁▁▁▁▁","██████████████▁▁▁▁▁▁","▁██████████████▁▁▁▁▁","▁██████████████▁▁▁▁▁","▁▁▁█████████████▁▁▁▁","▁▁▁▁▁████████████▁▁▁","▁▁▁▁▁████████████▁▁▁","▁▁▁▁▁▁███████████▁▁▁","▁▁▁▁▁▁▁▁█████████▁▁▁","▁▁▁▁▁▁▁▁█████████▁▁▁","▁▁▁▁▁▁▁▁▁█████████▁▁","▁▁▁▁▁▁▁▁▁█████████▁▁","▁▁▁▁▁▁▁▁▁▁█████████▁","▁▁▁▁▁▁▁▁▁▁▁████████▁","▁▁▁▁▁▁▁▁▁▁▁████████▁","▁▁▁▁▁▁▁▁▁▁▁▁███████▁","▁▁▁▁▁▁▁▁▁▁▁▁███████▁","▁▁▁▁▁▁▁▁▁▁▁▁▁███████","▁▁▁▁▁▁▁▁▁▁▁▁▁███████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁","▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁"]},"moon":{"interval":80,"frames":["🌑 ","🌒 ","🌓 ","🌔 ","🌕 ","🌖 ","🌗 ","🌘 "]},"runner":{"interval":140,"frames":["🚶 ","🏃 "]},"pong":{"interval":80,"frames":["▐⠂ ▌","▐⠈ ▌","▐ ⠂ ▌","▐ ⠠ ▌","▐ ⡀ ▌","▐ ⠠ ▌","▐ ⠂ ▌","▐ ⠈ ▌","▐ ⠂ ▌","▐ ⠠ ▌","▐ ⡀ ▌","▐ ⠠ ▌","▐ ⠂ ▌","▐ ⠈ ▌","▐ ⠂▌","▐ ⠠▌","▐ ⡀▌","▐ ⠠ ▌","▐ ⠂ ▌","▐ ⠈ ▌","▐ ⠂ ▌","▐ ⠠ ▌","▐ ⡀ ▌","▐ ⠠ ▌","▐ ⠂ ▌","▐ ⠈ ▌","▐ ⠂ ▌","▐ ⠠ ▌","▐ ⡀ ▌","▐⠠ ▌"]},"shark":{"interval":120,"frames":["▐|\\____________▌","▐_|\\___________▌","▐__|\\__________▌","▐___|\\_________▌","▐____|\\________▌","▐_____|\\_______▌","▐______|\\______▌","▐_______|\\_____▌","▐________|\\____▌","▐_________|\\___▌","▐__________|\\__▌","▐___________|\\_▌","▐____________|\\▌","▐____________/|▌","▐___________/|_▌","▐__________/|__▌","▐_________/|___▌","▐________/|____▌","▐_______/|_____▌","▐______/|______▌","▐_____/|_______▌","▐____/|________▌","▐___/|_________▌","▐__/|__________▌","▐_/|___________▌","▐/|____________▌"]},"dqpb":{"interval":100,"frames":["d","q","p","b"]},"weather":{"interval":100,"frames":["☀️ ","☀️ ","☀️ ","🌤 ","⛅️ ","🌥 ","☁️ ","🌧 ","🌨 ","🌧 ","🌨 ","🌧 ","🌨 ","⛈ ","🌨 ","🌧 ","🌨 ","☁️ ","🌥 ","⛅️ ","🌤 ","☀️ ","☀️ "]},"christmas":{"interval":400,"frames":["🌲","🎄"]},"grenade":{"interval":80,"frames":["، ","′ "," ´ "," ‾ "," ⸌"," ⸊"," |"," ⁎"," ⁕"," ෴ "," ⁓"," "," "," "]},"point":{"interval":125,"frames":["∙∙∙","●∙∙","∙●∙","∙∙●","∙∙∙"]},"layer":{"interval":150,"frames":["-","=","≡"]},"betaWave":{"interval":80,"frames":["ρββββββ","βρβββββ","ββρββββ","βββρβββ","ββββρββ","βββββρβ","ββββββρ"]},"fingerDance":{"interval":160,"frames":["🤘 ","🤟 ","🖖 ","✋ ","🤚 ","👆 "]},"fistBump":{"interval":80,"frames":["🤜 🤛 ","🤜 🤛 ","🤜 🤛 "," 🤜 🤛 "," 🤜🤛 "," 🤜✨🤛 ","🤜 ✨ 🤛 "]},"soccerHeader":{"interval":80,"frames":[" 🧑⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 ","🧑 ⚽️ 🧑 "]},"mindblown":{"interval":160,"frames":["😐 ","😐 ","😮 ","😮 ","😦 ","😦 ","😧 ","😧 ","🤯 ","💥 ","✨ "," "," "," "]},"speaker":{"interval":160,"frames":["🔈 ","🔉 ","🔊 ","🔉 "]},"orangePulse":{"interval":100,"frames":["🔸 ","🔶 ","🟠 ","🟠 ","🔶 "]},"bluePulse":{"interval":100,"frames":["🔹 ","🔷 ","🔵 ","🔵 ","🔷 "]},"orangeBluePulse":{"interval":100,"frames":["🔸 ","🔶 ","🟠 ","🟠 ","🔶 ","🔹 ","🔷 ","🔵 ","🔵 ","🔷 "]},"timeTravel":{"interval":100,"frames":["🕛 ","🕚 ","🕙 ","🕘 ","🕗 ","🕖 ","🕕 ","🕔 ","🕓 ","🕒 ","🕑 ","🕐 "]},"aesthetic":{"interval":80,"frames":["▰▱▱▱▱▱▱","▰▰▱▱▱▱▱","▰▰▰▱▱▱▱","▰▰▰▰▱▱▱","▰▰▰▰▰▱▱","▰▰▰▰▰▰▱","▰▰▰▰▰▰▰","▰▱▱▱▱▱▱"]},"dwarfFortress":{"interval":80,"frames":[" ██████£££ ","☺██████£££ ","☺██████£££ ","☺▓█████£££ ","☺▓█████£££ ","☺▒█████£££ ","☺▒█████£££ ","☺░█████£££ ","☺░█████£££ ","☺ █████£££ "," ☺█████£££ "," ☺█████£££ "," ☺▓████£££ "," ☺▓████£££ "," ☺▒████£££ "," ☺▒████£££ "," ☺░████£££ "," ☺░████£££ "," ☺ ████£££ "," ☺████£££ "," ☺████£££ "," ☺▓███£££ "," ☺▓███£££ "," ☺▒███£££ "," ☺▒███£££ "," ☺░███£££ "," ☺░███£££ "," ☺ ███£££ "," ☺███£££ "," ☺███£££ "," ☺▓██£££ "," ☺▓██£££ "," ☺▒██£££ "," ☺▒██£££ "," ☺░██£££ "," ☺░██£££ "," ☺ ██£££ "," ☺██£££ "," ☺██£££ "," ☺▓█£££ "," ☺▓█£££ "," ☺▒█£££ "," ☺▒█£££ "," ☺░█£££ "," ☺░█£££ "," ☺ █£££ "," ☺█£££ "," ☺█£££ "," ☺▓£££ "," ☺▓£££ "," ☺▒£££ "," ☺▒£££ "," ☺░£££ "," ☺░£££ "," ☺ £££ "," ☺£££ "," ☺£££ "," ☺▓££ "," ☺▓££ "," ☺▒££ "," ☺▒££ "," ☺░££ "," ☺░££ "," ☺ ££ "," ☺££ "," ☺££ "," ☺▓£ "," ☺▓£ "," ☺▒£ "," ☺▒£ "," ☺░£ "," ☺░£ "," ☺ £ "," ☺£ "," ☺£ "," ☺▓ "," ☺▓ "," ☺▒ "," ☺▒ "," ☺░ "," ☺░ "," ☺ "," ☺ &"," ☺ ☼&"," ☺ ☼ &"," ☺☼ &"," ☺☼ & "," ‼ & "," ☺ & "," ‼ & "," ☺ & "," ‼ & "," ☺ & ","‼ & "," & "," & "," & ░ "," & ▒ "," & ▓ "," & £ "," & ░£ "," & ▒£ "," & ▓£ "," & ££ "," & ░££ "," & ▒££ ","& ▓££ ","& £££ "," ░£££ "," ▒£££ "," ▓£££ "," █£££ "," ░█£££ "," ▒█£££ "," ▓█£££ "," ██£££ "," ░██£££ "," ▒██£££ "," ▓██£££ "," ███£££ "," ░███£££ "," ▒███£££ "," ▓███£££ "," ████£££ "," ░████£££ "," ▒████£££ "," ▓████£££ "," █████£££ "," ░█████£££ "," ▒█████£££ "," ▓█████£££ "," ██████£££ "," ██████£££ "]}}
|
package/dist/table.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Component } from './plugin.js';
|
|
2
|
+
export type Row = string[];
|
|
3
|
+
export interface TableOptions {
|
|
4
|
+
/** Column headers. Omitted, the table is drawn without a header row. */
|
|
5
|
+
head?: string[];
|
|
6
|
+
/** Columns the whole table may occupy. Default 80. */
|
|
7
|
+
width?: number;
|
|
8
|
+
/** Alignment per column; `left` for any column not named. */
|
|
9
|
+
align?: ('left' | 'right')[];
|
|
10
|
+
}
|
|
11
|
+
/** Draw `rows` as a table, as a string. */
|
|
12
|
+
export declare function table(rows: Row[], options?: TableOptions): string;
|
|
13
|
+
export interface TableState {
|
|
14
|
+
head?: string[];
|
|
15
|
+
rows: Row[];
|
|
16
|
+
}
|
|
17
|
+
/** A table as a component: `header: value` pairs off a terminal, the grid on one (R1). */
|
|
18
|
+
export declare function tableComponent(options?: TableOptions): Component<TableState>;
|