round-5 follow-up: completion + i18n sweep
Four user-reported gaps from the round-4 testing pass:
1. Empty-prompt hint reworded from "(no active hint)" to
"Type a command — press Tab for options, `help` for a
list" (6 snapshots updated to reflect 80-col truncation).
2. App-lifecycle commands (quit/q, help, rebuild, save/save as,
new, load, export, import, mode, messages) now flow through
the DSL parser:
- 15 new keywords + catalog token entries
- new Command::App(AppCommand) AST with 11 variants
- parse-first dispatch in submit() (app commands work in
both simple and advanced modes)
- pre-chumsky source-slice for `export <path>` /
`import <zip> [as <target>]` mirrors the replay precedent
- UsageEntry registry entries so parse errors surface
relevant usage templates
- `mode <bad>` / `messages <bad>` use try_map for the
friendly "unknown mode/messages" wording
3. DSL completion gaps:
- `1:n` surfaces as a composite candidate at `add `
- --all-rows / --create-fk / --force-conversion /
--dont-convert surface as new CandidateKind::Flag
candidates (coloured with tok_flag in hint panel)
- filter_clause .labelled() wrap removed so chumsky's
expected-set surfaces the constituent options
4. Hardcoded user-facing strings migrated to catalog:
- 4 parser custom errors (incl. the known "tables need at
least one column" wart)
- UnknownType Display now via parse.custom.unknown_type
- UI panel titles + mode labels (Output / Hint / SIMPLE /
ADVANCED / Advanced:)
- app.rs cascade rendering (action labels + summary)
- runtime --resume CLI stderr
- db.rs change-column diagnostic tables (7 headers + 3
wrapper summaries + force-conversion hint)
Tests: 765 → 769 passing, 0 failed, 1 ignored (same doctest
as before). Clippy clean with -D warnings.
Deferred:
- ~25 thiserror #[error] attributes still hand-rolled
(DbError, ArgsError, ArchiveError, PersistenceError,
LockError). Tracked separately.
- DSL/SQL relationship in advanced mode — clarified
implicitly via parse-first dispatch; broader ADR
amendment to follow.
- Post-complete-parse completion gap (e.g. `save ` Tab
can't offer `as` because `save` parses bare; same shape
as `--create-fk` after a complete `add relationship`).
This commit is contained in:
@@ -140,6 +140,13 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
// ---- Parse error rendering ----
|
||||
("parse.available_commands", &["commands"]),
|
||||
("parse.caret", &["padding"]),
|
||||
// Custom (try_map / source-slice) error messages raised
|
||||
// by the DSL parser. See `parse.custom.*` in the catalog.
|
||||
("parse.custom.change_column_flags_exclusive", &[]),
|
||||
("parse.custom.create_table_needs_pk", &[]),
|
||||
("parse.custom.on_action_specified_twice", &["target"]),
|
||||
("parse.custom.replay_path_expected", &[]),
|
||||
("parse.custom.unknown_type", &["found", "expected"]),
|
||||
("parse.empty", &[]),
|
||||
("parse.error", &["detail"]),
|
||||
// Per-command usage templates (ADR-0021 §1). One key per
|
||||
@@ -158,7 +165,17 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("parse.usage.drop_table", &[]),
|
||||
("parse.usage.insert", &[]),
|
||||
("parse.usage.rename_column", &[]),
|
||||
("parse.usage.export", &[]),
|
||||
("parse.usage.help", &[]),
|
||||
("parse.usage.import", &[]),
|
||||
("parse.usage.load", &[]),
|
||||
("parse.usage.messages", &[]),
|
||||
("parse.usage.mode", &[]),
|
||||
("parse.usage.new", &[]),
|
||||
("parse.usage.quit", &[]),
|
||||
("parse.usage.rebuild", &[]),
|
||||
("parse.usage.replay", &[]),
|
||||
("parse.usage.save", &[]),
|
||||
("parse.usage.show_data", &[]),
|
||||
("parse.usage.show_table", &[]),
|
||||
("parse.usage.update", &[]),
|
||||
@@ -176,6 +193,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("parse.token.identifier", &[]),
|
||||
("parse.token.keyword.action", &[]),
|
||||
("parse.token.keyword.add", &[]),
|
||||
("parse.token.keyword.advanced", &[]),
|
||||
("parse.token.keyword.as", &[]),
|
||||
("parse.token.keyword.cascade", &[]),
|
||||
("parse.token.keyword.change", &[]),
|
||||
@@ -184,26 +202,40 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("parse.token.keyword.data", &[]),
|
||||
("parse.token.keyword.delete", &[]),
|
||||
("parse.token.keyword.drop", &[]),
|
||||
("parse.token.keyword.export", &[]),
|
||||
("parse.token.keyword.false", &[]),
|
||||
("parse.token.keyword.from", &[]),
|
||||
("parse.token.keyword.help", &[]),
|
||||
("parse.token.keyword.import", &[]),
|
||||
("parse.token.keyword.in", &[]),
|
||||
("parse.token.keyword.insert", &[]),
|
||||
("parse.token.keyword.into", &[]),
|
||||
("parse.token.keyword.load", &[]),
|
||||
("parse.token.keyword.messages", &[]),
|
||||
("parse.token.keyword.mode", &[]),
|
||||
("parse.token.keyword.new", &[]),
|
||||
("parse.token.keyword.no", &[]),
|
||||
("parse.token.keyword.null", &[]),
|
||||
("parse.token.keyword.on", &[]),
|
||||
("parse.token.keyword.pk", &[]),
|
||||
("parse.token.keyword.q", &[]),
|
||||
("parse.token.keyword.quit", &[]),
|
||||
("parse.token.keyword.rebuild", &[]),
|
||||
("parse.token.keyword.relationship", &[]),
|
||||
("parse.token.keyword.rename", &[]),
|
||||
("parse.token.keyword.replay", &[]),
|
||||
("parse.token.keyword.restrict", &[]),
|
||||
("parse.token.keyword.save", &[]),
|
||||
("parse.token.keyword.set", &[]),
|
||||
("parse.token.keyword.short", &[]),
|
||||
("parse.token.keyword.show", &[]),
|
||||
("parse.token.keyword.simple", &[]),
|
||||
("parse.token.keyword.table", &[]),
|
||||
("parse.token.keyword.to", &[]),
|
||||
("parse.token.keyword.true", &[]),
|
||||
("parse.token.keyword.update", &[]),
|
||||
("parse.token.keyword.values", &[]),
|
||||
("parse.token.keyword.verbose", &[]),
|
||||
("parse.token.keyword.where", &[]),
|
||||
("parse.token.keyword.with", &[]),
|
||||
("parse.token.number", &[]),
|
||||
@@ -222,6 +254,8 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("project.import_usage", &[]),
|
||||
("project.import_zip_missing", &["path"]),
|
||||
("project.load_path_missing", &["path"]),
|
||||
("project.resume_no_previous", &["data_root"]),
|
||||
("project.resume_recorded_missing", &["path"]),
|
||||
("project.saveas_target_exists", &["path"]),
|
||||
("project.rebuild_failed", &["error"]),
|
||||
("project.rebuild_ok", &["summary"]),
|
||||
@@ -248,6 +282,8 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("modal.rebuild_confirm_title", &[]),
|
||||
// ---- Status bar + panels ----
|
||||
("panel.hint_empty", &[]),
|
||||
("panel.hint_title", &[]),
|
||||
("panel.output_title", &[]),
|
||||
("panel.tables_empty", &[]),
|
||||
("panel.tables_title", &[]),
|
||||
("status.no_project", &[]),
|
||||
@@ -276,12 +312,44 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
||||
("messages.set_verbose", &[]),
|
||||
("messages.show", &["current"]),
|
||||
("messages.unknown", &["value"]),
|
||||
("mode.label_advanced", &[]),
|
||||
("mode.label_advanced_one_shot", &[]),
|
||||
("mode.label_simple", &[]),
|
||||
("mode.set_advanced", &[]),
|
||||
("mode.set_simple", &[]),
|
||||
("mode.show_advanced", &[]),
|
||||
("mode.show_simple", &[]),
|
||||
("mode.unknown", &["value"]),
|
||||
("mode.usage", &[]),
|
||||
// ---- Cascade-effect summaries (per ADR-0014) ----
|
||||
("db.cascade.action_blocked", &[]),
|
||||
("db.cascade.action_deleted", &[]),
|
||||
("db.cascade.action_set_null", &[]),
|
||||
(
|
||||
"db.cascade.summary",
|
||||
&["count", "action", "child_table", "rel", "on_delete"],
|
||||
),
|
||||
// ---- change-column dry-run diagnostics (per ADR-0017) ----
|
||||
("db.diagnostic.force_conversion_hint", &[]),
|
||||
("db.diagnostic.header_becomes", &[]),
|
||||
("db.diagnostic.header_from", &[]),
|
||||
("db.diagnostic.header_reason", &[]),
|
||||
("db.diagnostic.header_source_rows", &["pk_label"]),
|
||||
("db.diagnostic.header_source_values", &[]),
|
||||
("db.diagnostic.header_to", &[]),
|
||||
("db.diagnostic.header_value", &[]),
|
||||
(
|
||||
"db.diagnostic.incompatible_summary",
|
||||
&["table", "column", "src_ty", "target_ty", "total"],
|
||||
),
|
||||
(
|
||||
"db.diagnostic.lossy_summary",
|
||||
&["table", "column", "src_ty", "target_ty", "total"],
|
||||
),
|
||||
(
|
||||
"db.diagnostic.uniqueness_summary",
|
||||
&["table", "column", "src_ty", "target_ty", "total"],
|
||||
),
|
||||
// ---- DSL command success summaries (ADR-0019 §9 sweep) ----
|
||||
("ok.rows_deleted", &["count"]),
|
||||
("ok.rows_inserted", &["count"]),
|
||||
|
||||
@@ -279,6 +279,19 @@ parse:
|
||||
# caret pointer (visualising the failure column) is printed
|
||||
# on its own preceding line via `parse.caret`.
|
||||
error: "parse error: {detail}"
|
||||
# Custom (try_map / source-slice) error messages raised by
|
||||
# the DSL parser. These were hand-written strings in
|
||||
# `src/dsl/parser.rs` until the catalog migration brought
|
||||
# them under the same roof as the rest of the user-facing
|
||||
# vocabulary. Wording is unchanged from the inline source
|
||||
# form so existing anchor-phrase tests still match.
|
||||
custom:
|
||||
replay_path_expected: "expected a path after `replay`"
|
||||
create_table_needs_pk: |-
|
||||
tables need at least one column. Add `with pk` for a default `id INTEGER PRIMARY KEY`, or `with pk <name>:<type>` to choose. Use a comma-separated list for compound primary keys.
|
||||
on_action_specified_twice: "`on {target}` specified twice"
|
||||
change_column_flags_exclusive: "`--force-conversion` and `--dont-convert` are mutually exclusive — pick one."
|
||||
unknown_type: "unknown type '{found}' (expected one of: {expected})"
|
||||
# Caret pointer showing where in the input the parser
|
||||
# failed. `{padding}` is the leading whitespace; the
|
||||
# template appends `^` so the rendered line places the
|
||||
@@ -322,6 +335,22 @@ parse:
|
||||
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>'"
|
||||
# App-lifecycle commands (per ADR-0003, surfaced through
|
||||
# the parser so they participate in usage templates +
|
||||
# completion). Templates here describe the surface
|
||||
# grammar that the parser accepts; the in-app `help`
|
||||
# listing in `help.in_app_body` carries the user-facing
|
||||
# description.
|
||||
quit: "quit | q"
|
||||
help: "help"
|
||||
rebuild: "rebuild"
|
||||
save: "save | save as"
|
||||
new: "new"
|
||||
load: "load"
|
||||
export: "export [<path>]"
|
||||
import: "import <zip-path> [as <target>]"
|
||||
mode: "mode simple | mode advanced"
|
||||
messages: "messages | messages short | messages verbose"
|
||||
# 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,
|
||||
@@ -360,6 +389,23 @@ parse:
|
||||
restrict: "`restrict`"
|
||||
action: "`action`"
|
||||
"no": "`no`"
|
||||
# App-lifecycle commands (per ADR-0003, surfaced through
|
||||
# the parser to drive completion + usage templates).
|
||||
quit: "`quit`"
|
||||
q: "`q`"
|
||||
help: "`help`"
|
||||
rebuild: "`rebuild`"
|
||||
save: "`save`"
|
||||
new: "`new`"
|
||||
load: "`load`"
|
||||
export: "`export`"
|
||||
import: "`import`"
|
||||
mode: "`mode`"
|
||||
messages: "`messages`"
|
||||
simple: "`simple`"
|
||||
advanced: "`advanced`"
|
||||
short: "`short`"
|
||||
verbose: "`verbose`"
|
||||
punct:
|
||||
colon: "`:`"
|
||||
open_paren: "`(`"
|
||||
@@ -395,6 +441,12 @@ project:
|
||||
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"
|
||||
# --resume CLI failures printed to stderr before the TUI
|
||||
# starts (ADR-0015 §7). Wording stays one line for clean
|
||||
# piping; the runtime prepends `rdbms-playground: ` from
|
||||
# `cli.binary_prefix` itself.
|
||||
resume_recorded_missing: "--resume: recorded project `{path}` no longer exists"
|
||||
resume_no_previous: "--resume: no previous project recorded under `{data_root}`"
|
||||
|
||||
# ---- DSL failure wrapper + advanced-mode placeholder + fatal --------
|
||||
dsl:
|
||||
@@ -447,7 +499,11 @@ status:
|
||||
panel:
|
||||
tables_title: "Tables"
|
||||
tables_empty: "(none yet)"
|
||||
hint_empty: "(no active hint)"
|
||||
hint_empty: "Type a command — press Tab for options, `help` for a list"
|
||||
# Panel titles for the output and hint panels (rendered inside
|
||||
# the rounded border, hence the leading/trailing space).
|
||||
output_title: "Output"
|
||||
hint_title: "Hint"
|
||||
|
||||
# ---- Shortcut hints (paired with key names in the bottom bar) -------
|
||||
shortcut:
|
||||
@@ -473,6 +529,12 @@ mode:
|
||||
show_advanced: "mode: advanced"
|
||||
usage: "usage: mode simple | mode advanced"
|
||||
unknown: "unknown mode '{value}' (expected 'simple' or 'advanced')"
|
||||
# Labels rendered inside the input panel's border to mark the
|
||||
# current input mode. `label_advanced_one_shot` is shown
|
||||
# while a `:` one-shot is in flight from simple mode.
|
||||
label_simple: "SIMPLE"
|
||||
label_advanced: "ADVANCED"
|
||||
label_advanced_one_shot: "Advanced:"
|
||||
|
||||
messages:
|
||||
show: "messages: {current}"
|
||||
@@ -480,6 +542,42 @@ messages:
|
||||
set_verbose: "messages: verbose"
|
||||
unknown: "unknown messages mode '{value}' (expected 'short' or 'verbose')"
|
||||
|
||||
# ---- Cascade-effect summaries (per ADR-0014 delete reporting) -------
|
||||
db:
|
||||
cascade:
|
||||
# Per-relationship cascade summary appended to a delete
|
||||
# success note. The same template handles cascade,
|
||||
# set-null, and restrict/no-action cases — `{action}` is
|
||||
# one of the three action phrases below.
|
||||
summary: " related: {count} row(s) {action} in `{child_table}` for relationship `{rel}` (on delete {on_delete})"
|
||||
action_deleted: "deleted"
|
||||
action_set_null: "had FK set to null"
|
||||
action_blocked: "blocked"
|
||||
|
||||
# `change column ... (newtype)` dry-run diagnostics (ADR-0017).
|
||||
# Surface when the migration would lose information (lossy),
|
||||
# produce values the target type can't represent
|
||||
# (incompatible), or violate a uniqueness contract (collision).
|
||||
diagnostic:
|
||||
# Column headers for the diagnostic tables.
|
||||
header_from: "From"
|
||||
header_to: "To"
|
||||
header_reason: "Reason"
|
||||
header_value: "Value"
|
||||
header_becomes: "Becomes"
|
||||
header_source_rows: "Source rows ({pk_label})"
|
||||
header_source_values: "Source values"
|
||||
# Summary lines printed above each diagnostic table.
|
||||
lossy_summary: |-
|
||||
Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} row(s) would discard information.
|
||||
incompatible_summary: |-
|
||||
Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} row(s) cannot be converted.
|
||||
uniqueness_summary: |-
|
||||
Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} collision(s) would violate uniqueness.
|
||||
# Follow-up suggestion appended to the lossy diagnostic
|
||||
# (only — incompatibles can't be force-overridden).
|
||||
force_conversion_hint: "if you want to execute this conversion in spite of the problems, re-run with `--force-conversion`."
|
||||
|
||||
# ---- DSL command success summaries (ADR-0019 §9 sweep) --------------
|
||||
ok:
|
||||
# Generic `[ok] <verb> <subject>` header used for every
|
||||
|
||||
Reference in New Issue
Block a user