use headwater_cli::command;
use std::collections::BTreeSet;
use std::path::Path;
fn parsed_forms() -> BTreeSet<String> {
fn walk(prefix: &str, command: &clap::Command, found: &mut BTreeSet<String>) {
let mut leaf = true;
for word in command.get_subcommands() {
leaf = false;
walk(&format!("{prefix} {}", word.get_name()), word, found);
}
if leaf {
found.insert(prefix.to_string());
}
}
let mut command = command();
command.build();
let mut found = BTreeSet::new();
walk(headwater_verbs::BINARY, &command, &mut found);
found
}
#[test]
fn the_parser_and_the_table_call_this_binary_the_same_thing() {
assert_eq!(command().get_name(), headwater_verbs::BINARY);
}
#[test]
fn the_parser_answers_to_exactly_the_command_lines_the_table_carries() {
let parsed = parsed_forms();
assert!(
parsed.len() > 20,
"the walk of the command tree found {} command lines, which is too few to be this surface",
parsed.len()
);
let declared: BTreeSet<String> = headwater_verbs::VERBS
.iter()
.flat_map(|verb| verb.forms())
.collect();
let unparsed: Vec<&String> = declared.difference(&parsed).collect();
assert!(
unparsed.is_empty(),
"the dispatch table carries {unparsed:?} and the parser answers to no such command line"
);
let undeclared: Vec<&String> = parsed.difference(&declared).collect();
assert!(
undeclared.is_empty(),
"the parser answers to {undeclared:?} and the dispatch table carries no such command line"
);
}
#[test]
fn the_second_words_of_each_verb_are_the_second_words_the_parser_answers_to() {
let mut command = command();
command.build();
for verb in headwater_verbs::VERBS {
let found = command
.get_subcommands()
.find(|one| one.get_name() == verb.name);
let parsed: BTreeSet<&str> = found
.into_iter()
.flat_map(|one| one.get_subcommands())
.map(clap::Command::get_name)
.collect();
let declared: BTreeSet<&str> = verb.words.iter().map(|word| word.name).collect();
assert_eq!(
declared, parsed,
"`{}` declares different second words from the ones the parser answers to",
verb.name
);
}
}
const GRAMMAR_MAY_WAIT: &[&str] = &["coverage"];
fn spec_six_grammar_verbs() -> BTreeSet<String> {
let path =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../docs/spec/06-engine-architecture.md");
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let (_, after_heading) = text
.split_once("### CLI")
.unwrap_or_else(|| panic!("{}: no '### CLI' heading", path.display()));
let (_, after_open) = after_heading
.split_once("```")
.unwrap_or_else(|| panic!("{}: no fenced block after '### CLI'", path.display()));
let (block, _) = after_open.split_once("```").unwrap_or_else(|| {
panic!(
"{}: unterminated fenced block after '### CLI'",
path.display()
)
});
block
.lines()
.filter_map(|line| line.strip_prefix("headwater "))
.filter_map(|rest| rest.split_whitespace().next())
.map(str::to_string)
.collect()
}
#[test]
fn the_cli_grammar_of_spec_6_names_every_verb_this_binary_ships() {
let grammar = spec_six_grammar_verbs();
let shipped: BTreeSet<String> = headwater_verbs::VERBS
.iter()
.map(|verb| verb.name.to_string())
.collect();
let missing: Vec<&String> = shipped.difference(&grammar).collect();
assert!(
missing.is_empty(),
"docs/spec/06-engine-architecture.md's CLI grammar names no `headwater {missing:?}` \
line, and the paragraph under the block claims every shipped verb is above"
);
let waiting: BTreeSet<String> = GRAMMAR_MAY_WAIT
.iter()
.map(|name| name.to_string())
.collect();
let unexplained: Vec<&String> = grammar
.iter()
.filter(|name| !shipped.contains(name.as_str()) && !waiting.contains(name.as_str()))
.collect();
assert!(
unexplained.is_empty(),
"docs/spec/06-engine-architecture.md's CLI grammar names {unexplained:?}, which the \
binary does not ship and GRAMMAR_MAY_WAIT does not explain as a deliberate wait"
);
}
fn spec_six_grammar_block_for(verb: &str) -> String {
let path =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../docs/spec/06-engine-architecture.md");
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let (_, after_heading) = text
.split_once("### CLI")
.unwrap_or_else(|| panic!("{}: no '### CLI' heading", path.display()));
let (_, after_open) = after_heading
.split_once("```")
.unwrap_or_else(|| panic!("{}: no fenced block after '### CLI'", path.display()));
let (block, _) = after_open.split_once("```").unwrap_or_else(|| {
panic!(
"{}: unterminated fenced block after '### CLI'",
path.display()
)
});
let prefix = format!("headwater {verb}");
let mut lines = String::new();
let mut in_block = false;
for line in block.lines() {
if line.starts_with("headwater ") {
if in_block {
break;
}
in_block = line.starts_with(&prefix);
}
if in_block {
lines.push_str(line);
lines.push('\n');
}
}
lines
}
fn spec_six_grammar_sub_words(verb: &str) -> BTreeSet<String> {
let block = spec_six_grammar_block_for(verb);
let mut depth = 0i32;
let mut segments: Vec<String> = vec![String::new()];
for ch in block.chars() {
match ch {
'[' => {
depth += 1;
segments.last_mut().unwrap().push(ch);
}
']' => {
depth -= 1;
segments.last_mut().unwrap().push(ch);
}
'|' if depth == 0 => segments.push(String::new()),
_ => segments.last_mut().unwrap().push(ch),
}
}
let prefix = format!("headwater {verb}");
segments
.iter()
.enumerate()
.filter_map(|(index, segment)| {
let rest = if index == 0 {
segment.strip_prefix(prefix.as_str()).unwrap_or(segment)
} else {
segment.as_str()
};
rest.split_whitespace().next().map(str::to_string)
})
.collect()
}
#[test]
fn the_second_words_of_spec_6s_grammar_lines_are_the_second_words_verbs_carries() {
for verb in headwater_verbs::VERBS {
if verb.words.is_empty() {
continue;
}
let grammar = spec_six_grammar_sub_words(verb.name);
let declared: BTreeSet<String> = verb
.words
.iter()
.map(|word| word.name.to_string())
.collect();
assert_eq!(
declared, grammar,
"`{}`'s spec 6 grammar line names different second words from the ones \
`headwater_verbs::VERBS` carries for it",
verb.name
);
}
}
#[test]
fn the_verb_index_names_exactly_the_verbs_this_binary_dispatches() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../docs/interfaces/README.md");
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let indexed: BTreeSet<&str> = text
.lines()
.filter_map(|line| line.strip_prefix("| `"))
.filter_map(|rest| rest.split_once("` |"))
.map(|(name, _)| name)
.collect();
let declared: BTreeSet<&str> = headwater_verbs::VERBS
.iter()
.map(|verb| verb.name)
.collect();
assert_eq!(
declared, indexed,
"the verb index and the dispatch table name different verbs; \
`headwater generate` writes that file"
);
}