ethers_solc/resolver/
tree.rs

1use crate::Graph;
2use std::{collections::HashSet, io, io::Write, str::FromStr};
3
4#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
5pub enum Charset {
6    // when operating in a console on windows non-UTF-8 byte sequences are not supported on
7    // stdout, See also [`StdoutLock`]
8    #[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/// Options to configure formatting
27#[derive(Debug, Clone, Default)]
28pub struct TreeOptions {
29    /// The style of characters to use.
30    pub charset: Charset,
31    /// If `true`, duplicate imports will be repeated.
32    /// If `false`, duplicates are suffixed with `(*)`, and their imports
33    /// won't be shown.
34    pub no_dedupe: bool,
35}
36
37/// Internal helper type for symbols
38struct 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    // used to determine whether to display `(*)`
56    let mut visited_imports = HashSet::new();
57
58    // A stack of bools used to determine where | symbols should appear
59    // when printing a line.
60    let mut levels_continue = Vec::new();
61    // used to detect dependency cycles when --no-dedupe is used.
62    // contains a `Node` for each level.
63    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    // if this node does not have any outgoing edges, don't include the (*)
106    // since there isn't really anything "deduplicated", and it generally just
107    // adds noise.
108    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/// Prints all the imports of a node
135#[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}