cargo_docs_md/multi_crate/collection.rs
1//! Crate collection for multi-crate documentation.
2//!
3//! This module provides [`CrateCollection`], a container for multiple parsed
4//! rustdoc crates that maintains a consistent processing order.
5
6use std::collections::HashMap;
7
8use rustdoc_types::Crate;
9
10/// Collection of parsed crates ready for documentation generation.
11///
12/// Uses `HashMap` for O(1) lookups and sorts keys on-demand when iteration
13/// is needed. This is optimal for our use case where:
14/// - All crates are inserted first (parsing phase)
15/// - Sorted iteration happens later (generation phase)
16/// - Collection size is small (typically 10-50 crates)
17///
18/// # Example
19///
20/// ```ignore
21/// let mut collection = CrateCollection::new();
22/// collection.insert("tracing".to_string(), tracing_crate);
23/// collection.insert("tracing_core".to_string(), tracing_core_crate);
24///
25/// for (name, krate) in collection.iter() {
26/// println!("Processing {name}");
27/// }
28/// ```
29#[derive(Debug, Default)]
30pub struct CrateCollection {
31 /// Map from crate name to parsed Crate data.
32 /// `HashMap` provides O(1) lookups; sorting done on-demand.
33 crates: HashMap<String, Crate>,
34}
35
36impl CrateCollection {
37 /// Create an empty crate collection.
38 #[must_use]
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 /// Insert a crate into the collection.
44 ///
45 /// If a crate with the same name already exists, it is replaced
46 /// and `Some(old_crate)` is returned.
47 pub fn insert(&mut self, name: String, krate: Crate) -> Option<Crate> {
48 self.crates.insert(name, krate)
49 }
50
51 /// Get a crate by name.
52 #[must_use]
53 pub fn get(&self, name: &str) -> Option<&Crate> {
54 self.crates.get(name)
55 }
56
57 /// Get a crate by name, returning the stored key as well.
58 ///
59 /// This is useful when you need a reference to the crate name that
60 /// has the same lifetime as the collection.
61 #[must_use]
62 pub fn get_with_name(&self, name: &str) -> Option<(&str, &Crate)> {
63 self.crates
64 .get_key_value(name)
65 .map(|(k, v)| (k.as_str(), v))
66 }
67
68 /// Check if a crate exists in the collection.
69 #[must_use]
70 pub fn contains(&self, name: &str) -> bool {
71 self.crates.contains_key(name)
72 }
73
74 /// Iterate over crates in alphabetical order.
75 ///
76 /// Returns tuples of `(&crate_name, &Crate)` sorted alphabetically
77 /// by crate name for deterministic output.
78 ///
79 /// Sorting is done on-demand since collection size is small (10-50 crates).
80 pub fn iter(&self) -> impl Iterator<Item = (&String, &Crate)> {
81 let mut entries: Vec<_> = self.crates.iter().collect();
82 entries.sort_by_key(|(name, _)| *name);
83 entries.into_iter()
84 }
85
86 /// Get the number of crates in the collection.
87 #[must_use]
88 pub fn len(&self) -> usize {
89 self.crates.len()
90 }
91
92 /// Check if the collection is empty.
93 #[must_use]
94 pub fn is_empty(&self) -> bool {
95 self.crates.is_empty()
96 }
97
98 /// Get crate names in alphabetical order.
99 ///
100 /// Returns a sorted `Vec` of crate names for deterministic processing.
101 /// Sorting is done on-demand since collection size is small.
102 #[must_use]
103 pub fn names(&self) -> Vec<&String> {
104 let mut names: Vec<_> = self.crates.keys().collect();
105 names.sort();
106 names
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_insert_and_get() {
116 let collection = CrateCollection::new();
117
118 // Create a minimal mock crate (we can't easily construct a real one)
119 // In practice, these come from parsing JSON files
120 assert!(collection.is_empty());
121 assert_eq!(collection.len(), 0);
122 }
123
124 #[test]
125 fn test_names_returns_sorted() {
126 let collection = CrateCollection::new();
127 // Order should be maintained alphabetically
128 let names = collection.names();
129 let mut sorted = names.clone();
130 sorted.sort();
131 assert_eq!(names, sorted);
132 }
133}