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!();
}
}
}
}