#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn validate_cli_documentation(
binary_path: &str,
command: &[&str],
) -> Result<CliDocumentationReport> {
let mut report = CliDocumentationReport {
command: command.join(" "),
has_help: false,
has_usage_section: false,
has_options_section: false,
has_examples_section: false,
documented_flags: Vec::new(),
generic_descriptions: Vec::new(),
missing_descriptions: Vec::new(),
issues: Vec::new(),
};
let help_text = match execute_help_command(binary_path, command) {
Ok(text) => {
report.has_help = true;
text
}
Err(exit_code) => {
report
.issues
.push(format!("Command failed with exit code: {}", exit_code));
return Ok(report);
}
};
validate_sections(&help_text, &mut report);
report.documented_flags = extract_flags_from_help(&help_text);
report.generic_descriptions = find_generic_flag_descriptions(&help_text);
Ok(report)
}
fn execute_help_command(binary_path: &str, command: &[&str]) -> Result<String, i32> {
let mut cmd = Command::new(binary_path);
for arg in command {
cmd.arg(arg);
}
cmd.arg("--help");
let output = cmd.output().map_err(|_| -1)?;
if !output.status.success() {
return Err(output.status.code().unwrap_or(-1));
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
fn validate_sections(help_text: &str, report: &mut CliDocumentationReport) {
report.has_usage_section = help_text.contains("Usage:");
report.has_options_section = help_text.contains("Options:") || help_text.contains("FLAGS:");
report.has_examples_section = help_text.contains("EXAMPLE")
|| help_text.contains("Example")
|| help_text.contains("example");
if !report.has_usage_section {
report.issues.push("Missing 'Usage:' section".to_string());
}
if !report.has_options_section {
report
.issues
.push("Missing 'Options:' or 'FLAGS:' section".to_string());
}
}
fn find_generic_flag_descriptions(help_text: &str) -> Vec<String> {
let mut generic_descriptions = Vec::new();
for line in help_text.lines() {
if line.trim().is_empty() || line.ends_with(':') {
continue;
}
if line.trim_start().starts_with('-') {
if let Some(description) = extract_description_from_flag_line(line) {
if is_generic_description(&description) {
generic_descriptions.push(format!(
"Flag '{}': {}",
extract_flag_name(line),
description
));
}
}
}
}
generic_descriptions
}
fn extract_description_from_flag_line(line: &str) -> Option<String> {
let desc_part: Vec<&str> = line
.split_whitespace()
.skip_while(|w| w.starts_with('-') || w.starts_with('<') || w.starts_with('['))
.collect();
if desc_part.is_empty() {
return None;
}
let description = desc_part.join(" ");
if description.is_empty() {
None
} else {
Some(description)
}
}