typos_git_commit/
thashmap.rs

1use crate::cli::{Cli, TypoType};
2use crate::keyvalue::{Key, Value};
3use crate::typosjsonline::TyposJsonLine;
4use fluent_i18n::t;
5use std::collections::HashMap;
6use std::error::Error;
7use std::fs::File;
8use std::io::{self, BufRead};
9use std::path::Path;
10use std::process::exit;
11
12// We use these Key and Value structures in a HashMap that
13// can handle multiples Value for a single Key (ie a typo
14// may appear more than once in more than one file)
15#[derive(Debug)]
16pub struct THashMap {
17    hashmap: HashMap<Key, Vec<Value>>,
18}
19
20impl Default for THashMap {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl THashMap {
27    fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
28    where
29        P: AsRef<Path>,
30    {
31        let file = File::open(filename)?;
32        Ok(io::BufReader::new(file).lines())
33    }
34
35    #[must_use]
36    pub fn new() -> Self {
37        let hashmap: HashMap<Key, Vec<Value>> = HashMap::new();
38        THashMap {
39            hashmap,
40        }
41    }
42
43    /// Here we populate the `HashMap` with a line in `TyposJsonLine` format
44    /// read from typo's output JSON file.
45    pub fn insert(&mut self, typojson: TyposJsonLine, cli: &Cli) {
46        if !typojson.is_excluded(cli) {
47            let typo = typojson.typo;
48            let corrections = typojson.corrections;
49            let key = Key {
50                typo,
51                corrections,
52            };
53
54            if let Some(v) = self.hashmap.get_mut(&key) {
55                // Key does already exists so we only need to
56                // add a new value for it.
57                let value = Value {
58                    path: typojson.path,
59                    line_num: typojson.line_num,
60                    byte_offset: typojson.byte_offset,
61                };
62                v.push(value);
63            } else {
64                // Key does not exist already -> create a new one
65                // and add it to the HashMap
66                let mut v: Vec<Value> = Vec::new();
67                let value = Value {
68                    path: typojson.path,
69                    line_num: typojson.line_num,
70                    byte_offset: typojson.byte_offset,
71                };
72                v.push(value);
73                self.hashmap.insert(key, v);
74            }
75        } else if cli.debug {
76            // The typo is excluded somehow and will not be corrected.
77            // We print some information about it when debug mode is on.
78            if typojson.is_file_excluded(cli) {
79                eprintln!("{}", t!("thashmap-file-excluded", {"file" => typojson.path}));
80            } else if typojson.is_typo_excluded(cli) {
81                eprintln!("{}", t!("thashmap-typo-excluded", {"typo" => typojson.typo}));
82            } else if typojson.is_correction_excluded(cli) {
83                eprintln!(
84                    "{}",
85                    t!("thashmap-correction-excluded", { "correction" => format!("{:?}", typojson.corrections), "typo" => typojson.typo})
86                );
87            }
88        }
89    }
90
91    /// Fills and returns a vector of typos that have `type_id` equal to "typo".
92    ///
93    /// # Errors
94    ///
95    /// Will return an error if the `serde_json` can not Deserialize a Json line
96    pub fn read_typos_file(mut self, cli: &Cli) -> Result<Self, Box<dyn Error>> {
97        match THashMap::read_lines(&cli.filename) {
98            Ok(lines) => {
99                // Consumes the iterator, returns an (Optional) String
100                for line in lines.map_while(Result::ok) {
101                    let typojson = serde_json::from_str::<TyposJsonLine>(&line)?;
102                    if typojson.type_id == "typo" {
103                        self.insert(typojson, cli);
104                    }
105                }
106            }
107            Err(e) => {
108                eprintln!("{}", t!("thashmap-error-file", {"e" => e.to_string()}));
109                exit(1);
110            }
111        }
112        Ok(self)
113    }
114
115    // Only lists typos in a brief manner or more verbosely when details is true.
116    pub fn list_typos(&self, cli: &Cli) {
117        if let Some(only_list_typos) = &cli.only_list_typos {
118            for (key, values) in &self.hashmap {
119                let values_len = values.len();
120                let files_string = t!("thashmap-file-count", {"count" => values_len }).to_string();
121                let mut printed = true;
122
123                // Corrections will only occur if
124                // * there is only one possible correction
125                // * and the len of the typo is at least equal to the minimum specified
126                if key.is_typo_correctable(cli) {
127                    match only_list_typos {
128                        TypoType::All | TypoType::Corrected => {
129                            println!("'{}' -> {:?}) {}", key.typo, key.corrections, files_string);
130                        }
131                        TypoType::NotCorrected => printed = false,
132                    }
133                } else {
134                    match only_list_typos {
135                        TypoType::All | TypoType::NotCorrected => {
136                            println!(
137                                "\t{}",
138                                t!("thashmap-wont-correct", {"typo" => key.typo, "correction" => format!("{:?}",key.corrections), "files" => files_string})
139                            );
140                        }
141                        TypoType::Corrected => printed = false,
142                    }
143                }
144
145                if cli.details && printed {
146                    for v in values {
147                        println!(
148                            "\t{}",
149                            t!("thashmap-typo-details", {"path" => v.path, "line" => v.line_num, "offset" => v.byte_offset})
150                        );
151                    }
152                    println!();
153                }
154            }
155        }
156    }
157
158    // Corrects typos for real unless --noop has been invoked
159    pub fn correct_typos(&self, cli: &Cli) {
160        for (key, values) in &self.hashmap {
161            // Corrections will only occur if
162            // * there is only one possible correction
163            // * and the len of the typo is at least equal to the minimum specified
164            if key.is_typo_correctable(cli) {
165                let mut files: Vec<String> = Vec::new();
166                for v in values {
167                    files.push(v.path.clone());
168                }
169
170                key.run_sed(&files, cli);
171                key.run_git_commit(cli);
172            } else if cli.details {
173                println!();
174                println!(
175                    "{}\n{}",
176                    t!("thashmap-typo-not-corrected", {"typo" => key.typo, "correction" => format!("{:?}",key.corrections)}),
177                    t!("thashmap-typo-look-carefully")
178                );
179                for v in values {
180                    println!(
181                        "{}",
182                        t!("thashmap-typo-details", {"path" => v.path, "line" => v.line_num, "offset" => v.byte_offset})
183                    );
184                }
185                println!();
186            }
187        }
188    }
189}