spec_driven_docs/services/
reader.rs1use include_dir::Dir;
9
10#[derive(Debug, Clone, Copy)]
12pub struct Shelf {
13 pub dir: &'static Dir<'static>,
15 pub strip: Option<&'static str>,
17}
18
19pub const METHOD: Shelf = Shelf {
21 dir: &crate::embedded::METHOD,
22 strip: None,
23};
24pub const SPECS: Shelf = Shelf {
26 dir: &crate::embedded::SPECS,
27 strip: Some("SPEC-"),
28};
29pub const TEMPLATES: Shelf = Shelf {
31 dir: &crate::embedded::TEMPLATES,
32 strip: Some("TEMPLATE-"),
33};
34
35fn short_name<'a>(shelf: &Shelf, file_name: &'a str) -> Option<&'a str> {
36 let stem = file_name.strip_suffix(".md")?;
37 shelf
38 .strip
39 .map_or(Some(stem), |prefix| stem.strip_prefix(prefix))
40}
41
42#[must_use]
44pub fn list(shelf: &Shelf) -> Vec<String> {
45 let mut names: Vec<String> = shelf
46 .dir
47 .files()
48 .filter_map(|file| {
49 let name = file.path().as_os_str().to_str()?;
50 short_name(shelf, name).map(String::from)
51 })
52 .collect();
53 names.sort();
54 names
55}
56
57#[must_use]
59pub fn get(shelf: &Shelf, name: &str) -> Option<&'static str> {
60 let wanted = name.strip_suffix(".md").unwrap_or(name);
61 shelf.dir.files().find_map(|file| {
62 let file_name = file.path().as_os_str().to_str()?;
63 let stem = file_name.strip_suffix(".md")?;
64 let matches = stem == wanted || short_name(shelf, file_name) == Some(wanted);
65 if matches { file.contents_utf8() } else { None }
66 })
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn shelves_list_their_short_names() {
75 assert!(list(&METHOD).contains(&"glossary".to_string()));
76 assert!(list(&SPECS).contains(&"distribution".to_string()));
77 assert!(list(&TEMPLATES).contains(&"adr".to_string()));
78 }
79
80 #[test]
81 fn documents_resolve_by_short_and_full_names() {
82 let by_short = get(&SPECS, "distribution").unwrap();
83 assert_eq!(get(&SPECS, "SPEC-distribution").unwrap(), by_short);
84 assert_eq!(get(&SPECS, "SPEC-distribution.md").unwrap(), by_short);
85 assert!(by_short.contains("distribution:instances-operate-offline"));
86 assert!(get(&SPECS, "nonexistent").is_none());
87 }
88}