cargo_docs_md/multi_crate/
summary.rs1use std::fmt::Write;
7use std::path::Path;
8
9use fs_err as FsErr;
10use rustdoc_types::{ItemEnum, Visibility};
11
12use crate::error::Error;
13use crate::multi_crate::CrateCollection;
14
15pub struct SummaryGenerator<'a> {
32 crates: &'a CrateCollection,
34
35 output_dir: &'a Path,
37
38 include_private: bool,
40}
41
42impl<'a> SummaryGenerator<'a> {
43 #[must_use]
51 pub const fn new(
52 crates: &'a CrateCollection,
53 output_dir: &'a Path,
54 include_private: bool,
55 ) -> Self {
56 Self {
57 crates,
58 output_dir,
59 include_private,
60 }
61 }
62
63 pub fn generate(&self) -> Result<(), Error> {
69 let mut content = String::from("# Summary\n\n");
70
71 for (crate_name, krate) in self.crates.iter() {
72 _ = writeln!(content, "- [{crate_name}]({crate_name}/index.md)");
74
75 if let Some(root) = krate.index.get(&krate.root)
77 && let ItemEnum::Module(module) = &root.inner
78 {
79 self.add_modules(&mut content, krate, &module.items, crate_name, 1);
80 }
81 }
82
83 let summary_path = self.output_dir.join("SUMMARY.md");
84 FsErr::write(&summary_path, content).map_err(Error::FileWrite)?;
85
86 Ok(())
87 }
88
89 fn add_modules(
91 &self,
92 content: &mut String,
93 krate: &rustdoc_types::Crate,
94 items: &[rustdoc_types::Id],
95 path_prefix: &str,
96 indent: usize,
97 ) {
98 let mut modules: Vec<_> = items
100 .iter()
101 .filter_map(|id| krate.index.get(id))
102 .filter(|item| matches!(&item.inner, ItemEnum::Module(_)))
103 .filter(|item| {
104 self.include_private || matches!(item.visibility, Visibility::Public)
106 })
107 .collect();
108
109 modules.sort_by_key(|item| item.name.as_deref().unwrap_or(""));
110
111 for item in modules {
112 let name = item.name.as_deref().unwrap_or("unnamed");
113 let indent_str = " ".repeat(indent);
114 let link_path = format!("{path_prefix}/{name}/index.md");
115
116 _ = writeln!(content, "{indent_str}- [{name}]({link_path})");
117
118 if let ItemEnum::Module(module) = &item.inner {
120 let child_prefix = format!("{path_prefix}/{name}");
121
122 self.add_modules(content, krate, &module.items, &child_prefix, indent + 1);
123 }
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_summary_generation() {
134 let crates = CrateCollection::new();
136 let temp_dir = std::env::temp_dir();
137 let generator = SummaryGenerator::new(&crates, &temp_dir, false);
138
139 assert!(generator.crates.is_empty());
141 }
142
143 #[test]
144 fn test_summary_respects_include_private() {
145 let crates = CrateCollection::new();
147 let temp_dir = std::env::temp_dir();
148
149 let public_only = SummaryGenerator::new(&crates, &temp_dir, false);
150 assert!(!public_only.include_private);
151
152 let with_private = SummaryGenerator::new(&crates, &temp_dir, true);
153 assert!(with_private.include_private);
154 }
155}