]> code.octet-stream.net Git - hashgood/blob - src/display.rs
Show errors per hash byte rather than per nybble
[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 write_source(
72 mut stdout: &mut StandardStream,
73 verify_source: &VerificationSource,
74 candidate_filename: &Option<String>,
75 ) -> PrintResult {
76 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true))?;
77 match &verify_source {
78 VerificationSource::CommandArgument => {
79 writeln!(&mut stdout, "command line argument")?;
80 }
81 VerificationSource::RawFile(raw_path) => match raw_path.as_str() {
82 "-" => {
83 writeln!(&mut stdout, "from standard input")?;
84 }
85 path => {
86 writeln!(&mut stdout, "from file '{}' containing raw hash", path)?;
87 }
88 },
89 VerificationSource::DigestsFile(digest_path) => match digest_path.as_str() {
90 "-" => {
91 writeln!(
92 &mut stdout,
93 "'{}' from digests on standard input",
94 candidate_filename.as_ref().unwrap()
95 )?;
96 }
97 path => {
98 writeln!(
99 &mut stdout,
100 "'{}' in digests file '{}'",
101 candidate_filename.as_ref().unwrap(),
102 path
103 )?;
104 }
105 },
106 }
107 stdout.reset()?;
108 Ok(())
109 }
110
111 fn calculate_match_indices(bytes1: &[u8], bytes2: &[u8]) -> Vec<bool> {
112 bytes1
113 .iter()
114 .zip(bytes2.iter())
115 .map(|(b1, b2)| b1 == b2)
116 .collect()
117 }
118
119 pub fn print_hash(
120 hash: &Hash,
121 verify_hash: Option<&CandidateHash>,
122 verify_source: Option<&VerificationSource>,
123 no_colour: bool,
124 ) -> PrintResult {
125 let mut stdout = get_stdout(no_colour);
126
127 write_filename(&mut stdout, &hash.filename)?;
128 write!(&mut stdout, " / ")?;
129 write_algorithm(&mut stdout, hash.alg)?;
130 writeln!(&mut stdout)?;
131
132 // Handle basic case first - nothing to compare it to
133 let verify_hash = match verify_hash {
134 None => {
135 write!(&mut stdout, "{}\n\n", hex::encode(&hash.bytes))?;
136 return Ok(());
137 }
138 Some(verify_hash) => verify_hash,
139 };
140
141 // Do a top-to-bottom comparison
142 let matches = calculate_match_indices(&hash.bytes, &verify_hash.bytes);
143 print_hex_compare(&hash.bytes, &matches, &mut stdout)?;
144 print_hex_compare(&verify_hash.bytes, &matches, &mut stdout)?;
145
146 // Show the source of our hash
147 if let Some(source) = verify_source {
148 write_source(&mut stdout, source, &verify_hash.filename)?;
149 }
150
151 writeln!(&mut stdout)?;
152 Ok(())
153 }
154
155 pub fn print_messages(messages: Vec<(MessageLevel, String)>, no_colour: bool) -> PrintResult {
156 let mut stdout = get_stdout(no_colour);
157
158 for (level, msg) in &messages {
159 match level {
160 MessageLevel::Error => {
161 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
162 write!(&mut stdout, "(error) ")?;
163 }
164 MessageLevel::Warning => {
165 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
166 write!(&mut stdout, "(warning) ")?;
167 }
168 MessageLevel::Note => {
169 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
170 write!(&mut stdout, "(note) ")?;
171 }
172 }
173 stdout.reset()?;
174 writeln!(&mut stdout, "{}", msg)?;
175 }
176 if !messages.is_empty() {
177 writeln!(&mut stdout)?
178 }
179
180 Ok(())
181 }
182
183 pub fn print_match_level(match_level: MatchLevel, no_colour: bool) -> PrintResult {
184 let mut stdout = get_stdout(no_colour);
185 write!(&mut stdout, "Result: ")?;
186 match match_level {
187 MatchLevel::Ok => {
188 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
189 writeln!(&mut stdout, "OK")?;
190 }
191 MatchLevel::Maybe => {
192 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)).set_bold(true))?;
193 writeln!(&mut stdout, "MAYBE")?;
194 }
195 MatchLevel::Fail => {
196 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
197 writeln!(&mut stdout, "FAIL")?;
198 }
199 }
200 stdout.reset()?;
201 Ok(())
202 }