ethers-rs/ethers-solc/src/resolver/tree.rs

170 lines
4.5 KiB
Rust

use crate::Graph;
use std::{collections::HashSet, io, io::Write, str::FromStr};
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub enum Charset {
// when operating in a console on windows non-UTF-8 byte sequences are not supported on
// stdout, See also [`StdoutLock`]
#[cfg_attr(not(target_os = "windows"), default)]
Utf8,
#[cfg_attr(target_os = "windows", default)]
Ascii,
}
impl FromStr for Charset {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"utf8" => Ok(Charset::Utf8),
"ascii" => Ok(Charset::Ascii),
s => Err(format!("invalid charset: {s}")),
}
}
}
/// Options to configure formatting
#[derive(Debug, Clone, Default)]
pub struct TreeOptions {
/// The style of characters to use.
pub charset: Charset,
/// If `true`, duplicate imports will be repeated.
/// If `false`, duplicates are suffixed with `(*)`, and their imports
/// won't be shown.
pub no_dedupe: bool,
}
/// Internal helper type for symbols
struct Symbols {
down: &'static str,
tee: &'static str,
ell: &'static str,
right: &'static str,
}
static UTF8_SYMBOLS: Symbols = Symbols { down: "", tee: "", ell: "", right: "" };
static ASCII_SYMBOLS: Symbols = Symbols { down: "|", tee: "|", ell: "`", right: "-" };
pub fn print(graph: &Graph, opts: &TreeOptions, out: &mut dyn Write) -> io::Result<()> {
let symbols = match opts.charset {
Charset::Utf8 => &UTF8_SYMBOLS,
Charset::Ascii => &ASCII_SYMBOLS,
};
// used to determine whether to display `(*)`
let mut visited_imports = HashSet::new();
// A stack of bools used to determine where | symbols should appear
// when printing a line.
let mut levels_continue = Vec::new();
// used to detect dependency cycles when --no-dedupe is used.
// contains a `Node` for each level.
let mut write_stack = Vec::new();
for (node_index, _) in graph.input_nodes().enumerate() {
print_node(
graph,
node_index,
symbols,
opts.no_dedupe,
&mut visited_imports,
&mut levels_continue,
&mut write_stack,
out,
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn print_node(
graph: &Graph,
node_index: usize,
symbols: &Symbols,
no_dedupe: bool,
visited_imports: &mut HashSet<usize>,
levels_continue: &mut Vec<bool>,
write_stack: &mut Vec<usize>,
out: &mut dyn Write,
) -> io::Result<()> {
let new_node = no_dedupe || visited_imports.insert(node_index);
if let Some((last_continues, rest)) = levels_continue.split_last() {
for continues in rest {
let c = if *continues { symbols.down } else { " " };
write!(out, "{c} ")?;
}
let c = if *last_continues { symbols.tee } else { symbols.ell };
write!(out, "{0}{1}{1} ", c, symbols.right)?;
}
let in_cycle = write_stack.contains(&node_index);
// if this node does not have any outgoing edges, don't include the (*)
// since there isn't really anything "deduplicated", and it generally just
// adds noise.
let has_deps = graph.has_outgoing_edges(node_index);
let star = if (new_node && !in_cycle) || !has_deps { "" } else { " (*)" };
writeln!(out, "{}{star}", graph.display_node(node_index))?;
if !new_node || in_cycle {
return Ok(())
}
write_stack.push(node_index);
print_imports(
graph,
node_index,
symbols,
no_dedupe,
visited_imports,
levels_continue,
write_stack,
out,
)?;
write_stack.pop();
Ok(())
}
/// Prints all the imports of a node
#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
fn print_imports(
graph: &Graph,
node_index: usize,
symbols: &Symbols,
no_dedupe: bool,
visited_imports: &mut HashSet<usize>,
levels_continue: &mut Vec<bool>,
write_stack: &mut Vec<usize>,
out: &mut dyn Write,
) -> io::Result<()> {
let imports = graph.imported_nodes(node_index);
if imports.is_empty() {
return Ok(())
}
let mut iter = imports.iter().peekable();
while let Some(import) = iter.next() {
levels_continue.push(iter.peek().is_some());
print_node(
graph,
*import,
symbols,
no_dedupe,
visited_imports,
levels_continue,
write_stack,
out,
)?;
levels_continue.pop();
}
Ok(())
}