mdbook_check_missing_md/
lib.rs

1pub mod md_files;
2pub mod summary;
3
4use std::collections::HashSet;
5
6use mdbook::renderer::RenderContext;
7use anyhow::Error;
8
9use crate::md_files::collect_md_files;
10use crate::summary::get_summary_md_files;
11
12pub fn run(ctx: &RenderContext) -> Result<(), Error> {
13    let root = ctx.root.join("src");
14    let summary_path = root.join("SUMMARY.md");
15
16    let mut all_md_files = HashSet::new();
17    collect_md_files(&root, &mut all_md_files)?;
18
19    // Exclude SUMMARY.md from the set of markdown files
20    all_md_files.remove(&summary_path.canonicalize()?);
21
22    let summary_md_files = get_summary_md_files(&summary_path)?;
23
24    let missing_in_summary: HashSet<_> = all_md_files.difference(&summary_md_files).collect();
25
26    if !missing_in_summary.is_empty() {
27        eprintln!("Error: The following .md files are not listed in SUMMARY.md:");
28        for file in missing_in_summary {
29            eprintln!("{}", file.display());
30        }
31        std::process::exit(1);
32    }
33
34    println!("All files are listed in SUMMARY.md");
35    Ok(())
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use std::fs::{self, File};
42    use std::io::Write;
43    
44    use tempfile::tempdir;
45    use mdbook::renderer::RenderContext;
46    use mdbook::book::Book;
47    use mdbook::config::Config;
48    
49    #[test]
50    fn test_check_missing() {
51        let dir = tempdir().unwrap();
52        let src_dir = dir.path().join("src");
53        let summary_path = src_dir.join("SUMMARY.md");
54    
55        // Create files and directories
56        fs::create_dir_all(&src_dir).unwrap();
57        let md_files = vec!["intro.md", "chapter_1.md", "chapter_1/section_1.md"];
58        for file in &md_files {
59            let file_path = src_dir.join(file);
60            fs::create_dir_all(file_path.parent().unwrap()).unwrap();
61            File::create(&file_path).unwrap();
62        }
63    
64        // Create SUMMARY.md
65        let summary_content = r#"
66        * [Introduction](intro.md)
67        * [Chapter 1](chapter_1.md)
68            * [Section 1](chapter_1/section_1.md)
69        "#;
70        let mut summary_file = File::create(&summary_path).unwrap();
71        summary_file.write_all(summary_content.as_bytes()).unwrap();
72    
73        let ctx = RenderContext::new(
74            dir.path().to_path_buf(),
75            Book::new(),
76            Config::default(),
77            dir.path().to_path_buf()
78        );
79    
80        let result = run(&ctx);
81    
82        assert!(result.is_ok());
83    }
84}