use std::fs;
use std::path::{Path, PathBuf};
fn markdown_sources() -> Vec<PathBuf> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut sources = vec![root.join("README.md")];
collect_markdown(&root.join("docs"), &mut sources);
sources.sort();
sources
}
fn collect_markdown(dir: &Path, found: &mut Vec<PathBuf>) {
let entries =
fs::read_dir(dir).unwrap_or_else(|error| panic!("read {}: {error}", dir.display()));
for entry in entries {
let path = entry.expect("directory entry").path();
if path.is_dir() {
collect_markdown(&path, found);
} else if path.extension().is_some_and(|extension| extension == "md") {
found.push(path);
}
}
}
struct Block {
file: PathBuf,
line: usize,
body: String,
}
impl Block {
fn location(&self) -> String {
let name = self
.file
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_default();
format!("{name}:{}", self.line)
}
}
fn yaml_blocks(file: &Path) -> Vec<Block> {
let text =
fs::read_to_string(file).unwrap_or_else(|error| panic!("read {}: {error}", file.display()));
let mut blocks = Vec::new();
let mut open: Option<(usize, usize, Vec<String>)> = None;
for (index, line) in text.lines().enumerate() {
let trimmed = line.trim_start();
let ticks = trimmed
.chars()
.take_while(|character| *character == '`')
.count();
match &mut open {
Some((fence, start, body)) => {
if ticks >= *fence && trimmed[ticks..].trim().is_empty() {
blocks.push(Block {
file: file.to_path_buf(),
line: *start,
body: body.join("\n"),
});
open = None;
} else {
body.push(line.to_owned());
}
}
None if ticks >= 3 && trimmed[ticks..].trim() == "yaml" => {
open = Some((ticks, index + 1, Vec::new()));
}
None => {}
}
}
assert!(
open.is_none(),
"{}: unterminated fenced block",
file.display()
);
blocks
}
fn looks_like_a_flow(body: &str) -> bool {
body.lines().any(|line| {
let trimmed = line.trim_start();
trimmed == "stages:" || trimmed.starts_with("- name:")
})
}
const MINIMUM_DOCUMENTED_FLOWS: usize = 7;
#[test]
fn every_documented_flow_parses() {
let sources = markdown_sources();
assert!(
sources.len() >= 5,
"expected the documentation set, found {sources:?}"
);
let mut parsed = 0;
for file in &sources {
for block in yaml_blocks(file) {
if !looks_like_a_flow(&block.body) {
continue;
}
let name = block.location();
if let Err(error) = sloop::flow::parse(&name, &block.body) {
panic!(
"{}: documented flow does not parse: {error}\n{}",
block.file.display(),
block.body
);
}
parsed += 1;
}
}
assert!(
parsed >= MINIMUM_DOCUMENTED_FLOWS,
"only {parsed} documented flows were found; expected at least \
{MINIMUM_DOCUMENTED_FLOWS}"
);
}