1crate::ix!();
3
4#[derive(Serialize,Deserialize,Clone,Getters,Debug)]
8#[getset(get="pub")]
9pub struct ModuleInterface {
10 docs: Option<String>,
11 attrs: Option<String>,
12 mod_name: String,
13 items: Vec<ConsolidatedItem>,
14
15 file_path: PathBuf,
17
18 crate_path: PathBuf,
20
21 raw_range: TextRange,
23
24 effective_range: TextRange,
26}
27
28impl ModuleInterface {
29 pub fn new_with_paths_and_range(
30 docs: Option<String>,
31 attrs: Option<String>,
32 mod_name: String,
33 file_path: PathBuf,
34 crate_path: PathBuf,
35 raw_range: TextRange,
36 effective_range: TextRange,
37 ) -> Self {
38 let x = Self {
39 docs,
40 attrs,
41 mod_name,
42 items: vec![],
43 file_path,
44 crate_path,
45 raw_range,
46 effective_range,
47 };
48 trace!("created ModuleInterface: {:#?}", x);
49 x
50 }
51
52 #[cfg(test)]
53 pub fn new_for_test(
54 docs: Option<String>,
55 attrs: Option<String>,
56 mod_name: String,
57 ) -> Self {
58 Self::new_with_paths_and_range(
59 docs,
60 attrs,
61 mod_name,
62 PathBuf::from("TEST_ONLY_file_path.rs"),
63 PathBuf::from("TEST_ONLY_crate_path"),
64 TextRange::new(0.into(), 0.into()),
65 TextRange::new(0.into(), 0.into()),
66 )
67 }
68
69 pub fn add_item(&mut self, item: ConsolidatedItem) {
70 self.items.push(item);
71 }
72
73 pub fn text_range(&self) -> &TextRange {
75 &self.effective_range
76 }
77
78 fn write_attrs(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 if let Some(attrs) = &self.attrs {
81 for line in attrs.lines() {
82 writeln!(f, "{}", line)?;
83 }
84 }
85 Ok(())
86 }
87
88 fn write_docs(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 if let Some(doc_text) = &self.docs {
94 if doc_text.trim().is_empty() && !doc_text.is_empty() {
98 write!(f, " \n\n")?;
100 } else {
101 for line in doc_text.lines() {
103 writeln!(f, "{}", line)?;
104 }
105 }
106 }
107 Ok(())
108 }
109
110 fn write_items(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 for (i, item) in self.items.iter().enumerate() {
117 let item_str = format!("{}", item);
118 let lines: Vec<&str> = item_str.lines().collect();
119
120 if lines.len() == 3 && lines[2].trim() == "}" {
123 writeln!(f, " {}", lines[0])?;
124 writeln!(f, " {}", lines[1])?;
125 writeln!(f, "{}", lines[2])?;
126 } else {
127 for line in lines {
129 writeln!(f, " {}", line)?;
130 }
131 }
132
133 if i + 1 < self.items.len() {
135 writeln!(f)?;
136 }
137 }
138 Ok(())
139 }
140}
141
142impl fmt::Display for ModuleInterface {
143
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 if self.items.is_empty() {
147 return Ok(());
148 }
149
150 self.write_attrs(f)?;
152
153 self.write_docs(f)?;
155
156 writeln!(f, "mod {} {{", self.mod_name)?;
158
159 self.write_items(f)?;
161
162 writeln!(f, "}}")?;
164 Ok(())
165 }
166}
167
168#[cfg(test)]
169mod test_module_interface {
170 use super::*;
171
172 #[test]
178 fn test_display_no_items_produces_empty_output() {
179 let module = ModuleInterface::new_for_test(None, None, "empty_mod".to_string());
180 let output = format!("{}", module);
181 assert!(
182 output.is_empty(),
183 "Expected empty output when `items` is empty"
184 );
185 }
186
187 #[test]
192 fn test_display_with_items_no_docs_no_attrs() {
193 let mut module = ModuleInterface::new_for_test(None, None, "my_mod".to_string());
194 module.add_item(ConsolidatedItem::MockTest("fn example() {}".to_string()));
195 let output = format!("{}", module);
196
197 let expected = r#"mod my_mod {
198 fn example() {}
199}
200"#;
201 assert_eq!(output, expected, "Output should wrap the item in mod block");
202 }
203
204 #[test]
206 fn test_display_with_docs_and_attrs() {
207 let docs = Some("/// This is my module\n/// Another doc line".to_string());
208 let attrs = Some("#[allow(dead_code)]\n#[cfg(feature = \"test\")]".to_string());
209 let mut module = ModuleInterface::new_for_test(docs.clone(), attrs.clone(), "my_mod".to_string());
210 module.add_item(ConsolidatedItem::MockTest("struct MyStruct;".to_string()));
211
212 let output = format!("{}", module);
213
214 let expected = r#"#[allow(dead_code)]
216#[cfg(feature = "test")]
217/// This is my module
218/// Another doc line
219mod my_mod {
220 struct MyStruct;
221}
222"#;
223 assert_eq!(
224 output, expected,
225 "Docs & attrs should appear before mod declaration, each on its own line"
226 );
227 }
228
229 #[test]
231 fn test_display_with_multiple_items_spacing() {
232 let mut module = ModuleInterface::new_for_test(None, None, "multi_mod".to_string());
233 module.add_item(ConsolidatedItem::MockTest("// Item 1".to_string()));
234 module.add_item(ConsolidatedItem::MockTest("// Item 2".to_string()));
235 module.add_item(ConsolidatedItem::MockTest("// Item 3".to_string()));
236
237 let output = format!("{}", module);
238
239 let expected = r#"mod multi_mod {
243 // Item 1
244
245 // Item 2
246
247 // Item 3
248}
249"#;
250 assert_eq!(output, expected);
251 }
252
253 #[test]
256 fn test_indentation_and_single_item_line_spacing() {
257 let mut module = ModuleInterface::new_for_test(None, None, "indented_mod".to_string());
258 module.add_item(ConsolidatedItem::MockTest("fn test_fn() {\nprintln!(\"Hello\");\n}".to_string()));
259
260 let output = format!("{}", module);
261 let expected = r#"mod indented_mod {
262 fn test_fn() {
263 println!("Hello");
264}
265}
266"#;
267 assert_eq!(
268 output, expected,
269 "Each line of the item should be indented by 4 spaces"
270 );
271 }
272
273 #[test]
275 fn test_multi_line_docs_and_attrs_verbatim() {
276 let docs = Some("//! First doc line\n//! Second doc line".to_string());
277 let attrs = Some("#![allow(unused)]\n#![no_std]".to_string());
278 let mut module = ModuleInterface::new_for_test(docs.clone(), attrs.clone(), "verbatim_mod".to_string());
279 module.add_item(ConsolidatedItem::MockTest("fn foo() {}".to_string()));
280
281 let output = format!("{}", module);
282 let expected = r#"#![allow(unused)]
283#![no_std]
284//! First doc line
285//! Second doc line
286mod verbatim_mod {
287 fn foo() {}
288}
289"#;
290 assert_eq!(
291 output, expected,
292 "Should preserve line-by-line printing of docs and attrs"
293 );
294 }
295
296 #[test]
298 fn test_doc_attr_whitespace_still_printed() {
299 let docs = Some(" ".to_string()); let attrs = Some("".to_string()); let mut module = ModuleInterface::new_for_test(docs.clone(), attrs.clone(), "white_mod".to_string());
302 module.add_item(ConsolidatedItem::MockTest("fn white() {}".to_string()));
303
304 let output = format!("{}", module);
305 let expected = r#"
306
307mod white_mod {
308 fn white() {}
309}
310"#;
311 assert_eq!(output, expected);
314 }
315
316 #[test]
318 fn test_multi_line_item_indentation() {
319 let mut module = ModuleInterface::new_for_test(None, None, "lines_mod".to_string());
320 let item_content = "/// item doc line\npub fn multiline() {\n // body\n}";
322 module.add_item(ConsolidatedItem::MockTest(item_content.to_string()));
323
324 let output = format!("{}", module);
325 let expected = r#"mod lines_mod {
326 /// item doc line
327 pub fn multiline() {
328 // body
329 }
330}
331"#;
332 assert_eq!(
333 output, expected,
334 "Each line in the item should be indented by 4 spaces"
335 );
336 }
337}