1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use crate::cli::Cli;
use crate::keyvalue::{Key, Value};
use crate::typosjsonline::TyposJsonLine;
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use std::process::exit;

// We use these Key and Value structures in a HashMap that
// can handle multiples Value for a single Key (ie a typo
// may appear more than once in more than one file)
#[derive(Debug)]
pub struct THashMap {
    thm: HashMap<Key, Vec<Value>>,
}

impl THashMap {
    fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
    where
        P: AsRef<Path>,
    {
        let file = File::open(filename)?;
        Ok(io::BufReader::new(file).lines())
    }

    pub fn new() -> Self {
        let thm: HashMap<Key, Vec<Value>> = HashMap::new();
        THashMap {
            thm,
        }
    }

    // Here we populate the HashMap with a line in TyposJsonLine format
    // read from typo's output JSON file.
    pub fn insert(&mut self, typojson: TyposJsonLine, cli: &Cli) {
        if !typojson.is_excluded(cli) {
            let typo = typojson.typo;
            let corrections = typojson.corrections;
            let key = Key {
                typo,
                corrections,
            };

            match self.thm.get_mut(&key) {
                Some(v) => {
                    // Key does already exists so we only need to
                    // add a new value for it.
                    let value = Value {
                        path: typojson.path,
                        line_num: typojson.line_num,
                        byte_offset: typojson.byte_offset,
                    };
                    v.push(value);
                }
                None => {
                    // Key does not exist already -> create a new one
                    // and add it to the HashMap
                    let mut v: Vec<Value> = Vec::new();
                    let value = Value {
                        path: typojson.path,
                        line_num: typojson.line_num,
                        byte_offset: typojson.byte_offset,
                    };
                    v.push(value);
                    self.thm.insert(key, v);
                }
            }
        } else if cli.debug {
            // The typo is excluded somehow and will not be corrected.
            // We print some information about it when debug mode is on.
            if typojson.is_file_excluded(cli) {
                eprintln!("File {} excluded", typojson.path);
            } else if typojson.is_typo_excluded(cli) {
                eprintln!("Typo {} excluded.", typojson.typo);
            } else if typojson.is_correction_excluded(cli) {
                eprintln!("Correction {:?} for typo {} excluded.", typojson.corrections, typojson.typo);
            }
        }
    }

    // Fills and returns a vector of typos that have type_id equal to "typo".
    pub fn read_typos_file(mut self, cli: &Cli) -> Result<Self, Box<dyn Error>> {
        match THashMap::read_lines(&cli.filename) {
            Ok(lines) => {
                // Consumes the iterator, returns an (Optional) String
                for line in lines.map_while(Result::ok) {
                    let typojson = serde_json::from_str::<TyposJsonLine>(&line)?;
                    if typojson.type_id == "typo" {
                        self.insert(typojson, cli);
                    }
                }
            }
            Err(e) => {
                eprintln!("Error reading file: {e}");
                exit(1);
            }
        }
        Ok(self)
    }

    // Only lists typos in a brief manner or more verbosely when details is true.
    pub fn list_typos(&self, cli: &Cli) {
        for (key, values) in &self.thm {
            let values_len = values.len();
            let files_string = match values_len {
                1 => "(1 file)".to_string(),
                _ => format!("({} files)", values_len),
            };

            // Corrections will only occur if
            // * there is only one possible correction
            // * and the len of the typo is at least equal to the minimum specified
            if key.is_typo_correctable(cli) {
                println!("'{}' -> {:?}) {}", key.typo, key.corrections, files_string);
            } else {
                println!("Won't correct '{}' -> {:?}) {}", key.typo, key.corrections, files_string);
            }

            if cli.details {
                for v in values {
                    println!("\t{} at line {} at offset {}", v.path, v.line_num, v.byte_offset);
                }
                println!();
            }
        }
    }

    // Corrects typos for real unless --noop has been invoked
    pub fn correct_typos(&self, cli: &Cli) {
        for (key, values) in &self.thm {
            // Corrections will only occur if
            // * there is only one possible correction
            // * and the len of the typo is at least equal to the minimum specified
            if key.is_typo_correctable(cli) {
                let mut files: Vec<String> = Vec::new();
                for v in values {
                    files.push(v.path.clone());
                }

                key.run_sed(&files, cli);
                key.run_git_commit(cli);
            } else if cli.details {
                println!();
                println!("Typo '{}' will not be corrected into {:?}\nYou should look carefully at file(s):", key.typo, key.corrections);
                for v in values {
                    println!("\t{} at line {} at offset {}", v.path, v.line_num, v.byte_offset);
                }
                println!();
            }
        }
    }
}