foundry_compilers/resolver/
tree.rs1use crate::{compilers::ParsedSource, Graph};
2use std::{collections::HashSet, io, io::Write, str::FromStr};
3
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5pub enum Charset {
6 #[cfg_attr(not(target_os = "windows"), default)]
9 Utf8,
10 #[cfg_attr(target_os = "windows", default)]
11 Ascii,
12}
13
14impl FromStr for Charset {
15 type Err = String;
16
17 fn from_str(s: &str) -> Result<Self, Self::Err> {
18 match s {
19 "utf8" => Ok(Self::Utf8),
20 "ascii" => Ok(Self::Ascii),
21 s => Err(format!("invalid charset: {s}")),
22 }
23 }
24}
25
26#[derive(Clone, Debug, Default)]
28pub struct TreeOptions {
29 pub charset: Charset,
31 pub no_dedupe: bool,
35}
36
37struct Symbols {
39 down: &'static str,
40 tee: &'static str,
41 ell: &'static str,
42 right: &'static str,
43}
44
45static UTF8_SYMBOLS: Symbols = Symbols { down: "│", tee: "├", ell: "└", right: "─" };
46
47static ASCII_SYMBOLS: Symbols = Symbols { down: "|", tee: "|", ell: "`", right: "-" };
48
49pub fn print<D: ParsedSource>(
50 graph: &Graph<D>,
51 opts: &TreeOptions,
52 out: &mut dyn Write,
53) -> io::Result<()> {
54 let symbols = match opts.charset {
55 Charset::Utf8 => &UTF8_SYMBOLS,
56 Charset::Ascii => &ASCII_SYMBOLS,
57 };
58
59 let mut visited_imports = HashSet::new();
61
62 let mut levels_continue = Vec::new();
65 let mut write_stack = Vec::new();
68
69 for (node_index, _) in graph.input_nodes().enumerate() {
70 print_node(
71 graph,
72 node_index,
73 symbols,
74 opts.no_dedupe,
75 &mut visited_imports,
76 &mut levels_continue,
77 &mut write_stack,
78 out,
79 )?;
80 }
81
82 Ok(())
83}
84
85#[allow(clippy::too_many_arguments)]
86fn print_node<D: ParsedSource>(
87 graph: &Graph<D>,
88 node_index: usize,
89 symbols: &Symbols,
90 no_dedupe: bool,
91 visited_imports: &mut HashSet<usize>,
92 levels_continue: &mut Vec<bool>,
93 write_stack: &mut Vec<usize>,
94 out: &mut dyn Write,
95) -> io::Result<()> {
96 let new_node = no_dedupe || visited_imports.insert(node_index);
97
98 if let Some((last_continues, rest)) = levels_continue.split_last() {
99 for continues in rest {
100 let c = if *continues { symbols.down } else { " " };
101 write!(out, "{c} ")?;
102 }
103
104 let c = if *last_continues { symbols.tee } else { symbols.ell };
105 write!(out, "{0}{1}{1} ", c, symbols.right)?;
106 }
107
108 let in_cycle = write_stack.contains(&node_index);
109 let has_deps = graph.has_outgoing_edges(node_index);
113 let star = if (new_node && !in_cycle) || !has_deps { "" } else { " (*)" };
114
115 writeln!(out, "{}{star}", graph.display_node(node_index))?;
116
117 if !new_node || in_cycle {
118 return Ok(());
119 }
120 write_stack.push(node_index);
121
122 print_imports(
123 graph,
124 node_index,
125 symbols,
126 no_dedupe,
127 visited_imports,
128 levels_continue,
129 write_stack,
130 out,
131 )?;
132
133 write_stack.pop();
134
135 Ok(())
136}
137
138#[allow(clippy::too_many_arguments)]
140fn print_imports<D: ParsedSource>(
141 graph: &Graph<D>,
142 node_index: usize,
143 symbols: &Symbols,
144 no_dedupe: bool,
145 visited_imports: &mut HashSet<usize>,
146 levels_continue: &mut Vec<bool>,
147 write_stack: &mut Vec<usize>,
148 out: &mut dyn Write,
149) -> io::Result<()> {
150 let imports = graph.imported_nodes(node_index);
151 if imports.is_empty() {
152 return Ok(());
153 }
154
155 let mut iter = imports.iter().peekable();
156
157 while let Some(import) = iter.next() {
158 levels_continue.push(iter.peek().is_some());
159 print_node(
160 graph,
161 *import,
162 symbols,
163 no_dedupe,
164 visited_imports,
165 levels_continue,
166 write_stack,
167 out,
168 )?;
169 levels_continue.pop();
170 }
171
172 Ok(())
173}