typos_git_commit/
thashmap.rs

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