ethers_solc/resolver/
tree.rs1use crate::Graph;
2use std::{collections::HashSet, io, io::Write, str::FromStr};
3
4#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
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(Charset::Utf8),
20 "ascii" => Ok(Charset::Ascii),
21 s => Err(format!("invalid charset: {s}")),
22 }
23 }
24}
25
26#[derive(Debug, Clone, 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(graph: &Graph, opts: &TreeOptions, out: &mut dyn Write) -> io::Result<()> {
50 let symbols = match opts.charset {
51 Charset::Utf8 => &UTF8_SYMBOLS,
52 Charset::Ascii => &ASCII_SYMBOLS,
53 };
54
55 let mut visited_imports = HashSet::new();
57
58 let mut levels_continue = Vec::new();
61 let mut write_stack = Vec::new();
64
65 for (node_index, _) in graph.input_nodes().enumerate() {
66 print_node(
67 graph,
68 node_index,
69 symbols,
70 opts.no_dedupe,
71 &mut visited_imports,
72 &mut levels_continue,
73 &mut write_stack,
74 out,
75 )?;
76 }
77
78 Ok(())
79}
80
81#[allow(clippy::too_many_arguments)]
82fn print_node(
83 graph: &Graph,
84 node_index: usize,
85 symbols: &Symbols,
86 no_dedupe: bool,
87 visited_imports: &mut HashSet<usize>,
88 levels_continue: &mut Vec<bool>,
89 write_stack: &mut Vec<usize>,
90 out: &mut dyn Write,
91) -> io::Result<()> {
92 let new_node = no_dedupe || visited_imports.insert(node_index);
93
94 if let Some((last_continues, rest)) = levels_continue.split_last() {
95 for continues in rest {
96 let c = if *continues { symbols.down } else { " " };
97 write!(out, "{c} ")?;
98 }
99
100 let c = if *last_continues { symbols.tee } else { symbols.ell };
101 write!(out, "{0}{1}{1} ", c, symbols.right)?;
102 }
103
104 let in_cycle = write_stack.contains(&node_index);
105 let has_deps = graph.has_outgoing_edges(node_index);
109 let star = if (new_node && !in_cycle) || !has_deps { "" } else { " (*)" };
110
111 writeln!(out, "{}{star}", graph.display_node(node_index))?;
112
113 if !new_node || in_cycle {
114 return Ok(())
115 }
116 write_stack.push(node_index);
117
118 print_imports(
119 graph,
120 node_index,
121 symbols,
122 no_dedupe,
123 visited_imports,
124 levels_continue,
125 write_stack,
126 out,
127 )?;
128
129 write_stack.pop();
130
131 Ok(())
132}
133
134#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
136fn print_imports(
137 graph: &Graph,
138 node_index: usize,
139 symbols: &Symbols,
140 no_dedupe: bool,
141 visited_imports: &mut HashSet<usize>,
142 levels_continue: &mut Vec<bool>,
143 write_stack: &mut Vec<usize>,
144 out: &mut dyn Write,
145) -> io::Result<()> {
146 let imports = graph.imported_nodes(node_index);
147 if imports.is_empty() {
148 return Ok(())
149 }
150
151 let mut iter = imports.iter().peekable();
152
153 while let Some(import) = iter.next() {
154 levels_continue.push(iter.peek().is_some());
155 print_node(
156 graph,
157 *import,
158 symbols,
159 no_dedupe,
160 visited_imports,
161 levels_continue,
162 write_stack,
163 out,
164 )?;
165 levels_continue.pop();
166 }
167
168 Ok(())
169}