typos_git_commit/
keyvalue.rs

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