From 47eb55405266d41fd7850da90f8e10801b84b8f7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 31 Mar 2021 20:06:33 -0500 Subject: [PATCH 1/7] refactor(cli): Clarify role of file config --- src/main.rs | 14 +++++++------- src/policy.rs | 22 ++++++++++------------ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5cddf7c..886337a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,19 +114,19 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult { engine .init_dir(cwd) .with_code(proc_exit::Code::CONFIG_ERR)?; - let files = engine.files(cwd); + let walk_policy = engine.walk(cwd); let threads = if path.is_file() { 1 } else { args.threads }; let single_threaded = threads == 1; let mut walk = ignore::WalkBuilder::new(path); walk.threads(args.threads) - .hidden(files.ignore_hidden()) - .ignore(files.ignore_dot()) - .git_global(files.ignore_global()) - .git_ignore(files.ignore_vcs()) - .git_exclude(files.ignore_vcs()) - .parents(files.ignore_parent()); + .hidden(walk_policy.ignore_hidden()) + .ignore(walk_policy.ignore_dot()) + .git_global(walk_policy.ignore_global()) + .git_ignore(walk_policy.ignore_vcs()) + .git_exclude(walk_policy.ignore_vcs()) + .parents(walk_policy.ignore_parent()); // HACK: Diff doesn't handle mixing content let output_reporter = if args.diff { diff --git a/src/policy.rs b/src/policy.rs index 92c8aa6..3a27c0f 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -40,7 +40,7 @@ pub struct ConfigEngine<'s> { isolated: bool, configs: std::collections::HashMap, - files: Intern, + walk: Intern, tokenizer: Intern, dict: Intern>, } @@ -53,7 +53,7 @@ impl<'s> ConfigEngine<'s> { custom: Default::default(), configs: Default::default(), isolated: false, - files: Default::default(), + walk: Default::default(), tokenizer: Default::default(), dict: Default::default(), } @@ -74,18 +74,16 @@ impl<'s> ConfigEngine<'s> { self } - pub fn files(&mut self, cwd: &std::path::Path) -> &crate::config::Walk { + pub fn walk(&mut self, cwd: &std::path::Path) -> &crate::config::Walk { let dir = self .configs .get(cwd) .expect("`init_dir` must be called first"); - self.get_files(dir) + self.get_walk(dir) } pub fn policy(&self, path: &std::path::Path) -> Policy<'_, '_> { - let dir = self - .get_dir(path) - .expect("`files()` should be called first"); + let dir = self.get_dir(path).expect("`walk()` should be called first"); Policy { check_filenames: dir.check_filenames, check_files: dir.check_files, @@ -95,8 +93,8 @@ impl<'s> ConfigEngine<'s> { } } - fn get_files(&self, dir: &DirConfig) -> &crate::config::Walk { - self.files.get(dir.files) + fn get_walk(&self, dir: &DirConfig) -> &crate::config::Walk { + self.walk.get(dir.walk) } fn get_tokenizer(&self, dir: &DirConfig) -> &typos::tokens::Tokenizer { @@ -177,11 +175,11 @@ impl<'s> ConfigEngine<'s> { ); let dict = self.dict.intern(dict); - let files = self.files.intern(files); + let walk = self.walk.intern(files); let tokenizer = self.tokenizer.intern(tokenizer); let dir = DirConfig { - files, + walk, check_filenames: check_filename, check_files: check_file, binary, @@ -223,7 +221,7 @@ impl Default for Intern { } struct DirConfig { - files: usize, + walk: usize, tokenizer: usize, dict: usize, check_filenames: bool, From 13617fa9d0213b50f4c3504004c6e6d581e7abd6 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 31 Mar 2021 20:19:52 -0500 Subject: [PATCH 2/7] refactor(cli): Decouple walk and engine policies --- src/policy.rs | 53 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/policy.rs b/src/policy.rs index 3a27c0f..f6083a5 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -85,11 +85,11 @@ impl<'s> ConfigEngine<'s> { pub fn policy(&self, path: &std::path::Path) -> Policy<'_, '_> { let dir = self.get_dir(path).expect("`walk()` should be called first"); Policy { - check_filenames: dir.check_filenames, - check_files: dir.check_files, - binary: dir.binary, - tokenizer: self.get_tokenizer(dir), - dict: self.get_dict(dir), + check_filenames: dir.default.check_filenames, + check_files: dir.default.check_files, + binary: dir.default.binary, + tokenizer: self.get_tokenizer(&dir.default), + dict: self.get_dict(&dir.default), } } @@ -97,12 +97,12 @@ impl<'s> ConfigEngine<'s> { self.walk.get(dir.walk) } - fn get_tokenizer(&self, dir: &DirConfig) -> &typos::tokens::Tokenizer { - self.tokenizer.get(dir.tokenizer) + fn get_tokenizer(&self, file: &FileConfig) -> &typos::tokens::Tokenizer { + self.tokenizer.get(file.tokenizer) } - fn get_dict(&self, dir: &DirConfig) -> &dyn typos::Dictionary { - self.dict.get(dir.dict) + fn get_dict(&self, file: &FileConfig) -> &dyn typos::Dictionary { + self.dict.get(file.dict) } fn get_dir(&self, path: &std::path::Path) -> Option<&DirConfig> { @@ -141,14 +141,27 @@ impl<'s> ConfigEngine<'s> { } let config = self.load_config(cwd)?; - let crate::config::Config { files, default } = config; - let binary = default.binary(); - let check_filename = default.check_filename(); - let check_file = default.check_file(); + + let walk = self.walk.intern(files); + let default = self.init_file_config(default)?; + + let dir = DirConfig { walk, default }; + + self.configs.insert(cwd.to_owned(), dir); + Ok(()) + } + + fn init_file_config( + &mut self, + engine: crate::config::EngineConfig, + ) -> Result { + let binary = engine.binary(); + let check_filename = engine.check_filename(); + let check_file = engine.check_file(); let crate::config::EngineConfig { tokenizer, dict, .. - } = default; + } = engine; let tokenizer_config = tokenizer.unwrap_or_else(crate::config::TokenizerConfig::from_defaults); let dict_config = dict.unwrap_or_else(crate::config::DictConfig::from_defaults); @@ -175,20 +188,16 @@ impl<'s> ConfigEngine<'s> { ); let dict = self.dict.intern(dict); - let walk = self.walk.intern(files); let tokenizer = self.tokenizer.intern(tokenizer); - let dir = DirConfig { - walk, + let file = FileConfig { check_filenames: check_filename, check_files: check_file, binary, tokenizer, dict, }; - - self.configs.insert(cwd.to_owned(), dir); - Ok(()) + Ok(file) } } @@ -222,6 +231,10 @@ impl Default for Intern { struct DirConfig { walk: usize, + default: FileConfig, +} + +struct FileConfig { tokenizer: usize, dict: usize, check_filenames: bool, From 78330ba9c1a6a4953d331ffba32b7feeaec7a373 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 31 Mar 2021 21:23:30 -0500 Subject: [PATCH 3/7] refactor(cli): Drop the traits from layering --- src/args.rs | 49 ++++++--- src/config.rs | 290 ++++++++------------------------------------------ src/main.rs | 4 +- 3 files changed, 82 insertions(+), 261 deletions(-) diff --git a/src/args.rs b/src/args.rs index 16e2db7..45c8ab1 100644 --- a/src/args.rs +++ b/src/args.rs @@ -129,7 +129,21 @@ pub(crate) struct FileArgs { pub(crate) locale: Option, } -impl config::EngineSource for FileArgs { +impl FileArgs { + pub fn to_config(&self) -> config::EngineConfig { + config::EngineConfig { + binary: self.binary(), + check_filename: self.check_filename(), + check_file: self.check_file(), + tokenizer: None, + dict: Some(config::DictConfig { + locale: self.locale, + ..Default::default() + }), + ..Default::default() + } + } + fn binary(&self) -> Option { match (self.binary, self.no_binary) { (true, false) => Some(true), @@ -156,16 +170,6 @@ impl config::EngineSource for FileArgs { (_, _) => unreachable!("StructOpt should make this impossible"), } } - - fn dict(&self) -> Option<&dyn config::DictSource> { - Some(self) - } -} - -impl config::DictSource for FileArgs { - fn locale(&self) -> Option { - self.locale - } } #[derive(Debug, StructOpt)] @@ -175,9 +179,12 @@ pub(crate) struct ConfigArgs { walk: WalkArgs, } -impl config::ConfigSource for ConfigArgs { - fn walk(&self) -> Option<&dyn config::WalkSource> { - Some(&self.walk) +impl ConfigArgs { + pub fn to_config(&self) -> config::Config { + config::Config { + files: self.walk.to_config(), + ..Default::default() + } } } @@ -221,7 +228,19 @@ pub(crate) struct WalkArgs { ignore_vcs: bool, } -impl config::WalkSource for WalkArgs { +impl WalkArgs { + pub fn to_config(&self) -> config::Walk { + config::Walk { + ignore_hidden: self.ignore_hidden(), + ignore_files: self.ignore_files(), + ignore_dot: self.ignore_dot(), + ignore_vcs: self.ignore_vcs(), + ignore_global: self.ignore_global(), + ignore_parent: self.ignore_parent(), + ..Default::default() + } + } + fn ignore_hidden(&self) -> Option { match (self.hidden, self.no_hidden) { (true, false) => Some(false), diff --git a/src/config.rs b/src/config.rs index 97cb9d5..a8e7323 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,113 +1,5 @@ use std::collections::HashMap; -pub trait ConfigSource { - fn walk(&self) -> Option<&dyn WalkSource> { - None - } - - fn default(&self) -> Option<&dyn EngineSource> { - None - } -} - -pub trait WalkSource { - /// Skip hidden files and directories. - fn ignore_hidden(&self) -> Option { - None - } - - /// Respect ignore files. - fn ignore_files(&self) -> Option { - None - } - - /// Respect .ignore files. - fn ignore_dot(&self) -> Option { - None - } - - /// Respect ignore files in vcs directories. - fn ignore_vcs(&self) -> Option { - None - } - - /// Respect global ignore files. - fn ignore_global(&self) -> Option { - None - } - - /// Respect ignore files in parent directories. - fn ignore_parent(&self) -> Option { - None - } -} - -pub trait EngineSource { - /// Check binary files. - fn binary(&self) -> Option { - None - } - - /// Verifying spelling in file names. - fn check_filename(&self) -> Option { - None - } - - /// Verifying spelling in files. - fn check_file(&self) -> Option { - None - } - - fn tokenizer(&self) -> Option<&dyn TokenizerSource> { - None - } - - fn dict(&self) -> Option<&dyn DictSource> { - None - } -} - -pub trait TokenizerSource { - /// Do not check identifiers that appear to be hexadecimal values. - fn ignore_hex(&self) -> Option { - None - } - - /// Allow identifiers to start with digits, in addition to letters. - fn identifier_leading_digits(&self) -> Option { - None - } - - /// Allow identifiers to start with one of these characters. - fn identifier_leading_chars(&self) -> Option<&str> { - None - } - - /// Allow identifiers to include digits, in addition to letters. - fn identifier_include_digits(&self) -> Option { - None - } - - /// Allow identifiers to include these characters. - fn identifier_include_chars(&self) -> Option<&str> { - None - } -} - -pub trait DictSource { - fn locale(&self) -> Option { - None - } - - fn extend_identifiers(&self) -> Box + '_> { - Box::new(None.into_iter()) - } - - fn extend_words(&self) -> Box + '_> { - Box::new(None.into_iter()) - } -} - #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields, default)] #[serde(rename_all = "kebab-case")] @@ -145,23 +37,9 @@ impl Config { } } - 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) - } - - fn default(&self) -> Option<&dyn EngineSource> { - Some(&self.default) + pub fn update(&mut self, source: &Config) { + self.files.update(&source.files); + self.default.update(&source.default); } } @@ -169,11 +47,17 @@ impl ConfigSource for Config { #[serde(deny_unknown_fields, default)] #[serde(rename_all = "kebab-case")] pub struct Walk { + /// Skip hidden files and directories. pub ignore_hidden: Option, + /// Respect ignore files. pub ignore_files: Option, + /// Respect .ignore files. pub ignore_dot: Option, + /// Respect ignore files in vcs directories. pub ignore_vcs: Option, + /// Respect global ignore files. pub ignore_global: Option, + /// Respect ignore files in parent directories. pub ignore_parent: Option, } @@ -190,28 +74,28 @@ impl Walk { } } - pub fn update(&mut self, source: &dyn WalkSource) { - if let Some(source) = source.ignore_hidden() { + pub fn update(&mut self, source: &Walk) { + if let Some(source) = source.ignore_hidden { self.ignore_hidden = Some(source); } - if let Some(source) = source.ignore_files() { + 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() { + if let Some(source) = source.ignore_dot { self.ignore_dot = Some(source); } - if let Some(source) = source.ignore_vcs() { + if let Some(source) = source.ignore_vcs { self.ignore_vcs = Some(source); self.ignore_global = None; } - if let Some(source) = source.ignore_global() { + if let Some(source) = source.ignore_global { self.ignore_global = Some(source); } - if let Some(source) = source.ignore_parent() { + if let Some(source) = source.ignore_parent { self.ignore_parent = Some(source); } } @@ -240,38 +124,15 @@ impl Walk { } } -impl WalkSource for Walk { - fn ignore_hidden(&self) -> Option { - self.ignore_hidden - } - - fn ignore_files(&self) -> Option { - self.ignore_files - } - - fn ignore_dot(&self) -> Option { - self.ignore_dot - } - - fn ignore_vcs(&self) -> Option { - self.ignore_vcs - } - - fn ignore_global(&self) -> Option { - self.ignore_global - } - - fn ignore_parent(&self) -> Option { - self.ignore_parent - } -} - #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields, default)] #[serde(rename_all = "kebab-case")] pub struct EngineConfig { + /// Check binary files. pub binary: Option, + /// Verifying spelling in file names. pub check_filename: Option, + /// Verifying spelling in files. pub check_file: Option, #[serde(flatten)] pub tokenizer: Option, @@ -295,17 +156,17 @@ impl EngineConfig { } } - pub fn update(&mut self, source: &dyn EngineSource) { - if let Some(source) = source.binary() { + pub fn update(&mut self, source: &EngineConfig) { + if let Some(source) = source.binary { self.binary = Some(source); } - if let Some(source) = source.check_filename() { + if let Some(source) = source.check_filename { self.check_filename = Some(source); } - if let Some(source) = source.check_file() { + if let Some(source) = source.check_file { self.check_file = Some(source); } - if let Some(source) = source.tokenizer() { + if let Some(source) = source.tokenizer.as_ref() { let mut tokenizer = None; std::mem::swap(&mut tokenizer, &mut self.tokenizer); let mut tokenizer = tokenizer.unwrap_or_default(); @@ -313,7 +174,7 @@ impl EngineConfig { let mut tokenizer = Some(tokenizer); std::mem::swap(&mut tokenizer, &mut self.tokenizer); } - if let Some(source) = source.dict() { + if let Some(source) = source.dict.as_ref() { let mut dict = None; std::mem::swap(&mut dict, &mut self.dict); let mut dict = dict.unwrap_or_default(); @@ -336,36 +197,19 @@ impl EngineConfig { } } -impl EngineSource for EngineConfig { - fn binary(&self) -> Option { - self.binary - } - - fn check_filename(&self) -> Option { - self.check_filename - } - - fn check_file(&self) -> Option { - self.check_file - } - - fn tokenizer(&self) -> Option<&dyn TokenizerSource> { - self.tokenizer.as_ref().map(|t| t as &dyn TokenizerSource) - } - - fn dict(&self) -> Option<&dyn DictSource> { - self.dict.as_ref().map(|d| d as &dyn DictSource) - } -} - #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields, default)] #[serde(rename_all = "kebab-case")] pub struct TokenizerConfig { + /// Do not check identifiers that appear to be hexadecimal values. pub ignore_hex: Option, + /// Allow identifiers to start with digits, in addition to letters. pub identifier_leading_digits: Option, + /// Allow identifiers to start with one of these characters. pub identifier_leading_chars: Option, + /// Allow identifiers to include digits, in addition to letters. pub identifier_include_digits: Option, + /// Allow identifiers to include these characters. pub identifier_include_chars: Option, } @@ -385,21 +229,21 @@ impl TokenizerConfig { } } - pub fn update(&mut self, source: &dyn TokenizerSource) { - if let Some(source) = source.ignore_hex() { + pub fn update(&mut self, source: &TokenizerConfig) { + if let Some(source) = source.ignore_hex { self.ignore_hex = Some(source); } - if let Some(source) = source.identifier_leading_digits() { + if let Some(source) = source.identifier_leading_digits { self.identifier_leading_digits = Some(source); } - if let Some(source) = source.identifier_leading_chars() { - self.identifier_leading_chars = Some(kstring::KString::from_ref(source)); + if let Some(source) = source.identifier_leading_chars.as_ref() { + self.identifier_leading_chars = Some(source.clone()); } - if let Some(source) = source.identifier_include_digits() { + 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(kstring::KString::from_ref(source)); + if let Some(source) = source.identifier_include_chars.as_ref() { + self.identifier_include_chars = Some(source.clone()); } } @@ -424,28 +268,6 @@ impl TokenizerConfig { } } -impl TokenizerSource for TokenizerConfig { - fn ignore_hex(&self) -> Option { - self.ignore_hex - } - - fn identifier_leading_digits(&self) -> Option { - self.identifier_leading_digits - } - - fn identifier_leading_chars(&self) -> Option<&str> { - self.identifier_leading_chars.as_deref() - } - - fn identifier_include_digits(&self) -> Option { - self.identifier_include_digits - } - - fn identifier_include_chars(&self) -> Option<&str> { - self.identifier_include_chars.as_deref() - } -} - #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields, default)] #[serde(rename_all = "kebab-case")] @@ -465,19 +287,21 @@ impl DictConfig { } } - pub fn update(&mut self, source: &dyn DictSource) { - if let Some(source) = source.locale() { + pub fn update(&mut self, source: &DictConfig) { + if let Some(source) = source.locale { self.locale = Some(source); } self.extend_identifiers.extend( source - .extend_identifiers() - .map(|(k, v)| (kstring::KString::from_ref(k), kstring::KString::from_ref(v))), + .extend_identifiers + .iter() + .map(|(key, value)| (key.clone(), value.clone())), ); self.extend_words.extend( source - .extend_words() - .map(|(k, v)| (kstring::KString::from_ref(k), kstring::KString::from_ref(v))), + .extend_words + .iter() + .map(|(key, value)| (key.clone(), value.clone())), ); } @@ -502,28 +326,6 @@ impl DictConfig { } } -impl DictSource for DictConfig { - fn locale(&self) -> Option { - self.locale - } - - fn extend_identifiers(&self) -> Box + '_> { - Box::new( - self.extend_identifiers - .iter() - .map(|(k, v)| (k.as_str(), v.as_str())), - ) - } - - fn extend_words(&self) -> Box + '_> { - Box::new( - self.extend_words - .iter() - .map(|(k, v)| (k.as_str(), v.as_str())), - ) - } -} - fn find_project_file(dir: &std::path::Path, names: &[&str]) -> Option { let mut file_path = dir.join("placeholder"); for name in names { diff --git a/src/main.rs b/src/main.rs index 886337a..c0202f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,7 +59,7 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi let storage = typos_cli::policy::ConfigStorage::new(); let mut overrides = config::EngineConfig::default(); - overrides.update(&args.overrides); + overrides.update(&args.overrides.to_config()); let mut engine = typos_cli::policy::ConfigEngine::new(&storage); engine.set_isolated(args.isolated).set_overrides(overrides); if let Some(path) = args.custom_config.as_ref() { @@ -87,7 +87,7 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult { let storage = typos_cli::policy::ConfigStorage::new(); let mut overrides = config::EngineConfig::default(); - overrides.update(&args.overrides); + overrides.update(&args.overrides.to_config()); let mut engine = typos_cli::policy::ConfigEngine::new(&storage); engine.set_isolated(args.isolated).set_overrides(overrides); if let Some(path) = args.custom_config.as_ref() { From 3fd90b09f8ca394fba8287d902216d2844e0d812 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 5 Apr 2021 07:34:05 -0500 Subject: [PATCH 4/7] fix(cli): Allow CLI to override walking config --- src/args.rs | 6 +++--- src/config.rs | 3 +++ src/main.rs | 21 +++++++++++++-------- src/policy.rs | 25 +++++++++++-------------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/args.rs b/src/args.rs index 45c8ab1..938c362 100644 --- a/src/args.rs +++ b/src/args.rs @@ -79,9 +79,6 @@ pub(crate) struct Args { /// Write the current configuration to file with `-` for stdout pub(crate) dump_config: Option, - #[structopt(flatten)] - pub(crate) overrides: FileArgs, - #[structopt( long, possible_values(&Format::variants()), @@ -177,12 +174,15 @@ impl FileArgs { pub(crate) struct ConfigArgs { #[structopt(flatten)] walk: WalkArgs, + #[structopt(flatten)] + overrides: FileArgs, } impl ConfigArgs { pub fn to_config(&self) -> config::Config { config::Config { files: self.walk.to_config(), + overrides: Some(self.overrides.to_config()), ..Default::default() } } diff --git a/src/config.rs b/src/config.rs index a8e7323..9d21222 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,8 @@ use std::collections::HashMap; pub struct Config { pub files: Walk, pub default: EngineConfig, + #[serde(skip)] + pub overrides: Option, } impl Config { @@ -34,6 +36,7 @@ impl Config { Self { files: Walk::from_defaults(), default: EngineConfig::from_defaults(), + overrides: None, } } diff --git a/src/main.rs b/src/main.rs index c0202f8..4b9c00b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,14 +58,17 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi }; let storage = typos_cli::policy::ConfigStorage::new(); - let mut overrides = config::EngineConfig::default(); - overrides.update(&args.overrides.to_config()); let mut engine = typos_cli::policy::ConfigEngine::new(&storage); - engine.set_isolated(args.isolated).set_overrides(overrides); + engine.set_isolated(args.isolated); + + let mut overrides = config::Config::default(); if let Some(path) = args.custom_config.as_ref() { let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; - engine.set_custom_config(custom); + overrides.update(&custom); } + overrides.update(&args.config.to_config()); + engine.set_overrides(overrides); + let config = engine .load_config(cwd) .with_code(proc_exit::Code::CONFIG_ERR)?; @@ -86,14 +89,16 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult { let global_cwd = std::env::current_dir()?; let storage = typos_cli::policy::ConfigStorage::new(); - let mut overrides = config::EngineConfig::default(); - overrides.update(&args.overrides.to_config()); let mut engine = typos_cli::policy::ConfigEngine::new(&storage); - engine.set_isolated(args.isolated).set_overrides(overrides); + engine.set_isolated(args.isolated); + + let mut overrides = config::Config::default(); if let Some(path) = args.custom_config.as_ref() { let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; - engine.set_custom_config(custom); + overrides.update(&custom); } + overrides.update(&args.config.to_config()); + engine.set_overrides(overrides); let mut typos_found = false; let mut errors_found = false; diff --git a/src/policy.rs b/src/policy.rs index f6083a5..4ac8cdc 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -35,8 +35,7 @@ impl Default for ConfigStorage { pub struct ConfigEngine<'s> { storage: &'s ConfigStorage, - overrides: Option, - custom: Option, + overrides: Option, isolated: bool, configs: std::collections::HashMap, @@ -50,7 +49,6 @@ impl<'s> ConfigEngine<'s> { Self { storage, overrides: Default::default(), - custom: Default::default(), configs: Default::default(), isolated: false, walk: Default::default(), @@ -59,16 +57,11 @@ impl<'s> ConfigEngine<'s> { } } - pub fn set_overrides(&mut self, overrides: crate::config::EngineConfig) -> &mut Self { + pub fn set_overrides(&mut self, overrides: crate::config::Config) -> &mut Self { self.overrides = Some(overrides); self } - pub fn set_custom_config(&mut self, custom: crate::config::Config) -> &mut Self { - self.custom = Some(custom); - self - } - pub fn set_isolated(&mut self, isolated: bool) -> &mut Self { self.isolated = isolated; self @@ -125,11 +118,8 @@ impl<'s> ConfigEngine<'s> { config.update(&derived); } } - if let Some(custom) = self.custom.as_ref() { - config.update(custom); - } if let Some(overrides) = self.overrides.as_ref() { - config.default.update(overrides); + config.update(overrides); } Ok(config) @@ -141,7 +131,14 @@ impl<'s> ConfigEngine<'s> { } let config = self.load_config(cwd)?; - let crate::config::Config { files, default } = config; + let crate::config::Config { + files, + mut default, + overrides, + } = config; + if let Some(overrides) = overrides { + default.update(&overrides); + } let walk = self.walk.intern(files); let default = self.init_file_config(default)?; From a101df95c2e9c6f8a18cc6da78bdbfb9bd8b5e8d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 5 Apr 2021 21:03:41 -0500 Subject: [PATCH 5/7] feat(config): Per-file type settings Fixes #14 --- docs/reference.md | 1 + src/args.rs | 2 +- src/config.rs | 7 ++++-- src/policy.rs | 60 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index 32039da..9ebf8c2 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -31,3 +31,4 @@ Configuration is read from the following (in precedence order) | default.locale | --locale | en, en-us, en-gb, en-ca, en-au | English dialect to correct to. | | default.extend-identifiers | \- | table of strings | Corrections for identifiers. When the correction is blank, the word is never valid. When the correction is the key, the word is always valid. | | default.extend-words | \- | table of strings | Corrections for identifiers. When the correction is blank, the word is never valid. When the correction is the key, the word is always valid. | +| type..binary | | | See `default.` for child keys. | diff --git a/src/args.rs b/src/args.rs index 938c362..1dd4863 100644 --- a/src/args.rs +++ b/src/args.rs @@ -182,7 +182,7 @@ impl ConfigArgs { pub fn to_config(&self) -> config::Config { config::Config { files: self.walk.to_config(), - overrides: Some(self.overrides.to_config()), + overrides: self.overrides.to_config(), ..Default::default() } } diff --git a/src/config.rs b/src/config.rs index 9d21222..e0c6a10 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,8 +6,10 @@ use std::collections::HashMap; pub struct Config { pub files: Walk, pub default: EngineConfig, + #[serde(rename = "type")] + pub type_: std::collections::HashMap, #[serde(skip)] - pub overrides: Option, + pub overrides: EngineConfig, } impl Config { @@ -36,7 +38,8 @@ impl Config { Self { files: Walk::from_defaults(), default: EngineConfig::from_defaults(), - overrides: None, + type_: Default::default(), + overrides: EngineConfig::default(), } } diff --git a/src/policy.rs b/src/policy.rs index 4ac8cdc..cbb474d 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -77,12 +77,13 @@ impl<'s> ConfigEngine<'s> { pub fn policy(&self, path: &std::path::Path) -> Policy<'_, '_> { let dir = self.get_dir(path).expect("`walk()` should be called first"); + let file_config = dir.get_file_config(path); Policy { - check_filenames: dir.default.check_filenames, - check_files: dir.default.check_files, - binary: dir.default.binary, - tokenizer: self.get_tokenizer(&dir.default), - dict: self.get_dict(&dir.default), + check_filenames: file_config.check_filenames, + check_files: file_config.check_files, + binary: file_config.binary, + tokenizer: self.get_tokenizer(&file_config), + dict: self.get_dict(&file_config), } } @@ -134,25 +135,37 @@ impl<'s> ConfigEngine<'s> { let crate::config::Config { files, mut default, + type_, overrides, } = config; - if let Some(overrides) = overrides { - default.update(&overrides); - } let walk = self.walk.intern(files); - let default = self.init_file_config(default)?; - let dir = DirConfig { walk, default }; + let types = type_ + .into_iter() + .map(|(type_, type_engine)| { + let mut new_type_engine = default.clone(); + new_type_engine.update(&type_engine); + new_type_engine.update(&overrides); + let type_config = self.init_file_config(new_type_engine); + (type_, type_config) + }) + .collect(); + default.update(&overrides); + let default = self.init_file_config(default); + + let dir = DirConfig { + walk, + default, + types, + type_matcher: ignore::types::TypesBuilder::new().add_defaults().build()?, + }; self.configs.insert(cwd.to_owned(), dir); Ok(()) } - fn init_file_config( - &mut self, - engine: crate::config::EngineConfig, - ) -> Result { + fn init_file_config(&mut self, engine: crate::config::EngineConfig) -> FileConfig { let binary = engine.binary(); let check_filename = engine.check_filename(); let check_file = engine.check_file(); @@ -194,7 +207,7 @@ impl<'s> ConfigEngine<'s> { tokenizer, dict, }; - Ok(file) + file } } @@ -226,11 +239,28 @@ impl Default for Intern { } } +#[derive(Clone, Debug)] struct DirConfig { walk: usize, default: FileConfig, + types: std::collections::HashMap, + type_matcher: ignore::types::Types, } +impl DirConfig { + fn get_file_config(&self, path: &std::path::Path) -> FileConfig { + let match_ = self.type_matcher.matched(path, false); + let name = match_ + .inner() + .and_then(|g| g.file_type_def()) + .map(|f| f.name()); + + name.and_then(|name| self.types.get(name).copied()) + .unwrap_or(self.default) + } +} + +#[derive(Copy, Clone, Debug)] struct FileConfig { tokenizer: usize, dict: usize, From 8f365ee1557d7d3dbe5a66f0725263f3b36e02ff Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 5 Apr 2021 21:15:41 -0500 Subject: [PATCH 6/7] feat(config): Show available type definitions --- docs/reference.md | 2 +- src/args.rs | 4 ++++ src/main.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++ src/policy.rs | 10 +++++++++- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index 9ebf8c2..0cfabf5 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -31,4 +31,4 @@ Configuration is read from the following (in precedence order) | default.locale | --locale | en, en-us, en-gb, en-ca, en-au | English dialect to correct to. | | default.extend-identifiers | \- | table of strings | Corrections for identifiers. When the correction is blank, the word is never valid. When the correction is the key, the word is always valid. | | default.extend-words | \- | table of strings | Corrections for identifiers. When the correction is blank, the word is never valid. When the correction is the key, the word is always valid. | -| type..binary | | | See `default.` for child keys. | +| type..binary | | | See `default.` for child keys. Run with `--type-list` to see available ``s | diff --git a/src/args.rs b/src/args.rs index 1dd4863..dcec8dc 100644 --- a/src/args.rs +++ b/src/args.rs @@ -79,6 +79,10 @@ pub(crate) struct Args { /// Write the current configuration to file with `-` for stdout pub(crate) dump_config: Option, + #[structopt(long, group = "mode")] + /// Show all supported file types. + pub(crate) type_list: bool, + #[structopt( long, possible_values(&Format::variants()), diff --git a/src/main.rs b/src/main.rs index 4b9c00b..0d9139d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,6 +35,8 @@ fn run() -> proc_exit::ExitResult { if let Some(output_path) = args.dump_config.as_ref() { run_dump_config(&args, output_path) + } else if args.type_list { + run_type_list(&args) } else { run_checks(&args) } @@ -85,6 +87,54 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi Ok(()) } +fn run_type_list(args: &args::Args) -> proc_exit::ExitResult { + let global_cwd = std::env::current_dir()?; + + let path = &args.path[0]; + let path = if path == std::path::Path::new("-") { + path.to_owned() + } else { + path.canonicalize().with_code(proc_exit::Code::USAGE_ERR)? + }; + let cwd = if path == std::path::Path::new("-") { + global_cwd.as_path() + } else if path.is_file() { + path.parent().unwrap() + } else { + path.as_path() + }; + + let storage = typos_cli::policy::ConfigStorage::new(); + let mut engine = typos_cli::policy::ConfigEngine::new(&storage); + engine.set_isolated(args.isolated); + + let mut overrides = config::Config::default(); + if let Some(path) = args.custom_config.as_ref() { + let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; + overrides.update(&custom); + } + overrides.update(&args.config.to_config()); + engine.set_overrides(overrides); + + engine + .init_dir(cwd) + .with_code(proc_exit::Code::CONFIG_ERR)?; + let definitions = engine.file_types(cwd); + + let stdout = std::io::stdout(); + let mut handle = stdout.lock(); + for def in definitions { + writeln!( + handle, + "{}: {}", + def.name(), + itertools::join(def.globs(), ", ") + )?; + } + + Ok(()) +} + fn run_checks(args: &args::Args) -> proc_exit::ExitResult { let global_cwd = std::env::current_dir()?; diff --git a/src/policy.rs b/src/policy.rs index cbb474d..bb0d896 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -67,7 +67,7 @@ impl<'s> ConfigEngine<'s> { self } - pub fn walk(&mut self, cwd: &std::path::Path) -> &crate::config::Walk { + pub fn walk(&self, cwd: &std::path::Path) -> &crate::config::Walk { let dir = self .configs .get(cwd) @@ -75,6 +75,14 @@ impl<'s> ConfigEngine<'s> { self.get_walk(dir) } + pub fn file_types(&self, cwd: &std::path::Path) -> &[ignore::types::FileTypeDef] { + let dir = self + .configs + .get(cwd) + .expect("`init_dir` must be called first"); + dir.type_matcher.definitions() + } + pub fn policy(&self, path: &std::path::Path) -> Policy<'_, '_> { let dir = self.get_dir(path).expect("`walk()` should be called first"); let file_config = dir.get_file_config(path); From aa21439502f287e95c409c40615c03f196685ce7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 6 Apr 2021 10:15:14 -0500 Subject: [PATCH 7/7] style: Clippy --- src/args.rs | 2 -- src/policy.rs | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/args.rs b/src/args.rs index dcec8dc..aa8bf00 100644 --- a/src/args.rs +++ b/src/args.rs @@ -141,7 +141,6 @@ impl FileArgs { locale: self.locale, ..Default::default() }), - ..Default::default() } } @@ -241,7 +240,6 @@ impl WalkArgs { ignore_vcs: self.ignore_vcs(), ignore_global: self.ignore_global(), ignore_parent: self.ignore_parent(), - ..Default::default() } } diff --git a/src/policy.rs b/src/policy.rs index bb0d896..cded1c9 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -208,14 +208,13 @@ impl<'s> ConfigEngine<'s> { let dict = self.dict.intern(dict); let tokenizer = self.tokenizer.intern(tokenizer); - let file = FileConfig { + FileConfig { check_filenames: check_filename, check_files: check_file, binary, tokenizer, dict, - }; - file + } } }