2020-05-27 21:46:41 -04:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
use structopt::StructOpt;
|
|
|
|
|
|
|
|
fn generate<W: std::io::Write>(file: &mut W, dict: &[u8]) {
|
2021-05-15 20:06:04 -04:00
|
|
|
let mut wtr = csv::WriterBuilder::new().flexible(true).from_writer(file);
|
2020-05-27 21:46:41 -04:00
|
|
|
|
2021-05-15 20:29:27 -04:00
|
|
|
let disallowed_typos = varcon_words();
|
|
|
|
let word_variants = proper_word_variants();
|
2020-05-27 21:46:41 -04:00
|
|
|
|
|
|
|
let mut reader = csv::ReaderBuilder::new()
|
|
|
|
.has_headers(false)
|
2021-05-15 20:06:04 -04:00
|
|
|
.flexible(true)
|
2020-05-27 21:46:41 -04:00
|
|
|
.from_reader(dict);
|
|
|
|
for record in reader.records() {
|
|
|
|
let record = record.unwrap();
|
2021-05-15 20:06:04 -04:00
|
|
|
let mut record_fields = record.iter();
|
|
|
|
let typo = record_fields.next().unwrap();
|
2020-05-27 21:46:41 -04:00
|
|
|
if disallowed_typos.contains(&unicase::UniCase::new(typo)) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-05-15 20:06:04 -04:00
|
|
|
|
|
|
|
let mut row = vec![typo];
|
|
|
|
for correction in record_fields {
|
|
|
|
let correction = word_variants
|
|
|
|
.get(correction)
|
|
|
|
.and_then(|words| find_best_match(typo, correction, words))
|
|
|
|
.unwrap_or(correction);
|
|
|
|
row.push(correction);
|
|
|
|
}
|
|
|
|
wtr.write_record(&row).unwrap();
|
2020-05-27 21:46:41 -04:00
|
|
|
}
|
|
|
|
wtr.flush().unwrap();
|
|
|
|
}
|
|
|
|
|
2021-05-15 20:29:27 -04:00
|
|
|
fn varcon_words() -> HashSet<unicase::UniCase<&'static str>> {
|
|
|
|
// Even include improper ones because we should be letting varcon handle that rather than our
|
|
|
|
// dictionary
|
2020-05-27 21:46:41 -04:00
|
|
|
varcon::VARCON
|
|
|
|
.iter()
|
|
|
|
.flat_map(|c| c.entries.iter())
|
|
|
|
.flat_map(|e| e.variants.iter())
|
|
|
|
.map(|v| unicase::UniCase::new(v.word))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2021-05-15 20:29:27 -04:00
|
|
|
fn proper_word_variants() -> HashMap<&'static str, HashSet<&'static str>> {
|
2020-05-27 21:46:41 -04:00
|
|
|
let mut words: HashMap<&'static str, HashSet<&'static str>> = HashMap::new();
|
|
|
|
for entry in varcon::VARCON.iter().flat_map(|c| c.entries.iter()) {
|
|
|
|
let variants: HashSet<_> = entry
|
|
|
|
.variants
|
|
|
|
.iter()
|
|
|
|
.filter(|v| v.types.iter().any(|t| t.tag != Some(varcon::Tag::Improper)))
|
|
|
|
.map(|v| v.word)
|
|
|
|
.collect();
|
|
|
|
for variant in variants.iter() {
|
|
|
|
let set = words.entry(variant).or_insert_with(HashSet::new);
|
|
|
|
set.extend(variants.iter().filter(|v| *v != variant));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
words
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_best_match<'c>(
|
|
|
|
typo: &'c str,
|
|
|
|
correction: &'c str,
|
2021-05-15 20:29:27 -04:00
|
|
|
word_variants: &HashSet<&'static str>,
|
2020-05-27 21:46:41 -04:00
|
|
|
) -> Option<&'c str> {
|
2021-05-15 20:29:27 -04:00
|
|
|
assert!(!word_variants.contains(correction));
|
2020-05-27 21:46:41 -04:00
|
|
|
let current = edit_distance::edit_distance(typo, correction);
|
2021-05-15 20:29:27 -04:00
|
|
|
let mut matches: Vec<_> = word_variants
|
2020-05-27 21:46:41 -04:00
|
|
|
.iter()
|
|
|
|
.map(|r| (edit_distance::edit_distance(typo, r), *r))
|
|
|
|
.filter(|(d, _)| *d < current)
|
|
|
|
.collect();
|
|
|
|
matches.sort_unstable();
|
|
|
|
matches.into_iter().next().map(|(_, r)| r)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, StructOpt)]
|
|
|
|
#[structopt(rename_all = "kebab-case")]
|
|
|
|
struct Options {
|
|
|
|
#[structopt(short("-i"), long, parse(from_os_str))]
|
|
|
|
input: std::path::PathBuf,
|
|
|
|
#[structopt(flatten)]
|
|
|
|
codegen: codegenrs::CodeGenArgs,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run() -> Result<i32, Box<dyn std::error::Error>> {
|
|
|
|
let options = Options::from_args();
|
|
|
|
|
|
|
|
let data = std::fs::read(&options.input).unwrap();
|
|
|
|
|
|
|
|
let mut content = vec![];
|
|
|
|
generate(&mut content, &data);
|
|
|
|
|
|
|
|
let content = String::from_utf8(content)?;
|
|
|
|
options.codegen.write_str(&content)?;
|
|
|
|
|
|
|
|
Ok(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let code = run().unwrap();
|
|
|
|
std::process::exit(code);
|
|
|
|
}
|