]> code.octet-stream.net Git - hashgood/blob - src/display.rs
Add markers to highlight errors when ANSI colours are disabled
[hashgood] / src / display.rs
1 use super::{Algorithm, CandidateHash, Hash, MatchLevel, MessageLevel, VerificationSource};
2 use std::error::Error;
3 use std::io::Write;
4 use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
5
6 pub type PrintResult = Result<(), Box<dyn Error>>;
7
8 fn filename_display(filename: &str) -> &str {
9 if filename == "-" {
10 return "standard input";
11 }
12 filename
13 }
14
15 fn get_stdout(no_colour: bool) -> StandardStream {
16 if no_colour {
17 StandardStream::stdout(ColorChoice::Never)
18 } else {
19 StandardStream::stdout(ColorChoice::Always)
20 }
21 }
22
23 fn write_filename(mut stdout: &mut StandardStream, filename: &str) -> PrintResult {
24 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true))?;
25 write!(&mut stdout, "{}", filename_display(filename))?;
26 stdout.reset()?;
27 Ok(())
28 }
29
30 fn write_algorithm(mut stdout: &mut StandardStream, alg: Algorithm) -> PrintResult {
31 match alg {
32 Algorithm::Md5 => {
33 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Magenta)).set_bold(true))?;
34 write!(&mut stdout, "MD5")?;
35 }
36 Algorithm::Sha1 => {
37 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))?;
38 write!(&mut stdout, "SHA-1")?;
39 }
40 Algorithm::Sha256 => {
41 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
42 write!(&mut stdout, "SHA-256")?;
43 }
44 Algorithm::Sha512 => {
45 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)).set_bold(true))?;
46 write!(&mut stdout, "SHA-512")?;
47 }
48 }
49 stdout.reset()?;
50 Ok(())
51 }
52
53 fn print_hex_compare(
54 print: &[u8],
55 matches: &[bool],
56 mut stdout: &mut StandardStream,
57 ) -> PrintResult {
58 for (p, m) in print.iter().zip(matches.iter()) {
59 if *m {
60 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
61 } else {
62 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
63 }
64 write!(&mut stdout, "{:02x}", p)?;
65 }
66 stdout.reset()?;
67 writeln!(&mut stdout)?;
68 Ok(())
69 }
70
71 fn print_pointer_line(
72 matches: &[bool],
73 marker: &str,
74 mut stdout: &mut StandardStream,
75 ) -> PrintResult {
76 for m in matches {
77 if *m {
78 write!(&mut stdout, " ")?;
79 } else {
80 write!(&mut stdout, "{}{}", marker, marker)?;
81 }
82 }
83 write!(&mut stdout, "\n")?;
84 Ok(())
85 }
86
87 fn write_source(
88 mut stdout: &mut StandardStream,
89 verify_source: &VerificationSource,
90 candidate_filename: &Option<String>,
91 ) -> PrintResult {
92 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true))?;
93 match &verify_source {
94 VerificationSource::CommandArgument => {
95 writeln!(&mut stdout, "command line argument")?;
96 }
97 VerificationSource::RawFile(raw_path) => match raw_path.as_str() {
98 "-" => {
99 writeln!(&mut stdout, "from standard input")?;
100 }
101 path => {
102 writeln!(&mut stdout, "from file '{}' containing raw hash", path)?;
103 }
104 },
105 VerificationSource::DigestsFile(digest_path) => match digest_path.as_str() {
106 "-" => {
107 writeln!(
108 &mut stdout,
109 "'{}' from digests on standard input",
110 candidate_filename.as_ref().unwrap()
111 )?;
112 }
113 path => {
114 writeln!(
115 &mut stdout,
116 "'{}' in digests file '{}'",
117 candidate_filename.as_ref().unwrap(),
118 path
119 )?;
120 }
121 },
122 }
123 stdout.reset()?;
124 Ok(())
125 }
126
127 fn calculate_match_indices(bytes1: &[u8], bytes2: &[u8]) -> Vec<bool> {
128 bytes1
129 .iter()
130 .zip(bytes2.iter())
131 .map(|(b1, b2)| b1 == b2)
132 .collect()
133 }
134
135 pub fn print_hash(
136 hash: &Hash,
137 verify_hash: Option<&CandidateHash>,
138 verify_source: Option<&VerificationSource>,
139 no_colour: bool,
140 ) -> PrintResult {
141 let mut stdout = get_stdout(no_colour);
142
143 write_filename(&mut stdout, &hash.filename)?;
144 write!(&mut stdout, " / ")?;
145 write_algorithm(&mut stdout, hash.alg)?;
146 writeln!(&mut stdout)?;
147
148 // Handle basic case first - nothing to compare it to
149 let verify_hash = match verify_hash {
150 None => {
151 write!(&mut stdout, "{}\n\n", hex::encode(&hash.bytes))?;
152 return Ok(());
153 }
154 Some(verify_hash) => verify_hash,
155 };
156
157 // Do a top-to-bottom comparison
158 let matches = calculate_match_indices(&hash.bytes, &verify_hash.bytes);
159 let any_wrong = matches.iter().any(|m| !*m);
160
161 if any_wrong && no_colour {
162 print_pointer_line(&matches, "v", &mut stdout)?;
163 }
164 print_hex_compare(&hash.bytes, &matches, &mut stdout)?;
165 print_hex_compare(&verify_hash.bytes, &matches, &mut stdout)?;
166 if any_wrong && no_colour {
167 print_pointer_line(&matches, "^", &mut stdout)?;
168 }
169
170 // Show the source of our hash
171 if let Some(source) = verify_source {
172 write_source(&mut stdout, source, &verify_hash.filename)?;
173 }
174
175 writeln!(&mut stdout)?;
176 Ok(())
177 }
178
179 pub fn print_messages(messages: Vec<(MessageLevel, String)>, no_colour: bool) -> PrintResult {
180 let mut stdout = get_stdout(no_colour);
181
182 for (level, msg) in &messages {
183 match level {
184 MessageLevel::Error => {
185 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
186 write!(&mut stdout, "(error) ")?;
187 }
188 MessageLevel::Warning => {
189 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
190 write!(&mut stdout, "(warning) ")?;
191 }
192 MessageLevel::Note => {
193 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
194 write!(&mut stdout, "(note) ")?;
195 }
196 }
197 stdout.reset()?;
198 writeln!(&mut stdout, "{}", msg)?;
199 }
200 if !messages.is_empty() {
201 writeln!(&mut stdout)?
202 }
203
204 Ok(())
205 }
206
207 pub fn print_match_level(match_level: MatchLevel, no_colour: bool) -> PrintResult {
208 let mut stdout = get_stdout(no_colour);
209 write!(&mut stdout, "Result: ")?;
210 match match_level {
211 MatchLevel::Ok => {
212 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
213 writeln!(&mut stdout, "OK")?;
214 }
215 MatchLevel::Maybe => {
216 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true))?;
217 writeln!(&mut stdout, "MAYBE")?;
218 }
219 MatchLevel::Fail => {
220 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
221 writeln!(&mut stdout, "FAIL")?;
222 }
223 }
224 stdout.reset()?;
225 Ok(())
226 }