#![allow(clippy::expect_used, clippy::panic)]
use std::fs;
use std::path::{Path, PathBuf};
use super::COMMANDS;
const DECLARED_DOCUMENTS: &[&str] = &[
"docs/src/guide/checkout/checkout.md",
"docs/src/guide/checkout/snapshot-checkout.md",
"docs/src/guide/checkout/snapshot-materialization.md",
"docs/src/guide/history.md",
"docs/src/guide/merge-evidence.md",
"docs/src/guide/merge.md",
"docs/src/guide/merge-plan.md",
"docs/src/guide/patches/patch-deletions.md",
"docs/src/guide/patches/patch-inverse.md",
"docs/src/guide/patches/patch-materialization.md",
"docs/src/guide/patches/patch-replay.md",
"docs/src/guide/patches/text-edits.md",
"docs/src/guide/patches/worktree-patch.md",
"docs/src/guide/rollback/rollback-draft.md",
"docs/src/guide/rollback/rollback-draft-verify.md",
"docs/src/guide/rollback/rollback-preview.md",
"docs/src/guide/rollback/sealed-rollback-history.md",
"docs/src/guide/security-setup.md",
"docs/src/guide/status.md",
"docs/src/guide/sync.md",
"docs/src/guide/worktree-status.md",
"docs/src/reference/architecture.md",
"docs/src/reference/concurrency-locking.md",
"docs/src/reference/data-model-lifecycle.md",
"docs/src/reference/data-model.md",
"docs/src/reference/integrity-recovery.md",
"docs/src/reference/patch-algebra.md",
"docs/src/reference/path-safety.md",
"docs/src/reference/platform-support.md",
"docs/src/reference/release-compatibility.md",
"docs/src/reference/repository-layout.md",
"docs/src/reference/trust-threat-model.md",
"README.md",
];
const DECLARED_UNDOCUMENTED: &[(&str, &str)] = &[];
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("prikk-cli's manifest dir has a workspace root two levels up")
.to_path_buf()
}
#[test]
fn every_declared_document_exists() {
let root = repo_root();
for path in DECLARED_DOCUMENTS {
assert!(
root.join(path).is_file(),
"declared document is missing: {path}"
);
}
}
fn code_regions(text: &str) -> Vec<&str> {
let mut fenced_ranges = Vec::new();
let mut regions = Vec::new();
let mut offset = 0usize;
while let Some(rel_start) = text[offset..].find("```") {
let start = offset + rel_start;
let after_open = start + 3;
let Some(rel_end) = text[after_open..].find("```") else {
regions.push(&text[start..]);
fenced_ranges.push((start, text.len()));
break;
};
let end = after_open + rel_end + 3;
regions.push(&text[start..end]);
fenced_ranges.push((start, end));
offset = end;
}
let mut offset = 0usize;
while let Some(rel_start) = text[offset..].find('`') {
let start = offset + rel_start;
if fenced_ranges
.iter()
.any(|&(range_start, range_end)| start >= range_start && start < range_end)
{
offset = start + 1;
continue;
}
let Some(rel_end) = text[start + 1..].find('`') else {
break;
};
let end = start + 1 + rel_end + 1;
regions.push(&text[start..end]);
offset = end;
}
regions
}
fn command_tokens(region: &str) -> Vec<&str> {
let mut tokens = Vec::new();
let mut rest = region;
while let Some(rel) = rest.find("prikk ") {
let after = &rest[rel + "prikk ".len()..];
let token_len = after
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '-'))
.unwrap_or(after.len());
let token = &after[..token_len];
if token
.chars()
.next()
.is_some_and(|first| first.is_ascii_alphabetic())
{
tokens.push(token);
}
let advance = token_len.max(1).min(after.len());
rest = &after[advance..];
}
tokens
}
fn strip_readme_command_listing(text: &str) -> String {
let Some(start) = text.find("## Useful Commands") else {
return text.to_string();
};
let after_heading = &text[start + "## Useful Commands".len()..];
let end = after_heading
.find("\n## ")
.map_or(text.len(), |rel| start + "## Useful Commands".len() + rel);
let mut result = String::with_capacity(text.len());
result.push_str(&text[..start]);
result.push_str(&text[end..]);
result
}
fn document_text(root: &Path, path: &str) -> String {
let text = fs::read_to_string(root.join(path))
.unwrap_or_else(|err| panic!("declared document {path} must read: {err}"));
if path == "README.md" {
strip_readme_command_listing(&text)
} else {
text
}
}
#[test]
fn rule_a_every_documented_command_names_a_real_registry_entry() {
let root = repo_root();
for path in DECLARED_DOCUMENTS {
let text = fs::read_to_string(root.join(path))
.unwrap_or_else(|err| panic!("declared document {path} must read: {err}"));
for region in code_regions(&text) {
for token in command_tokens(region) {
assert!(
COMMANDS.iter().any(|command| command.name == token),
"{path} documents `prikk {token}`, which is not a real registry command name"
);
}
}
}
}
fn is_explained(root: &Path, name: &str) -> bool {
DECLARED_DOCUMENTS.iter().any(|path| {
let text = document_text(root, path);
code_regions(&text)
.iter()
.any(|region| command_tokens(region).contains(&name))
})
}
#[test]
fn rule_b_every_registry_entry_is_explained_or_declared_undocumented() {
let root = repo_root();
for command in COMMANDS {
if DECLARED_UNDOCUMENTED
.iter()
.any(|(name, _)| *name == command.name)
{
continue;
}
assert!(
is_explained(&root, command.name),
"`{}` is neither explained in a declared document outside README's bare command \
listing, nor declared undocumented with a reason in DECLARED_UNDOCUMENTED",
command.name
);
}
}
#[test]
fn declared_undocumented_names_are_real_registry_entries() {
for (name, reason) in DECLARED_UNDOCUMENTED {
assert!(
COMMANDS.iter().any(|command| &command.name == name),
"DECLARED_UNDOCUMENTED names `{name}`, which is not a real registry command"
);
assert!(
!reason.trim().is_empty(),
"DECLARED_UNDOCUMENTED entry for `{name}` has no reason"
);
}
}