use std::collections::BTreeSet;
use std::path::PathBuf;
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.canonicalize()
.expect("repo root resolves")
}
fn guard_state_labels() -> BTreeSet<String> {
use keyhog_core::guard_state::GuardRootState;
let mut labels = BTreeSet::new();
labels.insert(GuardRootState::Indexing.label().to_string());
labels.insert(GuardRootState::Current.label().to_string());
labels.insert(GuardRootState::Dirty.label().to_string());
labels.insert(GuardRootState::Blocked.label().to_string());
labels.insert(GuardRootState::Degraded.label().to_string());
labels.insert(GuardRootState::StalePolicy.label().to_string());
labels.insert(GuardRootState::Stopped.label().to_string());
labels
}
fn guard_mode_labels() -> BTreeSet<String> {
use keyhog_core::guard_state::GuardRootMode;
let mut labels = BTreeSet::new();
labels.insert(GuardRootMode::Repo.label().to_string());
labels.insert(GuardRootMode::Filesystem.label().to_string());
labels
}
fn scanner_residency_labels() -> BTreeSet<String> {
["active", "resident", "idle-unload"]
.iter()
.map(|s| s.to_string())
.collect()
}
fn shipped_markdown_docs(root: &std::path::Path) -> Vec<PathBuf> {
let mut docs = Vec::new();
let readme = root.join("README.md");
if readme.is_file() {
docs.push(readme);
}
let changelog = root.join("CHANGELOG.md");
if changelog.is_file() {
docs.push(changelog);
}
let docs_dir = root.join("docs");
if docs_dir.is_dir() {
if let Ok(entries) = std::fs::read_dir(&docs_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "md") {
docs.push(path);
}
}
}
}
docs
}
fn guard_state_tokens(text: &str) -> Vec<(String, String)> {
let valid = guard_state_labels();
let mut found = Vec::new();
for word in text.split_whitespace() {
let cleaned = word
.trim_matches(|c: char| !c.is_alphanumeric() && c != '-')
.to_lowercase();
if cleaned.is_empty() {
continue;
}
if valid.contains(&cleaned) {
found.push((cleaned.clone(), word.to_string()));
}
let no_hyphen = cleaned.replace('-', "");
for valid_label in &valid {
if valid_label.replace('-', "") == no_hyphen && *valid_label != cleaned {
found.push((valid_label.clone(), word.to_string()));
}
}
}
found
}
#[test]
fn shipped_docs_guard_state_labels_match_code() {
let root = repo_root();
let docs = shipped_markdown_docs(&root);
assert!(!docs.is_empty(), "no shipped markdown docs found");
let valid_labels = guard_state_labels();
let mode_labels = guard_mode_labels();
let residency_labels = scanner_residency_labels();
for doc_path in &docs {
let text = std::fs::read_to_string(doc_path)
.unwrap_or_else(|_| panic!("read {}", doc_path.display()));
for (label, original) in guard_state_tokens(&text) {
assert!(
valid_labels.contains(&label),
"{}: guard state token '{}' (from '{}') is not a valid GuardRootState label",
doc_path.display(),
label,
original
);
}
for word in text.split_whitespace() {
let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '-');
if w.eq_ignore_ascii_case("stalepolicy")
&& !w.contains('-')
&& !w.chars().next().is_some_and(|c| c.is_uppercase())
{
panic!(
"{}: contains '{}' (lowercase, missing hyphen); the correct label is 'stale-policy'",
doc_path.display(),
word
);
}
}
let _ = &mode_labels;
let _ = &residency_labels;
}
}
#[test]
fn guard_state_labels_are_stable() {
use keyhog_core::guard_state::GuardRootState;
assert_eq!(GuardRootState::Indexing.label(), "indexing");
assert_eq!(GuardRootState::Current.label(), "current");
assert_eq!(GuardRootState::Dirty.label(), "dirty");
assert_eq!(GuardRootState::Blocked.label(), "blocked");
assert_eq!(GuardRootState::Degraded.label(), "degraded");
assert_eq!(GuardRootState::StalePolicy.label(), "stale-policy");
assert_eq!(GuardRootState::Stopped.label(), "stopped");
}
#[test]
fn guard_mode_labels_are_stable() {
use keyhog_core::guard_state::GuardRootMode;
assert_eq!(GuardRootMode::Repo.label(), "repo");
assert_eq!(GuardRootMode::Filesystem.label(), "filesystem");
}