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
use super::{Formatter, Logger, OsStr};
use std::fmt::{Display, Write};

pub use ansi_term::{Color, Style};

pub struct SyntaxHighLight<Prompt: Display> {
    prompt: Prompt,
    program: Style,
    argument: Style,
    short_flag: Style,
    long_flag: Style,
}

impl SyntaxHighLight<String> {
    const DEFAULT_PROMPT: &'static str = "$";

    pub fn default_colorless() -> Self {
        Self {
            prompt: Self::DEFAULT_PROMPT.to_owned(),
            program: Style::default(),
            argument: Style::default(),
            short_flag: Style::default(),
            long_flag: Style::default(),
        }
    }

    pub fn default_color() -> Self {
        Self {
            prompt: Style::default()
                .dimmed()
                .paint(Self::DEFAULT_PROMPT)
                .to_string(),
            program: Color::Green.into(),
            argument: Style::default(),
            short_flag: Color::Red.into(),
            long_flag: Color::Red.into(),
        }
    }
}

impl<Prompt: Display> Formatter for SyntaxHighLight<Prompt> {
    fn fmt(&self, program: impl AsRef<OsStr>, arguments: &[impl AsRef<OsStr>]) -> String {
        use pipe_trait::*;
        use shell_escape::unix::escape;
        let mut result = String::new();

        let prompt = format!("{}", self.prompt);
        if !prompt.is_empty() {
            write!(result, "{} ", prompt).expect("write prompt");
        }

        write!(
            result,
            "{}",
            program
                .as_ref()
                .to_string_lossy()
                .pipe(escape)
                .pipe(|x| self.program.paint(x))
        )
        .expect("write program name");

        for argument in arguments {
            let argument = argument.as_ref().to_string_lossy();
            let paint = |text: &str, style: &Style| {
                text.to_owned()
                    .pipe(std::borrow::Cow::from)
                    .pipe(escape)
                    .pipe(|x| style.paint(x))
            };
            let argument = if argument.starts_with("--") {
                let segments: Vec<_> = argument.splitn(2, '=').collect();
                match segments[..] {
                    [_] => paint(&argument, &self.long_flag),
                    [flag, val] => Style::default().paint(format!(
                        "{flag}{eq}{val}",
                        flag = paint(flag, &self.long_flag),
                        eq = self.argument.paint("="),
                        val = paint(val, &self.argument),
                    )),
                    _ => unreachable!(),
                }
            } else if argument.starts_with('-') {
                paint(&argument, &self.short_flag)
            } else {
                paint(&argument, &self.argument)
            };
            write!(result, " {}", argument).expect("write argument");
        }

        result
    }
}

impl<Prompt: Display> Logger for SyntaxHighLight<Prompt> {
    fn log(&self, program: impl AsRef<OsStr>, arguments: &[impl AsRef<OsStr>]) {
        eprintln!("{}", self.fmt(program, arguments))
    }
}