crate_paths_cli_core/
tree.rs

1use 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            // item_entry.path is the path within the crate, e.g., "module::submodule::ItemName" or "ItemName"
39            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}