Merge pull request #48 from epage/config

Config file support
This commit is contained in:
Ed Page 2019-08-08 09:30:25 -05:00 committed by GitHub
commit ce4a7415ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 572 additions and 155 deletions

48
Cargo.lock generated
View file

@ -75,7 +75,7 @@ name = "bstr"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "regex-automata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -164,6 +164,19 @@ dependencies = [
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "derive_more"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "difference" name = "difference"
version = "2.0.0" version = "2.0.0"
@ -271,7 +284,7 @@ name = "heck"
version = "0.3.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -289,7 +302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"crossbeam-channel 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -314,7 +327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.2.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -651,6 +664,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "serde" name = "serde"
version = "1.0.85" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
@ -780,7 +796,15 @@ name = "thread_local"
version = "0.3.6" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "toml"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -797,20 +821,22 @@ dependencies = [
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap-verbosity-flag 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap-verbosity-flag 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", "phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -828,7 +854,7 @@ dependencies = [
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.2.1" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
@ -935,6 +961,7 @@ dependencies = [
"checksum crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "41ee4864f4797060e52044376f7d107429ce1fb43460021b126424b7180ee21a" "checksum crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "41ee4864f4797060e52044376f7d107429ce1fb43460021b126424b7180ee21a"
"checksum csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd1c44c58078cfbeaf11fbb3eac9ae5534c23004ed770cc4bfb48e658ae4f04" "checksum csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd1c44c58078cfbeaf11fbb3eac9ae5534c23004ed770cc4bfb48e658ae4f04"
"checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65" "checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65"
"checksum derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe"
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0"
"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38"
@ -952,7 +979,7 @@ dependencies = [
"checksum ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ad03ca67dc12474ecd91fdb94d758cbd20cb4e7a78ebe831df26a9b7511e1162" "checksum ignore 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ad03ca67dc12474ecd91fdb94d758cbd20cb4e7a78ebe831df26a9b7511e1162"
"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
"checksum libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)" = "48450664a984b25d5b479554c29cc04e3150c97aa4c01da5604a2d4ed9151476" "checksum libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)" = "48450664a984b25d5b479554c29cc04e3150c97aa4c01da5604a2d4ed9151476"
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
@ -1010,10 +1037,11 @@ dependencies = [
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f"
"checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" "checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"

View file

@ -28,14 +28,16 @@ ignore = "0.4"
phf = { version = "0.7", features = ["unicase"] } phf = { version = "0.7", features = ["unicase"] }
regex = "1.0" regex = "1.0"
lazy_static = "1.2.0" lazy_static = "1.2.0"
serde = "1.0" serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
toml = "0.4"
itertools = "0.8" itertools = "0.8"
unicase = "1.1" unicase = "1.1"
bstr = "0.2" bstr = "0.2"
log = "0.4" log = "0.4"
env_logger = "0.6" env_logger = "0.6"
unicode-segmentation = "1.3.0"
derive_more = "0.15.0"
[dev-dependencies] [dev-dependencies]
assert_fs = "0.10" assert_fs = "0.10"

View file

@ -7,6 +7,7 @@ use crate::report;
use crate::tokens; use crate::tokens;
use crate::Dictionary; use crate::Dictionary;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CheckSettings { pub struct CheckSettings {
check_filenames: bool, check_filenames: bool,
check_files: bool, check_files: bool,
@ -58,6 +59,7 @@ impl Default for CheckSettings {
} }
} }
#[derive(Clone)]
pub struct Checks<'d, 'p> { pub struct Checks<'d, 'p> {
dictionary: &'d Dictionary, dictionary: &'d Dictionary,
parser: &'p tokens::Parser, parser: &'p tokens::Parser,
@ -170,3 +172,14 @@ impl<'d, 'p> Checks<'d, 'p> {
Ok(typos_found) Ok(typos_found)
} }
} }
impl std::fmt::Debug for Checks<'_, '_> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Checks")
.field("parser", self.parser)
.field("check_filenames", &self.check_filenames)
.field("check_files", &self.check_files)
.field("binary", &self.binary)
.finish()
}
}

317
src/config.rs Normal file
View file

@ -0,0 +1,317 @@
use std::io::Read;
pub trait ConfigSource {
fn walk(&self) -> Option<&dyn WalkSource> {
None
}
fn default(&self) -> Option<&dyn FileSource> {
None
}
}
pub trait WalkSource {
/// Search binary files.
fn binary(&self) -> Option<bool> {
None
}
/// Skip hidden files and directories.
fn ignore_hidden(&self) -> Option<bool> {
None
}
/// Respect ignore files.
fn ignore_files(&self) -> Option<bool> {
None
}
/// Respect .ignore files.
fn ignore_dot(&self) -> Option<bool> {
None
}
/// Respect ignore files in vcs directories.
fn ignore_vcs(&self) -> Option<bool> {
None
}
/// Respect global ignore files.
fn ignore_global(&self) -> Option<bool> {
None
}
/// Respect ignore files in parent directories.
fn ignore_parent(&self) -> Option<bool> {
None
}
}
pub trait FileSource {
/// Verifying spelling in file names.
fn check_filename(&self) -> Option<bool> {
None
}
/// Verifying spelling in filess.
fn check_file(&self) -> Option<bool> {
None
}
/// Do not check identifiers that appear to be hexadecimal values
fn ignore_hex(&self) -> Option<bool> {
None
}
/// Allow identifiers to include digits, in addition to letters
fn identifier_include_digits(&self) -> Option<bool> {
None
}
/// Specify additional characters to be included in identifiers
fn identifier_include_chars(&self) -> Option<&str> {
None
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
pub files: Walk,
pub default: FileConfig,
}
impl Config {
pub fn from_file(path: &std::path::Path) -> Result<Self, failure::Error> {
let mut file = std::fs::File::open(path)?;
let mut s = String::new();
file.read_to_string(&mut s)?;
Self::from_toml(&s)
}
pub fn from_toml(data: &str) -> Result<Self, failure::Error> {
let content = toml::from_str(data)?;
Ok(content)
}
pub fn derive(cwd: &std::path::Path) -> Result<Self, failure::Error> {
if let Some(path) = find_project_file(cwd.to_owned(), "typos.toml") {
Self::from_file(&path)
} else {
Ok(Default::default())
}
}
pub fn update(&mut self, source: &dyn ConfigSource) {
if let Some(walk) = source.walk() {
self.files.update(walk);
}
if let Some(default) = source.default() {
self.default.update(default);
}
}
}
impl ConfigSource for Config {
fn walk(&self) -> Option<&dyn WalkSource> {
Some(&self.files)
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")]
pub struct Walk {
pub binary: Option<bool>,
pub ignore_hidden: Option<bool>,
pub ignore_files: Option<bool>,
pub ignore_dot: Option<bool>,
pub ignore_vcs: Option<bool>,
pub ignore_global: Option<bool>,
pub ignore_parent: Option<bool>,
}
impl Walk {
pub fn update(&mut self, source: &dyn WalkSource) {
if let Some(source) = source.binary() {
self.binary = Some(source);
}
if let Some(source) = source.ignore_hidden() {
self.ignore_hidden = Some(source);
}
if let Some(source) = source.ignore_files() {
self.ignore_files = Some(source);
self.ignore_dot = None;
self.ignore_vcs = None;
self.ignore_global = None;
self.ignore_parent = None;
}
if let Some(source) = source.ignore_dot() {
self.ignore_dot = Some(source);
}
if let Some(source) = source.ignore_vcs() {
self.ignore_vcs = Some(source);
self.ignore_global = None;
}
if let Some(source) = source.ignore_global() {
self.ignore_global = Some(source);
}
if let Some(source) = source.ignore_parent() {
self.ignore_parent = Some(source);
}
}
pub fn binary(&self) -> bool {
self.binary.unwrap_or(false)
}
pub fn ignore_hidden(&self) -> bool {
self.ignore_hidden.unwrap_or(true)
}
pub fn ignore_dot(&self) -> bool {
self.ignore_dot
.or_else(|| self.ignore_files)
.unwrap_or(true)
}
pub fn ignore_vcs(&self) -> bool {
self.ignore_vcs
.or_else(|| self.ignore_files)
.unwrap_or(true)
}
pub fn ignore_global(&self) -> bool {
self.ignore_global
.or_else(|| self.ignore_vcs)
.or_else(|| self.ignore_files)
.unwrap_or(true)
}
pub fn ignore_parent(&self) -> bool {
self.ignore_parent
.or_else(|| self.ignore_files)
.unwrap_or(true)
}
}
impl WalkSource for Walk {
fn binary(&self) -> Option<bool> {
self.binary
}
fn ignore_hidden(&self) -> Option<bool> {
self.ignore_hidden
}
fn ignore_files(&self) -> Option<bool> {
self.ignore_files
}
fn ignore_dot(&self) -> Option<bool> {
self.ignore_dot
}
fn ignore_vcs(&self) -> Option<bool> {
self.ignore_vcs
}
fn ignore_global(&self) -> Option<bool> {
self.ignore_global
}
fn ignore_parent(&self) -> Option<bool> {
self.ignore_parent
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")]
pub struct FileConfig {
pub check_filename: Option<bool>,
pub check_file: Option<bool>,
pub ignore_hex: Option<bool>,
pub identifier_include_digits: Option<bool>,
pub identifier_include_chars: Option<String>,
}
impl FileConfig {
pub fn update(&mut self, source: &dyn FileSource) {
if let Some(source) = source.check_filename() {
self.check_filename = Some(source);
}
if let Some(source) = source.check_file() {
self.check_file = Some(source);
}
if let Some(source) = source.ignore_hex() {
self.ignore_hex = Some(source);
}
if let Some(source) = source.identifier_include_digits() {
self.identifier_include_digits = Some(source);
}
if let Some(source) = source.identifier_include_chars() {
self.identifier_include_chars = Some(source.to_owned());
}
}
pub fn check_filename(&self) -> bool {
self.check_filename.unwrap_or(true)
}
pub fn check_file(&self) -> bool {
self.check_file.unwrap_or(true)
}
pub fn ignore_hex(&self) -> bool {
self.ignore_hex.unwrap_or(true)
}
pub fn identifier_include_digits(&self) -> bool {
self.identifier_include_digits.unwrap_or(true)
}
pub fn identifier_include_chars(&self) -> &str {
self.identifier_include_chars
.as_ref()
.map(|s| s.as_str())
.unwrap_or("_'")
}
}
impl FileSource for FileConfig {
fn check_filename(&self) -> Option<bool> {
self.check_filename
}
fn check_file(&self) -> Option<bool> {
self.check_file
}
fn ignore_hex(&self) -> Option<bool> {
self.ignore_hex
}
fn identifier_include_digits(&self) -> Option<bool> {
self.identifier_include_digits
}
fn identifier_include_chars(&self) -> Option<&str> {
self.identifier_include_chars.as_ref().map(|s| s.as_str())
}
}
fn find_project_file(dir: std::path::PathBuf, name: &str) -> Option<std::path::PathBuf> {
let mut file_path = dir;
file_path.push(name);
while !file_path.exists() {
file_path.pop(); // filename
let hit_bottom = !file_path.pop();
if hit_bottom {
return None;
}
file_path.push(name);
}
Some(file_path)
}

View file

@ -1,6 +1,3 @@
#[macro_use]
extern crate serde_derive;
mod dict; mod dict;
mod dict_codegen; mod dict_codegen;

View file

@ -6,6 +6,8 @@ use std::io::Write;
use structopt::StructOpt; use structopt::StructOpt;
mod config;
arg_enum! { arg_enum! {
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Format { enum Format {
@ -35,11 +37,39 @@ impl Default for Format {
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")] #[structopt(rename_all = "kebab-case")]
struct Options { struct Args {
#[structopt(parse(from_os_str), default_value = ".")] #[structopt(parse(from_os_str), default_value = ".")]
/// Paths to check /// Paths to check
path: Vec<std::path::PathBuf>, path: Vec<std::path::PathBuf>,
#[structopt(short = "c", long = "config")]
/// Custom config file
custom_config: Option<std::path::PathBuf>,
#[structopt(long = "isolated")]
/// Ignore implicit configuration files.
isolated: bool,
#[structopt(flatten)]
overrides: FileArgs,
#[structopt(
long = "format",
raw(possible_values = "&Format::variants()", case_insensitive = "true"),
default_value = "long"
)]
pub format: Format,
#[structopt(flatten)]
config: ConfigArgs,
#[structopt(flatten)]
verbose: clap_verbosity_flag::Verbosity,
}
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")]
pub struct FileArgs {
#[structopt(long, raw(overrides_with = r#""check-filenames""#))] #[structopt(long, raw(overrides_with = r#""check-filenames""#))]
/// Skip verifying spelling in file names. /// Skip verifying spelling in file names.
no_check_filenames: bool, no_check_filenames: bool,
@ -65,14 +95,53 @@ struct Options {
no_hex: bool, no_hex: bool,
#[structopt(long, raw(overrides_with = r#""no-hex""#), raw(hidden = "true"))] #[structopt(long, raw(overrides_with = r#""no-hex""#), raw(hidden = "true"))]
hex: bool, hex: bool,
}
#[structopt( impl config::FileSource for FileArgs {
long = "format", fn check_filename(&self) -> Option<bool> {
raw(possible_values = "&Format::variants()", case_insensitive = "true"), match (self.check_filenames, self.no_check_filenames) {
default_value = "long" (true, false) => Some(true),
)] (false, true) => Some(false),
pub format: Format, (false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn check_file(&self) -> Option<bool> {
match (self.check_files, self.no_check_files) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn ignore_hex(&self) -> Option<bool> {
match (self.hex, self.no_hex) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
}
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")]
struct ConfigArgs {
#[structopt(flatten)]
walk: WalkArgs,
}
impl config::ConfigSource for ConfigArgs {
fn walk(&self) -> Option<&dyn config::WalkSource> {
Some(&self.walk)
}
}
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")]
struct WalkArgs {
#[structopt(long, raw(overrides_with = r#""no-binary""#))] #[structopt(long, raw(overrides_with = r#""no-binary""#))]
/// Search binary files. /// Search binary files.
binary: bool, binary: bool,
@ -122,44 +191,10 @@ struct Options {
no_ignore_vcs: bool, no_ignore_vcs: bool,
#[structopt(long, raw(overrides_with = r#""no-ignore-vcs""#), raw(hidden = "true"))] #[structopt(long, raw(overrides_with = r#""no-ignore-vcs""#), raw(hidden = "true"))]
ignore_vcs: bool, ignore_vcs: bool,
#[structopt(flatten)]
verbose: clap_verbosity_flag::Verbosity,
} }
impl Options { impl config::WalkSource for WalkArgs {
pub fn infer(self) -> Self { fn binary(&self) -> Option<bool> {
self
}
pub fn check_files(&self) -> Option<bool> {
match (self.check_files, self.no_check_files) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
pub fn check_filenames(&self) -> Option<bool> {
match (self.check_filenames, self.no_check_filenames) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
pub fn ignore_hex(&self) -> Option<bool> {
match (self.no_hex, self.hex) {
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
pub fn binary(&self) -> Option<bool> {
match (self.binary, self.no_binary) { match (self.binary, self.no_binary) {
(true, false) => Some(true), (true, false) => Some(true),
(false, true) => Some(false), (false, true) => Some(false),
@ -168,7 +203,7 @@ impl Options {
} }
} }
pub fn ignore_hidden(&self) -> Option<bool> { fn ignore_hidden(&self) -> Option<bool> {
match (self.hidden, self.no_hidden) { match (self.hidden, self.no_hidden) {
(true, false) => Some(false), (true, false) => Some(false),
(false, true) => Some(true), (false, true) => Some(true),
@ -177,49 +212,44 @@ impl Options {
} }
} }
pub fn ignore_dot(&self) -> Option<bool> { fn ignore_files(&self) -> Option<bool> {
match (self.no_ignore, self.ignore) {
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn ignore_dot(&self) -> Option<bool> {
match (self.no_ignore_dot, self.ignore_dot) { match (self.no_ignore_dot, self.ignore_dot) {
(true, false) => Some(false), (true, false) => Some(false),
(false, true) => Some(true), (false, true) => Some(true),
(false, false) => None, (false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"), (_, _) => unreachable!("StructOpt should make this impossible"),
} }
.or_else(|| self.ignore_files())
} }
pub fn ignore_global(&self) -> Option<bool> { fn ignore_vcs(&self) -> Option<bool> {
match (self.no_ignore_global, self.ignore_global) {
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
.or_else(|| self.ignore_vcs())
.or_else(|| self.ignore_files())
}
pub fn ignore_parent(&self) -> Option<bool> {
match (self.no_ignore_parent, self.ignore_parent) {
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
.or_else(|| self.ignore_files())
}
pub fn ignore_vcs(&self) -> Option<bool> {
match (self.no_ignore_vcs, self.ignore_vcs) { match (self.no_ignore_vcs, self.ignore_vcs) {
(true, false) => Some(false), (true, false) => Some(false),
(false, true) => Some(true), (false, true) => Some(true),
(false, false) => None, (false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"), (_, _) => unreachable!("StructOpt should make this impossible"),
} }
.or_else(|| self.ignore_files())
} }
fn ignore_files(&self) -> Option<bool> { fn ignore_global(&self) -> Option<bool> {
match (self.no_ignore, self.ignore) { match (self.no_ignore_global, self.ignore_global) {
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn ignore_parent(&self) -> Option<bool> {
match (self.no_ignore_parent, self.ignore_parent) {
(true, false) => Some(false), (true, false) => Some(false),
(false, true) => Some(true), (false, true) => Some(true),
(false, false) => None, (false, false) => None,
@ -250,55 +280,70 @@ pub fn get_logging(level: log::Level) -> env_logger::Builder {
} }
fn run() -> Result<i32, failure::Error> { fn run() -> Result<i32, failure::Error> {
let options = Options::from_args().infer(); let args = Args::from_args();
let mut builder = get_logging(options.verbose.log_level()); let mut builder = get_logging(args.verbose.log_level());
builder.init(); builder.init();
let check_filenames = options.check_filenames().unwrap_or(true); let mut config = config::Config::default();
let check_files = options.check_files().unwrap_or(true); if let Some(path) = args.custom_config.as_ref() {
let ignore_hex = options.ignore_hex().unwrap_or(true); let custom = config::Config::from_file(path)?;
let binary = options.binary().unwrap_or(false); config.update(&custom);
}
let config = config;
let mut typos_found = false;
for path in args.path.iter() {
let path = path.canonicalize()?;
let cwd = if path.is_file() {
path.parent().unwrap()
} else {
path.as_path()
};
let mut config = config.clone();
if !args.isolated {
let derived = config::Config::derive(cwd)?;
config.update(&derived);
}
config.update(&args.config);
config.default.update(&args.overrides);
let config = config;
let dictionary = typos::BuiltIn::new(); let dictionary = typos::BuiltIn::new();
let parser = typos::tokens::ParserBuilder::new() let parser = typos::tokens::ParserBuilder::new()
.ignore_hex(ignore_hex) .ignore_hex(config.default.ignore_hex())
.include_digits(config.default.identifier_include_digits())
.include_chars(config.default.identifier_include_chars().to_owned())
.build(); .build();
let checks = typos::checks::CheckSettings::new() let checks = typos::checks::CheckSettings::new()
.check_filenames(check_filenames) .check_filenames(config.default.check_filename())
.check_files(check_files) .check_files(config.default.check_file())
.binary(binary) .binary(config.files.binary())
.build(&dictionary, &parser); .build(&dictionary, &parser);
let first_path = &options let mut walk = ignore::WalkBuilder::new(path);
.path walk.hidden(config.files.ignore_hidden())
.get(0) .ignore(config.files.ignore_dot())
.expect("arg parsing enforces at least one"); .git_global(config.files.ignore_global())
let mut walk = ignore::WalkBuilder::new(first_path); .git_ignore(config.files.ignore_vcs())
for path in &options.path[1..] { .git_exclude(config.files.ignore_vcs())
walk.add(path); .parents(config.files.ignore_parent());
}
walk.hidden(options.ignore_hidden().unwrap_or(true))
.ignore(options.ignore_dot().unwrap_or(true))
.git_global(options.ignore_global().unwrap_or(true))
.git_ignore(options.ignore_vcs().unwrap_or(true))
.git_exclude(options.ignore_vcs().unwrap_or(true))
.parents(options.ignore_parent().unwrap_or(true));
let mut typos_found = false;
for entry in walk.build() { for entry in walk.build() {
let entry = entry?; let entry = entry?;
if entry.file_type().map(|t| t.is_file()).unwrap_or(true) { if entry.file_type().map(|t| t.is_file()).unwrap_or(true) {
let explicit = entry.depth() == 0; let explicit = entry.depth() == 0;
if checks.check_filename(entry.path(), options.format.report())? { if checks.check_filename(entry.path(), args.format.report())? {
typos_found = true; typos_found = true;
} }
if checks.check_file(entry.path(), explicit, options.format.report())? { if checks.check_file(entry.path(), explicit, args.format.report())? {
typos_found = true; typos_found = true;
} }
} }
} }
}
if typos_found { if typos_found {
Ok(1) Ok(1)

View file

@ -1,7 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::io::{self, Write}; use std::io::{self, Write};
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, serde::Serialize, derive_more::From)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Message<'m> { pub enum Message<'m> {
@ -10,32 +10,15 @@ pub enum Message<'m> {
FilenameCorrection(FilenameCorrection<'m>), FilenameCorrection(FilenameCorrection<'m>),
} }
impl<'m> From<BinaryFile<'m>> for Message<'m> { #[derive(Clone, Debug, serde::Serialize, derive_more::Display)]
fn from(msg: BinaryFile<'m>) -> Self { #[display(fmt = "Skipping binary file {}", "path.display()")]
Message::BinaryFile(msg)
}
}
impl<'m> From<Correction<'m>> for Message<'m> {
fn from(msg: Correction<'m>) -> Self {
Message::Correction(msg)
}
}
impl<'m> From<FilenameCorrection<'m>> for Message<'m> {
fn from(msg: FilenameCorrection<'m>) -> Self {
Message::FilenameCorrection(msg)
}
}
#[derive(Clone, Debug, Serialize)]
pub struct BinaryFile<'m> { pub struct BinaryFile<'m> {
pub path: &'m std::path::Path, pub path: &'m std::path::Path,
#[serde(skip)] #[serde(skip)]
pub(crate) non_exhaustive: (), pub(crate) non_exhaustive: (),
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, serde::Serialize)]
pub struct Correction<'m> { pub struct Correction<'m> {
pub path: &'m std::path::Path, pub path: &'m std::path::Path,
#[serde(skip)] #[serde(skip)]
@ -48,7 +31,7 @@ pub struct Correction<'m> {
pub(crate) non_exhaustive: (), pub(crate) non_exhaustive: (),
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, serde::Serialize)]
pub struct FilenameCorrection<'m> { pub struct FilenameCorrection<'m> {
pub path: &'m std::path::Path, pub path: &'m std::path::Path,
pub typo: &'m str, pub typo: &'m str,
@ -64,7 +47,7 @@ pub fn print_silent(_: Message) {}
pub fn print_brief(msg: Message) { pub fn print_brief(msg: Message) {
match msg { match msg {
Message::BinaryFile(msg) => { Message::BinaryFile(msg) => {
println!("Skipping binary file {}", msg.path.display(),); println!("{}", msg);
} }
Message::Correction(msg) => { Message::Correction(msg) => {
println!( println!(
@ -85,7 +68,7 @@ pub fn print_brief(msg: Message) {
pub fn print_long(msg: Message) { pub fn print_long(msg: Message) {
match msg { match msg {
Message::BinaryFile(msg) => { Message::BinaryFile(msg) => {
println!("Skipping binary file {}", msg.path.display(),); println!("{}", msg);
} }
Message::Correction(msg) => print_long_correction(msg), Message::Correction(msg) => print_long_correction(msg),
Message::FilenameCorrection(msg) => { Message::FilenameCorrection(msg) => {

View file

@ -6,9 +6,11 @@ pub enum Case {
None, None,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParserBuilder { pub struct ParserBuilder {
ignore_hex: bool, ignore_hex: bool,
include_digits: bool,
include_chars: String,
} }
impl ParserBuilder { impl ParserBuilder {
@ -21,10 +23,30 @@ impl ParserBuilder {
self self
} }
pub fn include_digits(&mut self, yes: bool) -> &mut Self {
self.include_digits = yes;
self
}
pub fn include_chars(&mut self, chars: String) -> &mut Self {
self.include_chars = chars;
self
}
pub fn build(&self) -> Parser { pub fn build(&self) -> Parser {
let pattern = r#"\b(\p{Alphabetic}|\d|_|')+\b"#; let mut pattern = r#"\b(\p{Alphabetic}"#.to_owned();
let words_str = regex::Regex::new(pattern).unwrap(); if self.include_digits {
let words_bytes = regex::bytes::Regex::new(pattern).unwrap(); pattern.push_str(r#"|\d"#);
}
for grapheme in
unicode_segmentation::UnicodeSegmentation::graphemes(self.include_chars.as_str(), true)
{
let escaped = regex::escape(&grapheme);
pattern.push_str(&format!("|{}", escaped));
}
pattern.push_str(r#")+\b"#);
let words_str = regex::Regex::new(&pattern).unwrap();
let words_bytes = regex::bytes::Regex::new(&pattern).unwrap();
Parser { Parser {
words_str, words_str,
words_bytes, words_bytes,
@ -33,6 +55,16 @@ impl ParserBuilder {
} }
} }
impl Default for ParserBuilder {
fn default() -> Self {
Self {
ignore_hex: true,
include_digits: true,
include_chars: "_'".to_owned(),
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Parser { pub struct Parser {
words_str: regex::Regex, words_str: regex::Regex,