1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use colored::*;

use crate::{replacer::Fragment, Replacement};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verbosity {
    Quiet,
    Normal,
}

impl Default for Verbosity {
    fn default() -> Self {
        Verbosity::Normal
    }
}

#[derive(Debug, Default)]
/// Used to print messages to the console according to a Verbosity
/// level
pub struct Console {
    verbosity: Verbosity,
}

impl Console {
    /// Create a new console with the given verbosity
    pub fn with_verbosity(verbosity: Verbosity) -> Self {
        Self { verbosity }
    }

    /// Create a new console with default verbosity
    pub fn new() -> Self {
        Default::default()
    }

    /// Print a message to the console
    /// (using standard output)
    pub fn print_message(&self, message: &str) {
        if matches!(self.verbosity, Verbosity::Quiet) {
            return;
        }
        print!("{message}");
    }

    /// Print an error message to the console
    /// (using standard error)
    pub fn print_error(&self, error: &str) {
        eprintln!("{error}");
    }

    /// Print the replacement as two lines (red then green)
    /// ```
    /// use ruplacer::{Console, Query, replace};
    /// let input = "let foo_bar = FooBar::new();";
    /// let query = Query::subvert("foo_bar", "spam_eggs");
    /// let replacement = replace(input, &query).unwrap();
    /// let console = Console::new();
    /// console.print_replacement("foo.rs:3", &replacement);
    /// // outputs:
    /// // foo.rs:3 let foo_bar = FooBar::new()
    /// // foo.rs:3 let spam_eggs = SpamEggs::new()
    /// ```
    pub fn print_replacement(&self, prefix: &str, replacement: &Replacement) {
        let red_underline = { |x: &str| x.red().underline() };
        let fragments = replacement.fragments();
        let input_fragments = fragments.into_iter().map(|x| &x.0);
        let red_prefix = format!("{}{}", prefix, "- ".red());
        self.print_fragments(
            &red_prefix,
            red_underline,
            replacement.input(),
            input_fragments,
        );

        let green_underline = { |x: &str| x.green().underline() };
        let green_prefix = format!("{}{}", prefix, "+ ".green());
        let output_fragments = fragments.into_iter().map(|x| &x.1);
        self.print_fragments(
            &green_prefix,
            green_underline,
            replacement.output(),
            output_fragments,
        );
    }

    fn print_fragments<'f, C>(
        &self,
        prefix: &str,
        color: C,
        line: &str,
        fragments: impl Iterator<Item = &'f Fragment>,
    ) where
        C: Fn(&str) -> ColoredString,
    {
        self.print_message(prefix);
        let mut current_index = 0;
        for (i, fragment) in fragments.enumerate() {
            let Fragment { index, text } = fragment;
            // Whitespace between prefix and the first fragment does not matter
            if i == 0 {
                self.print_message((&line[current_index..*index].trim_start()).as_ref());
            } else {
                self.print_message(&line[current_index..*index]);
            }
            self.print_message(&format!("{}", color(text)));
            current_index = index + text.len();
        }
        self.print_message(&line[current_index..]);
        if !line.ends_with('\n') {
            self.print_message("\n");
        }
    }
}