typos_git_commit/
keyvalue.rs

1use crate::cli::Cli;
2use std::io::{self, Write};
3use std::process::{Command, Output};
4
5// Convenient Key, Value structures to be used with a HashMap.
6// Key is identified by the typo and its suggested corrections
7#[derive(Eq, Hash, PartialEq, Debug)]
8pub struct Key {
9    pub typo: String,
10    pub corrections: Vec<String>,
11}
12
13impl Key {
14    // Returns true if the Key contains a typo that may be corrected
15    pub fn is_typo_correctable(&self, cli: &Cli) -> bool {
16        self.corrections.len() == 1 && self.typo.len() >= cli.minlen
17    }
18
19    // Runs sed command. This is way too basic and may lead to replacement
20    // mistakes.
21    pub fn run_sed(&self, files: &Vec<String>, cli: &Cli) {
22        if self.is_typo_correctable(cli) {
23            // Here we know that we have exactly one correction
24            let sed_script = format!("s/\\b{}\\b/{}/g", self.typo, self.corrections[0]);
25
26            if cli.noop {
27                println!("sed --in-place --expression={sed_script} {files:?}");
28            } else {
29                let output = Command::new("sed").arg("--in-place").arg(format!("--expression={sed_script}")).args(files).output().expect("failed to execute sed process");
30
31                if !output.status.success() {
32                    output_detected_errors(output, &format!("Error while executing sed ({} -> {:?})", self.typo, self.corrections[0]));
33                }
34            }
35        }
36    }
37
38    // Runs git commit command.
39    // @TODO: check whether the sed command did modify something before
40    //        trying to commit anything
41    // @TODO: avoid spawning external process
42    pub fn run_git_commit(&self, cli: &Cli) {
43        if self.is_typo_correctable(cli) {
44            // Here we know that we have exactly one correction
45            let replaced = cli.message.replace("{typo}", &self.typo);
46            let typo_message = match cli.message.contains("{typo}") {
47                true => &replaced,
48                false => &cli.message,
49            };
50
51            let replaced = typo_message.replace("{correction}", &self.corrections[0]);
52            let git_message = match cli.message.contains("{correction}") {
53                true => &replaced,
54                false => typo_message,
55            };
56
57            if cli.noop {
58                println!("git commit --all --message={git_message}");
59            } else {
60                let output = Command::new("git").arg("commit").arg("--all").arg(format!("--message={git_message}")).output().expect("failed to execute git process");
61
62                if !output.status.success() {
63                    output_detected_errors(output, &format!("Error while executing git commit ({} -> {:?})", self.typo, self.corrections[0]));
64                }
65            }
66        }
67    }
68}
69
70// Value contains information about where the typo is located
71#[derive(Eq, Hash, PartialEq, Debug)]
72pub struct Value {
73    pub path: String,
74    pub line_num: u32,
75    pub byte_offset: u32,
76}
77
78// Helper function to print the output of the command that
79// has been executed and that returned an error status.
80// Prints an another message if an error occurred when printing
81// to stderr or stdout.
82fn output_detected_errors(output: Output, message: &str) {
83    eprintln!("{message}");
84    if let Err(e) = io::stderr().write_all(&output.stderr) {
85        eprintln!("Error while writing to stderr: {e}");
86    }
87    if let Err(e) = io::stdout().write_all(&output.stdout) {
88        eprintln!("Error while writing to stdout: {e}");
89    }
90}