Skip to main content

big_code_analysis/output/
dump_ops.rs

1use std::io::Write;
2use termcolor::{Color, ColorChoice, StandardStream, StandardStreamLock};
3
4use crate::ops::Ops;
5
6use crate::tools::{color, intense_color};
7
8/// Dumps all operands and operators of a code.
9///
10/// Returns a [`Result`] value, when an error occurs.
11///
12/// # Errors
13///
14/// Propagates any [`std::io::Error`] produced by the color-aware
15/// writer that backs `stdout` (broken pipe, write failure, …).
16///
17/// # Examples
18///
19/// ```
20/// use std::path::PathBuf;
21///
22/// use big_code_analysis::{dump_ops, get_ops, LANG};
23///
24/// let source_code = "int a = 42;";
25/// let path = PathBuf::from("foo.c");
26/// let source_as_vec = source_code.as_bytes().to_vec();
27///
28/// // Retrieve all operands and operators via the non-generic
29/// // `get_ops` entry point.
30/// let ops = get_ops(&LANG::Cpp, source_as_vec, &path, None).unwrap();
31///
32/// // Dump all operands and operators
33/// dump_ops(&ops).unwrap();
34/// ```
35///
36/// [`Result`]: #variant.Result
37pub fn dump_ops(ops: &Ops) -> std::io::Result<()> {
38    let stdout = StandardStream::stdout(ColorChoice::Always);
39    let mut stdout = stdout.lock();
40    dump_space(ops, "", true, &mut stdout)?;
41    color(&mut stdout, Color::White)?;
42
43    Ok(())
44}
45
46fn dump_space(
47    space: &Ops,
48    prefix: &str,
49    last: bool,
50    stdout: &mut StandardStreamLock,
51) -> std::io::Result<()> {
52    let (pref_child, pref) = if last { ("   ", "`- ") } else { ("|  ", "|- ") };
53
54    color(stdout, Color::Blue)?;
55    write!(stdout, "{prefix}{pref}")?;
56
57    intense_color(stdout, Color::Yellow)?;
58    write!(stdout, "{}: ", space.kind)?;
59
60    intense_color(stdout, Color::Cyan)?;
61    write!(stdout, "{}", space.name.as_ref().map_or("", |name| name))?;
62
63    intense_color(stdout, Color::Red)?;
64    writeln!(stdout, " (@{})", space.start_line)?;
65
66    let prefix = format!("{prefix}{pref_child}");
67    dump_space_ops(space, &prefix, space.spaces.is_empty(), stdout)?;
68
69    if let Some((last, spaces)) = space.spaces.split_last() {
70        for space in spaces {
71            dump_space(space, &prefix, false, stdout)?;
72        }
73        dump_space(last, &prefix, true, stdout)?;
74    }
75
76    Ok(())
77}
78
79fn dump_space_ops(
80    ops: &Ops,
81    prefix: &str,
82    last: bool,
83    stdout: &mut StandardStreamLock,
84) -> std::io::Result<()> {
85    dump_ops_values("operators", &ops.operators, prefix, last, stdout)?;
86    dump_ops_values("operands", &ops.operands, prefix, last, stdout)
87}
88
89fn dump_ops_values(
90    name: &str,
91    ops: &[String],
92    prefix: &str,
93    last: bool,
94    stdout: &mut StandardStreamLock,
95) -> std::io::Result<()> {
96    let (pref_child, pref) = if last { ("   ", "`- ") } else { ("|  ", "|- ") };
97
98    color(stdout, Color::Blue)?;
99    write!(stdout, "{prefix}{pref}")?;
100
101    intense_color(stdout, Color::Green)?;
102    writeln!(stdout, "{name}")?;
103
104    let Some((last_op, rest)) = ops.split_last() else {
105        return Ok(());
106    };
107
108    let prefix = format!("{prefix}{pref_child}");
109    for op in rest {
110        color(stdout, Color::Blue)?;
111        write!(stdout, "{prefix}|- ")?;
112
113        color(stdout, Color::White)?;
114        writeln!(stdout, "{op}")?;
115    }
116
117    color(stdout, Color::Blue)?;
118    write!(stdout, "{prefix}`- ")?;
119
120    color(stdout, Color::White)?;
121    writeln!(stdout, "{last_op}")
122}
123
124#[cfg(test)]
125#[allow(
126    clippy::float_cmp,
127    clippy::cast_precision_loss,
128    clippy::cast_possible_truncation,
129    clippy::cast_sign_loss,
130    clippy::similar_names,
131    clippy::doc_markdown,
132    clippy::needless_raw_string_hashes,
133    clippy::too_many_lines
134)]
135mod tests {
136    use super::*;
137    use crate::spaces::SpaceKind;
138
139    #[test]
140    fn dump_ops_empty_operators_and_operands_does_not_panic() {
141        // Regression: `ops.len() - 1` underflowed (usize) when ops was empty,
142        // then `ops.last().unwrap()` panicked. A space with no Halstead
143        // operators or operands is a realistic input.
144        let ops = Ops {
145            name: Some("unit".to_string()),
146            name_was_lossy: false,
147            start_line: 1,
148            end_line: 1,
149            kind: SpaceKind::Unit,
150            spaces: vec![],
151            operands: vec![],
152            operators: vec![],
153        };
154        assert!(dump_ops(&ops).is_ok());
155    }
156}