spec_driven_docs/
embedded.rs1use std::collections::BTreeSet;
10
11use include_dir::{Dir, include_dir};
12
13pub static SPECS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/_docs/specs");
15pub static TEMPLATES: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/templates");
17pub static MARKDOWNLINT: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/.markdownlint");
19pub static SNIPPETS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/instance/snippets");
21pub static METHOD: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/method");
23
24pub static LICENSE: &str = include_str!("../LICENSE");
26pub static LICENSE_MIT: &str = include_str!("../LICENSE-MIT");
28pub static LICENSE_CC_BY: &str = include_str!("../LICENSE-CC-BY-4.0");
30
31const SOURCE_ROOTS: &[(&str, &Dir<'static>)] = &[
32 ("_docs/specs/", &SPECS),
33 ("templates/", &TEMPLATES),
34 (".markdownlint/", &MARKDOWNLINT),
35 ("instance/snippets/", &SNIPPETS),
36];
37
38#[must_use]
41pub fn asset(source: &str) -> Option<&'static [u8]> {
42 SOURCE_ROOTS.iter().find_map(|(prefix, dir)| {
43 let rest = source.strip_prefix(prefix)?;
44 dir.get_file(rest).map(include_dir::File::contents)
45 })
46}
47
48#[must_use]
50pub fn spec_rule_ids() -> BTreeSet<String> {
51 let mut ids = BTreeSet::new();
52 for file in SPECS.files() {
53 let Some(text) = file.contents_utf8() else {
54 continue;
55 };
56 ids.extend(rule_ids_in(text));
57 }
58 ids
59}
60
61pub fn rule_ids_in(text: &str) -> impl Iterator<Item = String> + '_ {
63 text.lines().filter_map(|line| {
64 let candidate = line.strip_prefix("### `")?;
65 let (id, _) = candidate.split_once('`')?;
66 let (domain, rule) = id.split_once(':')?;
67 let is_slug = |part: &str| {
68 !part.is_empty()
69 && part
70 .bytes()
71 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
72 };
73 (is_slug(domain) && is_slug(rule)).then(|| id.to_string())
74 })
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::domain::profile::ProfileId;
81 use crate::domain::rule_id::RuleId;
82
83 #[test]
84 fn rule_id_enum_matches_the_embedded_specs() {
85 let from_specs = spec_rule_ids();
86 let from_enum: BTreeSet<String> = RuleId::ALL
87 .iter()
88 .map(|rule| rule.as_str().to_string())
89 .collect();
90 assert_eq!(
91 from_specs, from_enum,
92 "RuleId and the specs disagree; update the enum and the specs together"
93 );
94 }
95
96 #[test]
97 fn every_profile_projection_resolves_to_an_embedded_asset() {
98 for id in [ProfileId::Codebase, ProfileId::KnowledgeBase] {
99 let profile = id.profile();
100 for entry in profile.managed.iter().chain(profile.adopted) {
101 assert!(
102 asset(entry.source).is_some(),
103 "{id}: {} is not embedded",
104 entry.source
105 );
106 }
107 }
108 }
109
110 #[test]
111 fn the_method_and_licenses_are_carried() {
112 assert!(METHOD.get_file("glossary.md").is_some());
113 assert!(METHOD.files().count() >= 15);
114 assert!(LICENSE.contains("LICENSE-MIT") && LICENSE.contains("LICENSE-CC-BY-4.0"));
115 }
116
117 #[test]
118 fn rule_id_parser_matches_heading_shape_only() {
119 let text = "### `a-b:c-d` — Title\n### `Bad:Id`\nplain ### `x:y`\n### `no-colon`\n";
120 let ids: Vec<String> = rule_ids_in(text).collect();
121 assert_eq!(ids, vec!["a-b:c-d".to_string()]);
122 }
123}