1use std::collections::BTreeMap;
2use std::fs;
3use std::path::Path;
4
5use crate::rules::{RuleRegistry, Severity};
6
7pub fn generate_docs() {
8 let registry = RuleRegistry::new();
9 let ids = registry.get_rule_ids();
10
11 let mut categories: BTreeMap<&str, Vec<(&str, &str, &str)>> = BTreeMap::new();
12
13 for id in &ids {
14 if let Some(rule) = registry.get_rule(id) {
15 let meta = rule.meta();
16 categories.entry(meta.category).or_default().push((
17 meta.id,
18 meta.description,
19 severity_str(&meta.default_severity),
20 ));
21 }
22 }
23
24 let docs_dir = Path::new("docs/rules");
25 fs::create_dir_all(docs_dir).expect("failed to create docs/rules");
26
27 for id in &ids {
28 if let Some(rule) = registry.get_rule(id) {
29 let meta = rule.meta();
30 let filename = format!("{}.md", meta.id);
31 let path = docs_dir.join(&filename);
32 let content = format!(
33 concat!(
34 "# {}\n\n",
35 "- **Category:** {}\n",
36 "- **Default severity:** {}\n",
37 "- **Auto-fix:** {}\n\n",
38 "{}\n",
39 ),
40 meta.id,
41 meta.category,
42 severity_str(&meta.default_severity),
43 if rule.has_fix() { "Yes" } else { "No" },
44 meta.description,
45 );
46 fs::write(&path, content).expect("failed to write rule doc");
47 println!("wrote {}", path.display());
48 }
49 }
50
51 let mut index = String::from("# Rules\n\nAll rules organized by category.\n\n");
52 for (category, rules) in &categories {
53 index.push_str(&format!("## {}\n\n", capitalize(category)));
54 index.push_str("| Rule | Default | Description |\n|------|---------|-------------|\n");
55 for (id, desc, sev) in rules {
56 index.push_str(&format!("| [{}]({}.md) | {} | {} |\n", id, id, sev, desc));
57 }
58 index.push('\n');
59 }
60
61 let index_path = docs_dir.join("README.md");
62 fs::write(&index_path, index).expect("failed to write index");
63 println!("wrote {}", index_path.display());
64}
65
66fn severity_str(s: &Severity) -> &str {
67 match s {
68 Severity::Error => "error",
69 Severity::Warning => "warning",
70 Severity::Off => "off",
71 }
72}
73
74fn capitalize(s: &str) -> String {
75 let mut chars = s.chars();
76 match chars.next() {
77 None => String::new(),
78 Some(c) => c.to_uppercase().to_string() + chars.as_str(),
79 }
80}