1use crate::cleanup::{apply_backtrack, apply_spoken_commands, deterministic_light, guard_accepts, Level};
7
8pub trait Formatter {
13 fn format(&self, level: Level, text: &str) -> String;
14}
15
16pub struct DeterministicFormatter;
19
20impl Formatter for DeterministicFormatter {
21 fn format(&self, _level: Level, text: &str) -> String {
25 deterministic_light(text)
26 }
27}
28
29pub fn guarded_format(f: &dyn Formatter, level: Level, raw: &str) -> String {
34 let pre = apply_backtrack(&apply_spoken_commands(raw));
35 if level == Level::None {
36 return pre;
37 }
38 let candidate = f.format(level, &pre);
39 if guard_accepts(&pre, &candidate) {
40 candidate
41 } else {
42 deterministic_light(&pre)
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use crate::test_support::{Faithful, OverEditing};
50
51 #[test]
52 fn none_level_returns_pre_layer_unchanged() {
53 assert_eq!(guarded_format(&Faithful, Level::None, "i am not done"), "i am not done");
54 }
55
56 #[test]
57 fn pre_layer_runs_before_formatting() {
58 let out = guarded_format(&Faithful, Level::Light, "the answer is yes scratch that the answer is no");
59 assert!(!out.contains("yes"));
60 assert!(out.contains("answer is no"));
61 }
62
63 #[test]
64 fn guard_rejects_a_meaning_substitution_and_falls_back() {
65 let out = guarded_format(&OverEditing, Level::Light, "i love her");
66 assert!(!out.contains("hate"));
67 assert!(out.to_lowercase().contains("love"));
68 }
69
70 #[test]
71 fn guard_rejects_a_dropped_negation_and_falls_back() {
72 let out = guarded_format(&OverEditing, Level::Light, "i am not angry");
73 assert!(out.to_lowercase().contains("not"));
74 }
75
76 #[test]
77 fn faithful_output_passes_the_guard_unchanged() {
78 assert_eq!(guarded_format(&Faithful, Level::Light, "um so i keep avoiding it"), "So I keep avoiding it.");
79 }
80
81 #[test]
82 fn guard_fires_at_medium_too() {
83 let out = guarded_format(&OverEditing, Level::Medium, "i love her");
86 assert!(!out.contains("hate"));
87 assert!(out.to_lowercase().contains("love"));
88 }
89}