11071ae164
New `dsl::usage` module: registry pairing each command's
entry-keyword with a `parse.usage.*` catalog key.
`matched_entry()` resolves the entry keyword from the
consumed token prefix; multi-entry families (add, drop,
show) return all matching keys.
Catalog: new `parse.usage.<command>` keys (one per command),
`parse.token.{keyword,punct,...}` vocabulary (one per
Keyword/Punct variant + token-class labels + LexError
kinds), and `parse.available_commands` for the no-prefix
fallback. Catalog grows ~60 entries.
Validator: extended KEYS_AND_PLACEHOLDERS; new completeness
test asserts every Keyword and Punct variant has its
`parse.token.*` entry.
`app::dispatch_dsl` rewritten to compose three blocks per
ADR-0021 §2: caret + structural/custom error + usage block
(or available-commands fallback per §5). Caret math fixed
to use original-input byte position rather than
trimmed-input position (the lexer no longer trims before
lexing). Three pre-existing app tests adjusted to look
across all error lines instead of `output.back()` (the
usage block is now the last line).
`dsl::usage::matched_entry` uses `<=` rather than `<` for
position comparison so custom errors raised by `try_map`
(whose span starts at the first consumed token) still
resolve to the entry keyword.
Tests: 668 passing, 0 failing, 1 ignored (650 baseline →
+18: 8 usage + 1 token-vocab completeness + 9 new
integration tests in tests/parse_error_pedagogy.rs
covering create/add/drop/show/frobulate/update/insert
cases). Clippy clean.
514 lines
22 KiB
YAML
514 lines
22 KiB
YAML
# en-US catalog (ADR-0019).
|
|
#
|
|
# Hierarchical groups flatten to dot-paths internally:
|
|
# error.unique.insert.headline
|
|
# error.unique.insert.hint
|
|
# help.cli_banner
|
|
# replay.completed
|
|
# … etc.
|
|
#
|
|
# Each error entry has a `headline` (one line, used in both
|
|
# short and verbose modes) and may have a `hint` (one or more
|
|
# lines, surfaced only in verbose mode). Short mode = headline
|
|
# only; verbose mode = headline + hint + (if present) the
|
|
# diagnostic table the translator built (ADR-0019 §7).
|
|
#
|
|
# Anchor phrases per ADR-0019 §10 are kept stable across
|
|
# wording changes:
|
|
# "no such table"
|
|
# "no such column"
|
|
# "no such relationship"
|
|
# "already exists"
|
|
# "already has the value"
|
|
# "cannot be converted"
|
|
# "discard information"
|
|
# "referenced by"
|
|
# "[client-side]"
|
|
#
|
|
# Placeholders use `{name}` substitution; format specifiers
|
|
# (`{name:08.2}`, `{name:>10}`, …) are explicitly rejected by
|
|
# the substitution helper (ADR-0019 §8.4).
|
|
|
|
# Sanity entry exercised by the loader's unit tests; not
|
|
# user-facing.
|
|
_test:
|
|
hello: "Hello, {name}!"
|
|
|
|
# ---- Error category --------------------------------------------------
|
|
error:
|
|
|
|
# UNIQUE constraint violations. Anchor: "already has the value".
|
|
unique:
|
|
insert:
|
|
headline: "`{table}.{column}` already has the value `{value}`."
|
|
hint: "The `{column}` column on `{table}` is unique — pick a different value, or update the existing row instead."
|
|
update:
|
|
headline: "`{table}.{column}` already has the value `{value}`."
|
|
hint: "The `{column}` column on `{table}` is unique — your update would create a duplicate."
|
|
# Primary-key collisions get distinct wording — the user
|
|
# learns that PK is the canonical unique constraint.
|
|
pk:
|
|
insert:
|
|
headline: "`{table}` already has a row with primary key `{value}`."
|
|
hint: "Primary keys must be unique — pick a different value or update the existing row."
|
|
update:
|
|
headline: "`{table}` already has a row with primary key `{value}`."
|
|
hint: "Primary keys must be unique — your update would create a duplicate."
|
|
|
|
# FOREIGN KEY violations. Anchor: "referenced by".
|
|
foreign_key:
|
|
# Child-side: insert/update sets a value that has no parent.
|
|
child_side:
|
|
insert:
|
|
headline: "no parent row in `{parent_table}` has `{parent_column}` = `{value}`."
|
|
hint: "Foreign keys must point at an existing parent row. Insert a matching parent first, or pick a value that already exists in `{parent_table}.{parent_column}`."
|
|
update:
|
|
headline: "no parent row in `{parent_table}` has `{parent_column}` = `{value}`."
|
|
hint: "Foreign keys must point at an existing parent row. Pick a value that already exists in `{parent_table}.{parent_column}`."
|
|
# Parent-side: delete/update on a row referenced by children.
|
|
# The engine refuses unless the relationship's `on delete /
|
|
# on update` says cascade or set null.
|
|
parent_side:
|
|
delete:
|
|
headline: "`{table}` rows are referenced by `{child_table}`."
|
|
hint: "Deleting these rows would orphan the children. Delete the children first, or change the relationship's `on delete` action to `cascade` or `set null`."
|
|
update:
|
|
headline: "`{table}` rows are referenced by `{child_table}`."
|
|
hint: "Updating the referenced column would orphan the children. Update the children first, or change the relationship's `on update` action to `cascade`."
|
|
|
|
# NOT NULL constraint violations.
|
|
not_null:
|
|
insert:
|
|
headline: "`{table}.{column}` cannot be null."
|
|
hint: "The `{column}` column is required — provide a value for it in the row you are inserting."
|
|
update:
|
|
headline: "`{table}.{column}` cannot be null."
|
|
hint: "The `{column}` column is required — pick a non-null value, or do not include `{column}` in your `set` list."
|
|
|
|
# CHECK constraint violations. Placeholder coverage —
|
|
# the playground does not emit CHECK constraints today
|
|
# (track C3), but the catalog is wired so the wording
|
|
# is ready when constraint-management lands.
|
|
check:
|
|
insert:
|
|
headline: "check constraint refused `{table}.{column}`."
|
|
hint: "A check constraint requires `{column}` to satisfy a rule the inserted value did not."
|
|
update:
|
|
headline: "check constraint refused `{table}.{column}`."
|
|
hint: "A check constraint requires `{column}` to satisfy a rule the new value did not."
|
|
|
|
# Type mismatch — engine-side STRICT refusal of a wrong-shape
|
|
# value. Mostly the `change column ... --dont-convert` path
|
|
# today (subsumes `friendly_change_column_engine_error`).
|
|
type_mismatch:
|
|
change_column:
|
|
headline: "cannot change `{table}.{column}` from `{src_type}` to `{target_type}` with `--dont-convert`."
|
|
hint: "The database refused at least one cell as the wrong shape for `{target_type}`. Re-run without `--dont-convert` to see which rows."
|
|
insert:
|
|
headline: "value `{value}` is not a `{expected_type}`."
|
|
hint: "The `{column}` column on `{table}` is `{expected_type}` — provide a `{expected_type}` value, or change the column's type with `change column`."
|
|
update:
|
|
headline: "value `{value}` is not a `{expected_type}`."
|
|
hint: "The `{column}` column on `{table}` is `{expected_type}` — provide a `{expected_type}` value, or change the column's type with `change column`."
|
|
|
|
# Object-not-found errors. Anchor: "no such ...". These are
|
|
# genuinely single-line errors — no hint adds value.
|
|
not_found:
|
|
table:
|
|
headline: "no such table: `{name}`"
|
|
column:
|
|
headline: "no such column: `{table}.{column}`"
|
|
column_unqualified:
|
|
headline: "no such column: `{column}`"
|
|
relationship:
|
|
headline: "no such relationship: `{name}`"
|
|
|
|
# Name-collision errors. Anchor: "already exists".
|
|
already_exists:
|
|
table:
|
|
headline: "table `{name}` already exists"
|
|
column:
|
|
headline: "column `{table}.{column}` already exists"
|
|
relationship:
|
|
headline: "relationship `{name}` already exists"
|
|
|
|
# Generic catch-all when the translator can't classify the
|
|
# engine error into a known category. The wording stays
|
|
# engine-neutral; the message text from the engine is NOT
|
|
# surfaced (ADR-0002 user-facing posture).
|
|
generic:
|
|
headline: "the database refused this `{operation}`."
|
|
hint: "The operation could not be completed against the current state of `{table}`."
|
|
|
|
# Errors that are specifically about value validation
|
|
# (DbError::InvalidValue) — wrong arity, wrong literal
|
|
# form, etc. Pre-engine; the catalog covers what the
|
|
# parser / validator already decided was wrong.
|
|
invalid_value:
|
|
arity:
|
|
headline: "expected {expected} value(s), got {actual}."
|
|
empty_insert:
|
|
headline: "INSERT requires at least one column value."
|
|
empty_update:
|
|
headline: "UPDATE requires at least one assignment."
|
|
|
|
# ---- Help text (CLI banner + in-app `help` command) ------------------
|
|
help:
|
|
# CLI usage banner. Printed by `--help` / `-h` and on
|
|
# argument-parse failure. Multi-line block; consumers
|
|
# iterate lines or print as-is.
|
|
cli_banner: |
|
|
rdbms-playground — a TUI playground for relational database concepts
|
|
|
|
Usage:
|
|
rdbms-playground [options] [<project-path>]
|
|
|
|
Arguments:
|
|
<project-path> Path to an existing project directory.
|
|
Without this, a fresh auto-named temp
|
|
project is created in the data dir.
|
|
|
|
Options:
|
|
-h, --help Print this help and exit.
|
|
--theme <light|dark> Override theme (default: auto-detect).
|
|
--data-dir <PATH> Use PATH as the data root instead of
|
|
the OS-standard location for this run.
|
|
--log-file <PATH> Write tracing output to PATH.
|
|
--resume Open the most-recently-used project
|
|
(path tracked under <data-root>/last_project).
|
|
Errors out if no previous project is
|
|
recorded. Mutually exclusive with
|
|
<project-path>.
|
|
|
|
App-level commands (typed inside the app, available in both modes):
|
|
quit / q Exit cleanly.
|
|
mode simple|advanced Switch input mode.
|
|
help Show this list of commands in-app.
|
|
save Save the current temp project under a
|
|
chosen name (or `save as` to copy a
|
|
named project to a new location).
|
|
save as Always prompt for a target name/path.
|
|
new Close current, create a fresh temp.
|
|
load Open the project picker.
|
|
rebuild Rebuild playground.db from project.yaml
|
|
+ data/, with confirmation.
|
|
export [<path>] Write a zip of project.yaml + data/ to
|
|
<path> (relative paths under the data
|
|
root). Excludes playground.db and
|
|
history.log.
|
|
import <zip> [as <t>] Unpack <zip> into a new project and
|
|
switch to it. <t> overrides the target
|
|
name (else taken from the zip).
|
|
# In-app `help` command output. Same shape as
|
|
# `cli_banner` — multi-line block, consumers iterate
|
|
# lines and emit each as its own output row so scroll
|
|
# math stays accurate.
|
|
in_app_body: |
|
|
Supported commands:
|
|
quit / q — exit
|
|
help — this list
|
|
mode simple|advanced — switch input mode
|
|
messages — show current verbosity
|
|
messages short|verbose— switch error wording (verbose is the default)
|
|
rebuild — rebuild .db from project.yaml + data/ (with confirmation)
|
|
save — save current temp project under a name
|
|
save as — copy current project to a new name/path
|
|
new — close current, start a fresh temp project
|
|
load — open the project picker
|
|
export [<path>] — write a zip of project.yaml + data/ (excludes .db, history.log)
|
|
import <zip> [as <t>] — unpack a zip and switch to the new project
|
|
DSL data commands (in simple mode):
|
|
create table <T> with pk [<col>:<type>...]
|
|
drop table <T>
|
|
add column [to] [table] <T>: <col> (<type>)
|
|
(for serial/shortid on a non-empty table: existing rows auto-filled)
|
|
drop column [from] [table] <T>: <col>
|
|
rename column [in] [table] <T>: <old> to <new>
|
|
change column [in] [table] <T>: <col> (<newtype>)
|
|
[--force-conversion | --dont-convert]
|
|
(to serial/shortid: null cells auto-filled with generated values)
|
|
add 1:n relationship [as <name>] from <P>.<col> to <C>.<col>
|
|
[on delete <action>] [on update <action>] [--create-fk]
|
|
drop relationship <name>
|
|
insert into <T> [(cols)] [values] (vals)
|
|
update <T> set <c>=<v>... where <c>=<v> | --all-rows
|
|
delete from <T> where <c>=<v> | --all-rows
|
|
show table <T>
|
|
show data <T>
|
|
replay <path> — run each non-blank, non-`#`-comment line
|
|
of <path> as a command. Stops at the first
|
|
error (no rollback). Relative paths resolve
|
|
under the current project's directory.
|
|
Types: text, int, real, decimal, bool, date, datetime, blob, serial, shortid
|
|
Auto-generated types (serial, shortid):
|
|
serial — integer that auto-fills with the next sequence value
|
|
(MAX(col)+1) on insert. Outside a primary key it carries
|
|
a UNIQUE contract.
|
|
shortid — short base58 identifier auto-filled at insert time. Always
|
|
carries a UNIQUE contract.
|
|
Adding or changing-to either type on a non-empty table auto-fills
|
|
existing/null cells in the same operation.
|
|
|
|
# ---- DSL parse error rendering --------------------------------------
|
|
parse:
|
|
# Wrapper around chumsky's structural error message. The
|
|
# caret pointer (visualising the failure column) is printed
|
|
# on its own preceding line via `parse.caret`.
|
|
error: "parse error: {detail}"
|
|
# Caret pointer showing where in the input the parser
|
|
# failed. `{padding}` is the leading whitespace; the
|
|
# template appends `^` so the rendered line places the
|
|
# marker under the offending character.
|
|
caret: "{padding}^"
|
|
# Default for the `ParseError::Empty` variant — surfaces as
|
|
# `{detail}` inside the wrapper.
|
|
empty: "empty input"
|
|
# No-prefix fallback (ADR-0021 §5): when the parse fails
|
|
# before any keyword is consumed, the renderer lists every
|
|
# command-entry keyword instead of attempting a per-command
|
|
# usage block. `{commands}` is an oxford-joined list of
|
|
# command-keyword renderings (each from
|
|
# `parse.token.keyword.*`).
|
|
available_commands: "available commands: {commands}"
|
|
# Per-command usage templates (ADR-0021 §1). Rendered under a
|
|
# "usage:" prefix when a parse fails after consuming a
|
|
# known command-entry keyword. The bracket convention `[...]`
|
|
# marks optional parts; angle-bracket `<...>` marks
|
|
# placeholders. ADR-0009's surface conventions apply.
|
|
usage:
|
|
create_table: "create table <Name> with pk [<col>:<type>[, ...]]"
|
|
drop_table: "drop table <Name>"
|
|
drop_column: "drop column [from] [table] <Table>: <Name>"
|
|
drop_relationship: |-
|
|
drop relationship <Name>
|
|
drop relationship from <Parent>.<col> to <Child>.<col>
|
|
add_column: "add column [to] [table] <Table>: <Name> (<Type>)"
|
|
add_relationship: |-
|
|
add 1:n relationship [as <Name>]
|
|
from <Parent>.<col> to <Child>.<col>
|
|
[on delete <action>] [on update <action>]
|
|
[--create-fk]
|
|
rename_column: "rename column [in] [table] <Table>: <Old> to <New>"
|
|
change_column: |-
|
|
change column [in] [table] <Table>: <Name> (<Type>)
|
|
[--force-conversion | --dont-convert]
|
|
show_data: "show data <Table>"
|
|
show_table: "show table <Table>"
|
|
insert: "insert into <Table> [(<col>[, ...])] [values] (<value>[, ...])"
|
|
update: "update <Table> set <col>=<value>[, ...] (where <col>=<value> | --all-rows)"
|
|
delete: "delete from <Table> (where <col>=<value> | --all-rows)"
|
|
replay: "replay <path> | replay '<path with spaces>'"
|
|
# Single-token vocabulary the renderer uses to translate
|
|
# chumsky's expected-set patterns. One key per Keyword variant
|
|
# (validated against `Keyword::ALL`), one per Punct variant,
|
|
# one per token-class label, one per LexError kind.
|
|
token:
|
|
keyword:
|
|
create: "`create`"
|
|
drop: "`drop`"
|
|
add: "`add`"
|
|
rename: "`rename`"
|
|
change: "`change`"
|
|
show: "`show`"
|
|
insert: "`insert`"
|
|
update: "`update`"
|
|
delete: "`delete`"
|
|
replay: "`replay`"
|
|
table: "`table`"
|
|
column: "`column`"
|
|
data: "`data`"
|
|
relationship: "`relationship`"
|
|
pk: "`pk`"
|
|
with: "`with`"
|
|
from: "`from`"
|
|
to: "`to`"
|
|
into: "`into`"
|
|
as: "`as`"
|
|
in: "`in`"
|
|
on: "`on`"
|
|
set: "`set`"
|
|
where: "`where`"
|
|
values: "`values`"
|
|
"null": "`null`"
|
|
"true": "`true`"
|
|
"false": "`false`"
|
|
cascade: "`cascade`"
|
|
restrict: "`restrict`"
|
|
action: "`action`"
|
|
"no": "`no`"
|
|
punct:
|
|
colon: "`:`"
|
|
open_paren: "`(`"
|
|
close_paren: "`)`"
|
|
comma: "`,`"
|
|
equals: "`=`"
|
|
dot: "`.`"
|
|
identifier: "identifier"
|
|
number: "number"
|
|
string_literal: "string literal"
|
|
flag: "flag (--name)"
|
|
end_of_input: "end of input"
|
|
error:
|
|
unterminated_string: "unterminated string literal"
|
|
unknown_char: "unrecognised character `{found}`"
|
|
bad_flag: "malformed flag (bare `--`)"
|
|
|
|
# ---- Project lifecycle event notes -----------------------------------
|
|
project:
|
|
rebuild_ok: "[ok] rebuild — {summary}"
|
|
rebuild_failed: "rebuild failed: {error}"
|
|
switched_ok: "[ok] now editing: {display_name}"
|
|
switch_failed: "project switch failed: {error}"
|
|
export_ok: "[ok] export — wrote {path}"
|
|
export_failed: "export failed: {error}"
|
|
# Usage / argument-parsing errors for app-level commands.
|
|
export_usage: "usage: export [<path>]"
|
|
import_usage: "usage: import <zip-path> [as <target>]"
|
|
import_empty_target: "import: empty target after `as`"
|
|
# Project-switch validation failures (load / save-as /
|
|
# import). Returned from the runtime as Err(String) and
|
|
# surfaced via project.switch_failed.
|
|
load_path_missing: "path `{path}` does not exist"
|
|
saveas_target_exists: "`{path}` already exists; pick a different name or remove it first"
|
|
import_zip_missing: "zip `{path}` does not exist"
|
|
|
|
# ---- DSL failure wrapper + advanced-mode placeholder + fatal --------
|
|
dsl:
|
|
# Wrapper around the friendly-error layer's translated
|
|
# message, surfaced as `"<verb> <subject>" failed: <rendered>`.
|
|
failed: '"{verb} {subject}" failed: {rendered}'
|
|
# Echo line `running: <input>` shown above each command's
|
|
# response so the user has on-screen context for the
|
|
# output that follows.
|
|
running: "running: {input}"
|
|
|
|
# ---- Advanced-mode placeholder until SQL parser lands (Q1) ----------
|
|
advanced_mode:
|
|
not_implemented: "advanced mode SQL not implemented yet — echo: {input}"
|
|
|
|
# ---- Persistence-fatal banner (ADR-0015 §8) -------------------------
|
|
fatal:
|
|
persistence: "FATAL: failed to {operation} `{path}` — {message}. Quitting; investigate and restart."
|
|
|
|
# ---- Modal labels (load picker, rebuild confirm, save-as path) ------
|
|
modal:
|
|
rebuild_cancelled: "rebuild cancelled"
|
|
load_cancelled: "load cancelled"
|
|
generic_cancelled: "{title} cancelled"
|
|
path_entry_empty_name: "path entry: empty name"
|
|
path_entry_empty_path: "path entry: empty path"
|
|
load_picker_nothing: "nothing to load"
|
|
# Modal titles + body prose rendered by ui.rs.
|
|
load_picker_title: "Load project"
|
|
load_picker_empty: "(no projects in data directory)"
|
|
load_picker_path_prompt: "Path to project directory:"
|
|
rebuild_confirm_title: "Rebuild project"
|
|
rebuild_confirm_prompt: "Continue?"
|
|
|
|
# ---- Save / save-as command surfaces ---------------------------------
|
|
save:
|
|
# `save` on a named project is a no-op with a friendly hint.
|
|
already_saved: "already auto-saved; use `save as` to copy to a different location"
|
|
# Modal titles for `save` (on a temp) vs `save as`.
|
|
title_as: "Save as"
|
|
title_save: "Save"
|
|
# Prompt body for the path-entry modal opened by save / save as.
|
|
path_prompt: "Name (under data dir/projects) or absolute path:"
|
|
|
|
# ---- Status bar (project label) and panels ---------------------------
|
|
status:
|
|
no_project: "(no project)"
|
|
project_label: "Project: "
|
|
|
|
panel:
|
|
tables_title: "Tables"
|
|
tables_empty: "(none yet)"
|
|
hint_empty: "(no active hint)"
|
|
|
|
# ---- Shortcut hints (paired with key names in the bottom bar) -------
|
|
shortcut:
|
|
submit: "submit"
|
|
confirm: "confirm"
|
|
cancel: "cancel"
|
|
yes: "yes"
|
|
no: "no"
|
|
load: "load"
|
|
select: "select"
|
|
browse_path: "browse path"
|
|
back_to_list: "back to list"
|
|
switch: "switch"
|
|
advanced_once: "advanced once"
|
|
cancel_one_shot: "cancel one-shot"
|
|
quit: "quit"
|
|
|
|
# ---- mode / messages banners (app-level commands) -------------------
|
|
mode:
|
|
set_simple: "mode: simple"
|
|
set_advanced: "mode: advanced"
|
|
show_simple: "mode: simple"
|
|
show_advanced: "mode: advanced"
|
|
usage: "usage: mode simple | mode advanced"
|
|
unknown: "unknown mode '{value}' (expected 'simple' or 'advanced')"
|
|
|
|
messages:
|
|
show: "messages: {current}"
|
|
set_short: "messages: short"
|
|
set_verbose: "messages: verbose"
|
|
unknown: "unknown messages mode '{value}' (expected 'short' or 'verbose')"
|
|
|
|
# ---- DSL command success summaries (ADR-0019 §9 sweep) --------------
|
|
ok:
|
|
# Generic `[ok] <verb> <subject>` header used for every
|
|
# successful DSL command. Verbs come from `Command::verb()`
|
|
# (already English keywords); subjects are the table /
|
|
# relationship endpoints derived in `Command::display_subject()`.
|
|
summary: "[ok] {verb} {subject}"
|
|
# Per-operation row-count footers shown beneath the summary.
|
|
rows_inserted: " {count} row(s) inserted"
|
|
rows_updated: " {count} row(s) updated"
|
|
rows_deleted: " {count} row(s) deleted"
|
|
|
|
# ---- Client-side success notes (ADR-0017 §6, ADR-0018 §9) ------------
|
|
client_side:
|
|
# Per-cell transformation notice when `change column ...` rewrote
|
|
# at least one stored value (storage-class change or non-identity
|
|
# mapping). `lossy` variant fires under --force-conversion when
|
|
# information was discarded.
|
|
transformed: |-
|
|
[client-side] {count} row(s) were transformed before being stored. In raw SQL this would need an explicit `CAST` or application-level code.
|
|
transformed_lossy: |-
|
|
[client-side] {count} row(s) transformed; {lossy} of those discarded information (lossy). In raw SQL this would need an explicit `CAST` or application-level code.
|
|
# Auto-fill notice when null cells were populated by the
|
|
# serial/shortid auto-generation contract (change column path).
|
|
auto_fill_transition: |-
|
|
[client-side] {count} null cell(s) given auto-generated {kind} values. In raw SQL this would need an explicit UPDATE to populate.
|
|
# Auto-fill notice for `add column T: x (serial)` on a
|
|
# non-empty table — values run 1..N.
|
|
auto_fill_add_serial: |-
|
|
[client-side] {count} row(s) given auto-generated serial values 1..{count}. In raw SQL this would need an explicit UPDATE to populate.
|
|
# Auto-fill notice for `add column T: x (shortid)` on a
|
|
# non-empty table.
|
|
auto_fill_add_shortid: |-
|
|
[client-side] {count} row(s) given auto-generated shortid values. In raw SQL this would need an explicit UPDATE to populate.
|
|
|
|
# ---- Replay command surfaces (ADR-0019 §9 sweep) ---------------------
|
|
replay:
|
|
# Success summary printed when `replay <path>` completes
|
|
# without per-line failure.
|
|
completed: "[ok] replay {path} — {count} command(s) run"
|
|
# File-open failure (line_number == 0 from the runtime).
|
|
failed_open: "replay {path} failed: {error}"
|
|
# Per-line failure header. The command echo is on a
|
|
# follow-up `command_echo` line so the renderer can format
|
|
# them as separate output rows.
|
|
failed_at_line: "replay {path} failed at line {line_number}: {error}"
|
|
# Indented echo of the offending command line, shown after
|
|
# `failed_at_line` when the runtime supplied the source.
|
|
command_echo: " > {command}"
|
|
# Errors the runtime constructs inside ReplayFailed.error
|
|
# before forwarding to App. Carried as plain text so they
|
|
# compose with `failed_at_line`'s `{error}` placeholder.
|
|
error_could_not_open: "could not open `{path}`: {detail}"
|
|
error_parse: "parse error: {detail}"
|
|
error_nested: "nested `replay` is not allowed inside a replay file"
|