crate_paths_cli_core/
tree.rs1use crate::item::ItemEntry;
2use std::{
3 collections::BTreeMap,
4 io::Write as _,
5 process::{Command, Stdio},
6};
7
8#[derive(Clone, Debug, Default)]
9struct ModuleScope {
10 items: Vec<ItemEntry>,
11 submodules: BTreeMap<String, ModuleScope>,
12}
13
14pub struct ModTree {
15 items: Vec<ItemEntry>,
16}
17
18impl ModTree {
19 pub fn new(items: Vec<ItemEntry>) -> Self {
20 Self { items }
21 }
22
23 pub fn to_string_list(&self) -> String {
24 self.items
25 .iter()
26 .map(|item| {
27 let kind_str = format!("{:?}", item.kinds());
28 format!("• {} {}", item.path(), kind_str)
29 })
30 .collect::<Vec<String>>()
31 .join("\n")
32 }
33
34 pub fn to_rust_module_string(&self) -> String {
35 let mut root_scope = ModuleScope::default();
36
37 for item_entry in &self.items {
38 let path_components: Vec<&str> = item_entry.path().split("::").collect();
40
41 let mut current_scope = &mut root_scope;
42
43 for &module_name_str in path_components
44 .iter()
45 .take(path_components.len().saturating_sub(1))
46 {
47 current_scope = current_scope
48 .submodules
49 .entry(module_name_str.to_string())
50 .or_default();
51 }
52
53 current_scope.items.push(item_entry.clone());
54 }
55
56 let mut lines = Vec::<String>::new();
57 lines.push("#![allow(dead_code, non_upper_case_globals)]".to_owned());
58
59 format_scope_recursive(&root_scope, &mut lines);
60
61 let raw = if lines.is_empty() {
62 String::new()
63 } else {
64 lines.join(" ")
65 };
66
67 format_with_rustfmt(&raw).unwrap_or(raw)
68 }
69}
70
71fn format_scope_recursive(scope: &ModuleScope, lines: &mut Vec<String>) {
72 if !scope.items.is_empty() {
73 for item_entry in &scope.items {
74 lines.push(item_entry.into_writable());
75 }
76 }
77
78 for (module_name, sub_scope) in &scope.submodules {
79 lines.push(format!("pub mod {} {{", module_name));
80 format_scope_recursive(sub_scope, lines);
81 lines.push('}'.to_string());
82 }
83}
84
85fn format_with_rustfmt(code: &str) -> std::io::Result<String> {
86 let mut child = Command::new("rustfmt")
87 .args(["--emit", "stdout"])
88 .stdin(Stdio::piped())
89 .stdout(Stdio::piped())
90 .spawn()?;
91
92 {
93 let stdin = child.stdin.as_mut().expect("Failed to open stdin");
94 stdin.write_all(code.as_bytes())?;
95 }
96
97 let output = child.wait_with_output()?;
98
99 if output.status.success() {
100 Ok(String::from_utf8_lossy(&output.stdout).to_string())
101 } else {
102 Err(std::io::Error::other(
103 String::from_utf8_lossy(&output.stderr).to_string(),
104 ))
105 }
106}