use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use super::super::result::CheckResult;
use super::rel_unix;
pub(crate) fn find_plain_file(root: &Path, rel: &str) -> Option<PathBuf> {
let p = root.join(rel);
if p.is_file() {
Some(p)
} else {
None
}
}
pub(crate) fn find_rule_files(root: &Path, dir_rel: &str) -> Vec<PathBuf> {
let mut out = Vec::new();
let dir = root.join(dir_rel);
if dir.is_dir() {
if let Ok(entries) = fs::read_dir(&dir) {
let mut names: Vec<_> = entries.flatten().collect();
names.sort_by_key(|e| e.file_name());
for entry in names {
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|e| e == "md") {
out.push(path);
}
}
}
}
out
}
pub(crate) fn check_plain_rule_md(
root: &Path,
path: &Path,
check_id: &str,
empty_hint: &str,
) -> Result<CheckResult> {
let raw = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
let raw = super::strip_bom(&raw);
let rel = rel_unix(root, path);
if raw.trim().is_empty() {
return Ok(CheckResult::fail(
check_id,
"rule file is non-empty",
format!("{rel} is empty"),
empty_hint,
));
}
if raw.trim_start().starts_with("---") {
return Ok(CheckResult::pass(
check_id,
"rule file validates",
format!("{rel} validates (frontmatter + body)"),
));
}
let first_non_blank = raw.lines().find(|l| !l.trim().is_empty());
match first_non_blank {
Some(line) if line.trim_start().starts_with('#') => Ok(CheckResult::pass(
check_id,
"rule file validates",
format!("{rel} validates"),
)),
Some(_) => Ok(CheckResult::warn(
check_id,
"rule file starts with a `#` heading",
"first non-blank line is not a markdown heading",
"To fix: start the file with `# <tool name>`.",
)),
None => Ok(CheckResult::fail(
check_id,
"rule file is non-empty",
"file contains only blank lines",
"To fix: add instructions content.",
)),
}
}
pub(crate) fn check_plain_md(
root: &Path,
path: &Path,
check_id: &str,
label: &str,
empty_hint: &str,
) -> Result<CheckResult> {
let raw = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
let raw = super::strip_bom(&raw);
let rel = rel_unix(root, path);
if raw.trim_start().starts_with("---") {
return Ok(CheckResult::fail(
check_id,
&format!("{label} is plain markdown (no frontmatter)"),
"file starts with a `---` frontmatter block",
format!("To fix: remove the frontmatter block. {label} is plain markdown."),
));
}
if raw.trim().is_empty() {
return Ok(CheckResult::fail(
check_id,
"file is non-empty",
format!("{label} is empty"),
empty_hint,
));
}
let first_non_blank = raw.lines().find(|l| !l.trim().is_empty());
match first_non_blank {
Some(line) if line.trim_start().starts_with('#') => Ok(CheckResult::pass(
check_id,
&format!("{label} file validates"),
format!("{rel} validates"),
)),
Some(_) => Ok(CheckResult::warn(
check_id,
"file starts with a `#` heading",
"first non-blank line is not a markdown heading",
"To fix: start the file with `# <tool name>`.",
)),
None => Ok(CheckResult::fail(
check_id,
"file is non-empty",
"file contains only blank lines",
"To fix: add instructions content.",
)),
}
}