Skip to content

Commit 2ca83dc

Browse files
authored
Refactor state store to revised PFFTT format (#104)
Refactor the state store used by the _run_ and _resume_ commands to use the revised PFFTT format as formalized in - technique-lang/specification#9 The format changes from being a tablet-esque line to being a more refined text line. This allows the payload at the end for the `Done` state to be a Technique value, up to and especially including being a tablet, without needing additional encoding, escaping, or wrapping. The record lines now include the RunId on each line. The `Start` state line replaces the manifest, fully qualified paths are now rooted at `/` (allowing both full http:// URLs and file:/// URLs to be used interchangeably), and the time steps are started is recorded with the `Begin` state.
2 parents 5c70c21 + 417bf23 commit 2ca83dc

17 files changed

Lines changed: 848 additions & 470 deletions

File tree

src/domain/checklist/adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ mod check {
281281
}
282282

283283
fn extract(source: &str) -> super::Document {
284-
let path = Path::new("test.tq");
284+
let path = Path::new("Test.tq");
285285
let doc = parsing::parse(path, source).unwrap();
286286
ChecklistAdapter.extract(&doc)
287287
}

src/domain/procedure/adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ mod check {
266266
}
267267

268268
fn extract(source: &str) -> super::Document {
269-
let path = Path::new("test.tq");
269+
let path = Path::new("Test.tq");
270270
let doc = parsing::parse(path, source).unwrap();
271271
ProcedureAdapter.extract(&doc)
272272
}

src/domain/recipe/adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ mod check {
341341
}
342342

343343
fn extract(source: &str) -> super::Document {
344-
let path = Path::new("test.tq");
344+
let path = Path::new("Test.tq");
345345
let doc = parsing::parse(path, source).unwrap();
346346
RecipeAdapter.extract(&doc)
347347
}

src/main.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -562,8 +562,8 @@ fn main() {
562562
};
563563

564564
match runner::start(filename, &program) {
565-
Ok((id, Outcome::Quit)) => {
566-
eprintln!("paused; resume with `technique resume {}`", id.render());
565+
Ok((run_id, Outcome::Quit)) => {
566+
eprintln!("paused; resume with `technique resume {}`", run_id.render());
567567
std::process::exit(0);
568568
}
569569
Ok((_, _)) => std::process::exit(0),
@@ -580,15 +580,15 @@ fn main() {
580580

581581
debug!(id);
582582

583-
let id = match RunId::parse(id) {
584-
Ok(id) => id,
583+
let run_id = match RunId::parse(id) {
584+
Ok(run_id) => run_id,
585585
Err(error) => {
586586
eprintln!("{}", problem::concise_runner_error(&error, &Terminal));
587587
std::process::exit(1);
588588
}
589589
};
590590

591-
let filename = match runner::locate(id) {
591+
let filename = match runner::locate(run_id) {
592592
Ok(path) => path,
593593
Err(error) => {
594594
eprintln!("{}", problem::concise_runner_error(&error, &Terminal));
@@ -644,9 +644,12 @@ fn main() {
644644
}
645645
};
646646

647-
match runner::resume(id, &program) {
647+
match runner::resume(run_id, &program) {
648648
Ok(Outcome::Quit) => {
649-
eprintln!("paused; continue with `technique resume {}`", id.render());
649+
eprintln!(
650+
"paused; continue with `technique resume {}`",
651+
run_id.render()
652+
);
650653
std::process::exit(0);
651654
}
652655
Ok(_) => std::process::exit(0),

src/parsing/checks/errors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::path::Path;
33

44
/// Helper function to check if parsing produces the expected error
55
fn expect_error(content: &str, expected: ParsingError) {
6-
let result = parse_with_recovery(Path::new("test.tq"), content);
6+
let result = parse_with_recovery(Path::new("Test.tq"), content);
77
match result {
88
Ok(_) => panic!(
99
"Expected parsing to fail, but it succeeded for input: {}",

src/parsing/checks/parser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2356,7 +2356,7 @@ broken :
23562356
1. Step one
23572357
"#;
23582358

2359-
let result = parse_with_recovery(Path::new("test.tq"), content);
2359+
let result = parse_with_recovery(Path::new("Test.tq"), content);
23602360

23612361
// Check that we get an error about the invalid signature
23622362
match result {

src/problem/messages.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,21 +1055,21 @@ pub fn generate_translation_error<'i>(
10551055
/// is being evaluated by the runner.
10561056
pub fn generate_runner_error(error: &RunnerError, _renderer: &dyn Render) -> (String, String) {
10571057
match error {
1058-
RunnerError::NoSuchRun(id) => (
1059-
format!("No such run '{:06}'", id.0),
1058+
RunnerError::NoSuchRun(run_id) => (
1059+
format!("No such run '{:06}'", run_id.0),
10601060
"The directory for this run identifier was not found in the local state store.".to_string(),
10611061
),
10621062
RunnerError::StoreError { path, error } => (
10631063
format!("I/O error with local state store at {}", path.display()),
10641064
format!("{}", error),
10651065
),
1066-
RunnerError::MalformedRecord { run, .. } => (
1067-
format!("Malformed record for run '{:06}'", run.0),
1066+
RunnerError::MalformedRecord { run_id, .. } => (
1067+
format!("Malformed record for run '{:06}'", run_id.0),
10681068
"The PFFTT state file for this run could not be parsed.".to_string(),
10691069
),
1070-
RunnerError::ManifestMissing(id) => (
1071-
format!("Manifest missing in run '{:06}'", id.0),
1072-
"The state file is present but its first tablet (the manifest) is missing or malformed.".to_string(),
1070+
RunnerError::StartMissing(run_id) => (
1071+
format!("Start record missing in run '{:06}'", run_id.0),
1072+
"The state file is present but its first record (the Start event) is missing or malformed.".to_string(),
10731073
),
10741074
RunnerError::InvalidRunId(text) => (
10751075
format!("Invalid run identifier '{}'", text),

src/runner/checks/path.rs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,39 @@ use crate::language::{Attribute, Identifier, Span};
22
use crate::runner::path::{PathSegment, QualifiedPath};
33

44
#[test]
5-
fn empty_stack_renders_empty_string() {
5+
fn empty_stack_renders_root() {
66
let stack = QualifiedPath::new();
7-
assert_eq!(stack.render(), "");
7+
assert_eq!(stack.render(), "/");
88
}
99

1010
#[test]
1111
fn single_section_renders_numeral() {
1212
let mut stack = QualifiedPath::new();
1313
stack.push(PathSegment::Section("I"));
14-
assert_eq!(stack.render(), "I");
14+
assert_eq!(stack.render(), "/I");
1515
}
1616

1717
#[test]
1818
fn section_then_step_joins_with_slash() {
1919
let mut stack = QualifiedPath::new();
2020
stack.push(PathSegment::Section("I"));
2121
stack.push(PathSegment::DependentStep("2"));
22-
assert_eq!(stack.render(), "I/2");
22+
assert_eq!(stack.render(), "/I/2");
2323
}
2424

2525
#[test]
2626
fn dependent_substep_chain() {
2727
let mut stack = QualifiedPath::new();
2828
stack.push(PathSegment::DependentStep("2"));
2929
stack.push(PathSegment::DependentStep("a"));
30-
assert_eq!(stack.render(), "2/a");
30+
assert_eq!(stack.render(), "/2/a");
3131
}
3232

3333
#[test]
3434
fn parallel_step_uses_dash_prefix() {
3535
let mut stack = QualifiedPath::new();
3636
stack.push(PathSegment::ParallelStep(3));
37-
assert_eq!(stack.render(), "-3");
37+
assert_eq!(stack.render(), "/-3");
3838
}
3939

4040
#[test]
@@ -46,7 +46,7 @@ fn attribute_frame_composes_role_and_place() {
4646
let mut stack = QualifiedPath::new();
4747
stack.push(PathSegment::Attributes(&frame));
4848
stack.push(PathSegment::DependentStep("a"));
49-
assert_eq!(stack.render(), "@chef^kitchen/a");
49+
assert_eq!(stack.render(), "/@chef^kitchen/a");
5050
}
5151

5252
#[test]
@@ -61,7 +61,7 @@ fn nested_attribute_frames_each_contribute_a_segment() {
6161
stack.push(PathSegment::Attributes(&outer));
6262
stack.push(PathSegment::Attributes(&inner));
6363
stack.push(PathSegment::DependentStep("a"));
64-
assert_eq!(stack.render(), "2/@team/@chef^kitchen/a");
64+
assert_eq!(stack.render(), "/2/@team/@chef^kitchen/a");
6565
}
6666

6767
#[test]
@@ -72,7 +72,7 @@ fn reset_role() {
7272
stack.push(PathSegment::DependentStep("1"));
7373
stack.push(PathSegment::Attributes(&frame));
7474
stack.push(PathSegment::DependentStep("a"));
75-
assert_eq!(stack.render(), "1/a");
75+
assert_eq!(stack.render(), "/1/a");
7676

7777
// A sibling Place attribute in the same frame still renders.
7878
let frame = vec![
@@ -83,34 +83,39 @@ fn reset_role() {
8383
stack.push(PathSegment::DependentStep("1"));
8484
stack.push(PathSegment::Attributes(&frame));
8585
stack.push(PathSegment::DependentStep("a"));
86-
assert_eq!(stack.render(), "1/^kitchen/a");
86+
assert_eq!(stack.render(), "/1/^kitchen/a");
8787
}
8888

8989
#[test]
9090
fn procedure_segment() {
91-
// Single procedure: name becomes the prefix, joined to the rest with `:`.
91+
// Procedure alone renders as `/name:`
92+
let mut stack = QualifiedPath::new();
93+
stack.push(PathSegment::Procedure("local_network"));
94+
assert_eq!(stack.render(), "/local_network:");
95+
96+
// Procedure with a step: `/name:N`.
9297
let mut stack = QualifiedPath::new();
9398
stack.push(PathSegment::Procedure("make_coffee"));
9499
stack.push(PathSegment::DependentStep("2"));
95-
assert_eq!(stack.render(), "make_coffee:2");
100+
assert_eq!(stack.render(), "/make_coffee:2");
96101

97102
// Nested procedure: the inner frame replaces the outer prefix entirely.
98103
let mut stack = QualifiedPath::new();
99104
stack.push(PathSegment::Procedure("outer"));
100105
stack.push(PathSegment::DependentStep("1"));
101106
stack.push(PathSegment::Procedure("inner"));
102107
stack.push(PathSegment::DependentStep("2"));
103-
assert_eq!(stack.render(), "inner:2");
108+
assert_eq!(stack.render(), "/inner:2");
104109
}
105110

106111
#[test]
107112
fn full_qualified_example_from_objective() {
108-
// 2/@barista/a/-1 — dependent step 2, role @barista, substep a, first parallel sub-substep
113+
// /2/@barista/a/-1 — dependent step 2, role @barista, substep a, first parallel sub-substep
109114
let frame = vec![Attribute::Role(Identifier::new("barista"), Span::default())];
110115
let mut stack = QualifiedPath::new();
111116
stack.push(PathSegment::DependentStep("2"));
112117
stack.push(PathSegment::Attributes(&frame));
113118
stack.push(PathSegment::DependentStep("a"));
114119
stack.push(PathSegment::ParallelStep(1));
115-
assert_eq!(stack.render(), "2/@barista/a/-1");
120+
assert_eq!(stack.render(), "/2/@barista/a/-1");
116121
}

0 commit comments

Comments
 (0)