ADR-0024 Phase E: replay end-to-end

Migrate `replay <path>` to the walker. Shape is
Choice(StringLit, BarePath); the StringLit branch handles the
quoted form (with the existing `''` escape), and BarePath
handles the unquoted form.

Per ADR-0024's path-bearing UX change (already shipped for
import / export in Phase A), bare `replay` paths terminate at
the first whitespace byte. Paths with spaces require the
quoted form. The legacy `try_parse_replay_with_bare_path`
source-slice helper in dsl/parser.rs is removed; the
chumsky-side replay branch in command_parser stays declared
but unreachable until Phase F sweeps the chumsky path.

Tests:
- 7 new walker-specific tests for replay: bare relative path,
  bare absolute path, quoted with whitespace, quoted with
  escaped quote, case-insensitive keyword, missing-path
  error, empty-quoted-path parses to empty (runtime layer
  rejects).
- Total: 844 passed, 0 failed, 1 ignored (was 838 / 1).
- cargo clippy --all-targets -- -D warnings clean.
This commit is contained in:
claude@clouddev1
2026-05-15 07:23:51 +00:00
parent c2accc2385
commit dca472f8a5
4 changed files with 115 additions and 51 deletions
+39
View File
@@ -490,6 +490,36 @@ fn build_delete(path: &MatchedPath) -> Result<Command, ValidationError> {
Ok(Command::Delete { table, filter })
}
// =================================================================
// replay — `replay <bare-path>` | `replay '<path>'`
// =================================================================
//
// Phase E (ADR-0024 §migration). The chumsky-side
// `try_parse_replay_with_bare_path` source-slice helper is
// retired here: walker BarePath consumes the unquoted form
// (terminating at whitespace per the path-bearing UX change),
// and StringLit consumes the quoted form. Paths with spaces
// must use the quoted form — same UX that `import` / `export`
// adopted in Phase A.
const REPLAY_PATH_CHOICES: &[Node] = &[Node::StringLit, Node::BarePath];
const REPLAY_PATH: Node = Node::Choice(REPLAY_PATH_CHOICES);
fn build_replay(path: &MatchedPath) -> Result<Command, ValidationError> {
let payload = path
.items
.iter()
.find_map(|i| match &i.kind {
MatchedKind::StringLit | MatchedKind::BarePath => Some(i.text.clone()),
_ => None,
})
.ok_or_else(|| ValidationError {
message_key: "parse.error_wrapper",
args: vec![("detail", "missing path".to_string())],
})?;
Ok(Command::Replay { path: payload })
}
// =================================================================
// CommandNodes
// =================================================================
@@ -529,3 +559,12 @@ pub static DELETE: CommandNode = CommandNode {
usage_id: Some("parse.usage.delete"),
hint_mode: None,
};
pub static REPLAY: CommandNode = CommandNode {
entry: Word::keyword("replay"),
shape: REPLAY_PATH,
ast_builder: build_replay,
help_id: Some("data.replay"),
usage_id: Some("parse.usage.replay"),
hint_mode: None,
};