Merge pull request #219 from epage/types

feat(config): Per-file type settings
This commit is contained in:
Ed Page 2021-04-06 13:27:51 -05:00 committed by GitHub
commit 0656a62860
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 250 additions and 320 deletions

View file

@ -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.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-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. | | 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.<name>.binary | <varied> | <varied> | See `default.` for child keys. Run with `--type-list` to see available `<name>`s |

View file

@ -79,8 +79,9 @@ pub(crate) struct Args {
/// Write the current configuration to file with `-` for stdout /// Write the current configuration to file with `-` for stdout
pub(crate) dump_config: Option<std::path::PathBuf>, pub(crate) dump_config: Option<std::path::PathBuf>,
#[structopt(flatten)] #[structopt(long, group = "mode")]
pub(crate) overrides: FileArgs, /// Show all supported file types.
pub(crate) type_list: bool,
#[structopt( #[structopt(
long, long,
@ -129,7 +130,20 @@ pub(crate) struct FileArgs {
pub(crate) locale: Option<config::Locale>, pub(crate) locale: Option<config::Locale>,
} }
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()
}),
}
}
fn binary(&self) -> Option<bool> { 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),
@ -156,16 +170,6 @@ impl config::EngineSource for FileArgs {
(_, _) => unreachable!("StructOpt should make this impossible"), (_, _) => unreachable!("StructOpt should make this impossible"),
} }
} }
fn dict(&self) -> Option<&dyn config::DictSource> {
Some(self)
}
}
impl config::DictSource for FileArgs {
fn locale(&self) -> Option<config::Locale> {
self.locale
}
} }
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@ -173,11 +177,17 @@ impl config::DictSource for FileArgs {
pub(crate) struct ConfigArgs { pub(crate) struct ConfigArgs {
#[structopt(flatten)] #[structopt(flatten)]
walk: WalkArgs, walk: WalkArgs,
#[structopt(flatten)]
overrides: FileArgs,
} }
impl config::ConfigSource for ConfigArgs { impl ConfigArgs {
fn walk(&self) -> Option<&dyn config::WalkSource> { pub fn to_config(&self) -> config::Config {
Some(&self.walk) config::Config {
files: self.walk.to_config(),
overrides: self.overrides.to_config(),
..Default::default()
}
} }
} }
@ -221,7 +231,18 @@ pub(crate) struct WalkArgs {
ignore_vcs: bool, 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(),
}
}
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),

View file

@ -1,119 +1,15 @@
use std::collections::HashMap; 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<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 EngineSource {
/// Check binary files.
fn binary(&self) -> Option<bool> {
None
}
/// Verifying spelling in file names.
fn check_filename(&self) -> Option<bool> {
None
}
/// Verifying spelling in files.
fn check_file(&self) -> Option<bool> {
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<bool> {
None
}
/// Allow identifiers to start with digits, in addition to letters.
fn identifier_leading_digits(&self) -> Option<bool> {
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<bool> {
None
}
/// Allow identifiers to include these characters.
fn identifier_include_chars(&self) -> Option<&str> {
None
}
}
pub trait DictSource {
fn locale(&self) -> Option<Locale> {
None
}
fn extend_identifiers(&self) -> Box<dyn Iterator<Item = (&str, &str)> + '_> {
Box::new(None.into_iter())
}
fn extend_words(&self) -> Box<dyn Iterator<Item = (&str, &str)> + '_> {
Box::new(None.into_iter())
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, default)] #[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Config { pub struct Config {
pub files: Walk, pub files: Walk,
pub default: EngineConfig, pub default: EngineConfig,
#[serde(rename = "type")]
pub type_: std::collections::HashMap<kstring::KString, EngineConfig>,
#[serde(skip)]
pub overrides: EngineConfig,
} }
impl Config { impl Config {
@ -142,26 +38,14 @@ impl Config {
Self { Self {
files: Walk::from_defaults(), files: Walk::from_defaults(),
default: EngineConfig::from_defaults(), default: EngineConfig::from_defaults(),
type_: Default::default(),
overrides: EngineConfig::default(),
} }
} }
pub fn update(&mut self, source: &dyn ConfigSource) { pub fn update(&mut self, source: &Config) {
if let Some(walk) = source.walk() { self.files.update(&source.files);
self.files.update(walk); self.default.update(&source.default);
}
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)
} }
} }
@ -169,11 +53,17 @@ impl ConfigSource for Config {
#[serde(deny_unknown_fields, default)] #[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Walk { pub struct Walk {
/// Skip hidden files and directories.
pub ignore_hidden: Option<bool>, pub ignore_hidden: Option<bool>,
/// Respect ignore files.
pub ignore_files: Option<bool>, pub ignore_files: Option<bool>,
/// Respect .ignore files.
pub ignore_dot: Option<bool>, pub ignore_dot: Option<bool>,
/// Respect ignore files in vcs directories.
pub ignore_vcs: Option<bool>, pub ignore_vcs: Option<bool>,
/// Respect global ignore files.
pub ignore_global: Option<bool>, pub ignore_global: Option<bool>,
/// Respect ignore files in parent directories.
pub ignore_parent: Option<bool>, pub ignore_parent: Option<bool>,
} }
@ -190,28 +80,28 @@ impl Walk {
} }
} }
pub fn update(&mut self, source: &dyn WalkSource) { pub fn update(&mut self, source: &Walk) {
if let Some(source) = source.ignore_hidden() { if let Some(source) = source.ignore_hidden {
self.ignore_hidden = Some(source); 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_files = Some(source);
self.ignore_dot = None; self.ignore_dot = None;
self.ignore_vcs = None; self.ignore_vcs = None;
self.ignore_global = None; self.ignore_global = None;
self.ignore_parent = None; self.ignore_parent = None;
} }
if let Some(source) = source.ignore_dot() { if let Some(source) = source.ignore_dot {
self.ignore_dot = Some(source); 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_vcs = Some(source);
self.ignore_global = None; self.ignore_global = None;
} }
if let Some(source) = source.ignore_global() { if let Some(source) = source.ignore_global {
self.ignore_global = Some(source); self.ignore_global = Some(source);
} }
if let Some(source) = source.ignore_parent() { if let Some(source) = source.ignore_parent {
self.ignore_parent = Some(source); self.ignore_parent = Some(source);
} }
} }
@ -240,38 +130,15 @@ impl Walk {
} }
} }
impl WalkSource for Walk {
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)] #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, default)] #[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct EngineConfig { pub struct EngineConfig {
/// Check binary files.
pub binary: Option<bool>, pub binary: Option<bool>,
/// Verifying spelling in file names.
pub check_filename: Option<bool>, pub check_filename: Option<bool>,
/// Verifying spelling in files.
pub check_file: Option<bool>, pub check_file: Option<bool>,
#[serde(flatten)] #[serde(flatten)]
pub tokenizer: Option<TokenizerConfig>, pub tokenizer: Option<TokenizerConfig>,
@ -295,17 +162,17 @@ impl EngineConfig {
} }
} }
pub fn update(&mut self, source: &dyn EngineSource) { pub fn update(&mut self, source: &EngineConfig) {
if let Some(source) = source.binary() { if let Some(source) = source.binary {
self.binary = Some(source); self.binary = Some(source);
} }
if let Some(source) = source.check_filename() { if let Some(source) = source.check_filename {
self.check_filename = Some(source); self.check_filename = Some(source);
} }
if let Some(source) = source.check_file() { if let Some(source) = source.check_file {
self.check_file = Some(source); self.check_file = Some(source);
} }
if let Some(source) = source.tokenizer() { if let Some(source) = source.tokenizer.as_ref() {
let mut tokenizer = None; let mut tokenizer = None;
std::mem::swap(&mut tokenizer, &mut self.tokenizer); std::mem::swap(&mut tokenizer, &mut self.tokenizer);
let mut tokenizer = tokenizer.unwrap_or_default(); let mut tokenizer = tokenizer.unwrap_or_default();
@ -313,7 +180,7 @@ impl EngineConfig {
let mut tokenizer = Some(tokenizer); let mut tokenizer = Some(tokenizer);
std::mem::swap(&mut tokenizer, &mut self.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; let mut dict = None;
std::mem::swap(&mut dict, &mut self.dict); std::mem::swap(&mut dict, &mut self.dict);
let mut dict = dict.unwrap_or_default(); let mut dict = dict.unwrap_or_default();
@ -336,36 +203,19 @@ impl EngineConfig {
} }
} }
impl EngineSource for EngineConfig {
fn binary(&self) -> Option<bool> {
self.binary
}
fn check_filename(&self) -> Option<bool> {
self.check_filename
}
fn check_file(&self) -> Option<bool> {
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)] #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, default)] #[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct TokenizerConfig { pub struct TokenizerConfig {
/// Do not check identifiers that appear to be hexadecimal values.
pub ignore_hex: Option<bool>, pub ignore_hex: Option<bool>,
/// Allow identifiers to start with digits, in addition to letters.
pub identifier_leading_digits: Option<bool>, pub identifier_leading_digits: Option<bool>,
/// Allow identifiers to start with one of these characters.
pub identifier_leading_chars: Option<kstring::KString>, pub identifier_leading_chars: Option<kstring::KString>,
/// Allow identifiers to include digits, in addition to letters.
pub identifier_include_digits: Option<bool>, pub identifier_include_digits: Option<bool>,
/// Allow identifiers to include these characters.
pub identifier_include_chars: Option<kstring::KString>, pub identifier_include_chars: Option<kstring::KString>,
} }
@ -385,21 +235,21 @@ impl TokenizerConfig {
} }
} }
pub fn update(&mut self, source: &dyn TokenizerSource) { pub fn update(&mut self, source: &TokenizerConfig) {
if let Some(source) = source.ignore_hex() { if let Some(source) = source.ignore_hex {
self.ignore_hex = Some(source); 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); self.identifier_leading_digits = Some(source);
} }
if let Some(source) = source.identifier_leading_chars() { if let Some(source) = source.identifier_leading_chars.as_ref() {
self.identifier_leading_chars = Some(kstring::KString::from_ref(source)); 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); self.identifier_include_digits = Some(source);
} }
if let Some(source) = source.identifier_include_chars() { if let Some(source) = source.identifier_include_chars.as_ref() {
self.identifier_include_chars = Some(kstring::KString::from_ref(source)); self.identifier_include_chars = Some(source.clone());
} }
} }
@ -424,28 +274,6 @@ impl TokenizerConfig {
} }
} }
impl TokenizerSource for TokenizerConfig {
fn ignore_hex(&self) -> Option<bool> {
self.ignore_hex
}
fn identifier_leading_digits(&self) -> Option<bool> {
self.identifier_leading_digits
}
fn identifier_leading_chars(&self) -> Option<&str> {
self.identifier_leading_chars.as_deref()
}
fn identifier_include_digits(&self) -> Option<bool> {
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)] #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, default)] #[serde(deny_unknown_fields, default)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
@ -465,19 +293,21 @@ impl DictConfig {
} }
} }
pub fn update(&mut self, source: &dyn DictSource) { pub fn update(&mut self, source: &DictConfig) {
if let Some(source) = source.locale() { if let Some(source) = source.locale {
self.locale = Some(source); self.locale = Some(source);
} }
self.extend_identifiers.extend( self.extend_identifiers.extend(
source source
.extend_identifiers() .extend_identifiers
.map(|(k, v)| (kstring::KString::from_ref(k), kstring::KString::from_ref(v))), .iter()
.map(|(key, value)| (key.clone(), value.clone())),
); );
self.extend_words.extend( self.extend_words.extend(
source source
.extend_words() .extend_words
.map(|(k, v)| (kstring::KString::from_ref(k), kstring::KString::from_ref(v))), .iter()
.map(|(key, value)| (key.clone(), value.clone())),
); );
} }
@ -502,28 +332,6 @@ impl DictConfig {
} }
} }
impl DictSource for DictConfig {
fn locale(&self) -> Option<Locale> {
self.locale
}
fn extend_identifiers(&self) -> Box<dyn Iterator<Item = (&str, &str)> + '_> {
Box::new(
self.extend_identifiers
.iter()
.map(|(k, v)| (k.as_str(), v.as_str())),
)
}
fn extend_words(&self) -> Box<dyn Iterator<Item = (&str, &str)> + '_> {
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<std::path::PathBuf> { fn find_project_file(dir: &std::path::Path, names: &[&str]) -> Option<std::path::PathBuf> {
let mut file_path = dir.join("placeholder"); let mut file_path = dir.join("placeholder");
for name in names { for name in names {

View file

@ -35,6 +35,8 @@ fn run() -> proc_exit::ExitResult {
if let Some(output_path) = args.dump_config.as_ref() { if let Some(output_path) = args.dump_config.as_ref() {
run_dump_config(&args, output_path) run_dump_config(&args, output_path)
} else if args.type_list {
run_type_list(&args)
} else { } else {
run_checks(&args) run_checks(&args)
} }
@ -58,14 +60,17 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi
}; };
let storage = typos_cli::policy::ConfigStorage::new(); let storage = typos_cli::policy::ConfigStorage::new();
let mut overrides = config::EngineConfig::default();
overrides.update(&args.overrides);
let mut engine = typos_cli::policy::ConfigEngine::new(&storage); 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() { if let Some(path) = args.custom_config.as_ref() {
let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; 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 let config = engine
.load_config(cwd) .load_config(cwd)
.with_code(proc_exit::Code::CONFIG_ERR)?; .with_code(proc_exit::Code::CONFIG_ERR)?;
@ -82,18 +87,68 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi
Ok(()) 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 { fn run_checks(args: &args::Args) -> proc_exit::ExitResult {
let global_cwd = std::env::current_dir()?; let global_cwd = std::env::current_dir()?;
let storage = typos_cli::policy::ConfigStorage::new(); let storage = typos_cli::policy::ConfigStorage::new();
let mut overrides = config::EngineConfig::default();
overrides.update(&args.overrides);
let mut engine = typos_cli::policy::ConfigEngine::new(&storage); 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() { if let Some(path) = args.custom_config.as_ref() {
let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; 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 typos_found = false;
let mut errors_found = false; let mut errors_found = false;
@ -114,19 +169,19 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult {
engine engine
.init_dir(cwd) .init_dir(cwd)
.with_code(proc_exit::Code::CONFIG_ERR)?; .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 threads = if path.is_file() { 1 } else { args.threads };
let single_threaded = threads == 1; let single_threaded = threads == 1;
let mut walk = ignore::WalkBuilder::new(path); let mut walk = ignore::WalkBuilder::new(path);
walk.threads(args.threads) walk.threads(args.threads)
.hidden(files.ignore_hidden()) .hidden(walk_policy.ignore_hidden())
.ignore(files.ignore_dot()) .ignore(walk_policy.ignore_dot())
.git_global(files.ignore_global()) .git_global(walk_policy.ignore_global())
.git_ignore(files.ignore_vcs()) .git_ignore(walk_policy.ignore_vcs())
.git_exclude(files.ignore_vcs()) .git_exclude(walk_policy.ignore_vcs())
.parents(files.ignore_parent()); .parents(walk_policy.ignore_parent());
// HACK: Diff doesn't handle mixing content // HACK: Diff doesn't handle mixing content
let output_reporter = if args.diff { let output_reporter = if args.diff {

View file

@ -35,12 +35,11 @@ impl Default for ConfigStorage {
pub struct ConfigEngine<'s> { pub struct ConfigEngine<'s> {
storage: &'s ConfigStorage, storage: &'s ConfigStorage,
overrides: Option<crate::config::EngineConfig>, overrides: Option<crate::config::Config>,
custom: Option<crate::config::Config>,
isolated: bool, isolated: bool,
configs: std::collections::HashMap<std::path::PathBuf, DirConfig>, configs: std::collections::HashMap<std::path::PathBuf, DirConfig>,
files: Intern<crate::config::Walk>, walk: Intern<crate::config::Walk>,
tokenizer: Intern<typos::tokens::Tokenizer>, tokenizer: Intern<typos::tokens::Tokenizer>,
dict: Intern<crate::dict::Override<'s, 's, crate::dict::BuiltIn>>, dict: Intern<crate::dict::Override<'s, 's, crate::dict::BuiltIn>>,
} }
@ -50,61 +49,62 @@ impl<'s> ConfigEngine<'s> {
Self { Self {
storage, storage,
overrides: Default::default(), overrides: Default::default(),
custom: Default::default(),
configs: Default::default(), configs: Default::default(),
isolated: false, isolated: false,
files: Default::default(), walk: Default::default(),
tokenizer: Default::default(), tokenizer: Default::default(),
dict: Default::default(), dict: Default::default(),
} }
} }
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.overrides = Some(overrides);
self 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 { pub fn set_isolated(&mut self, isolated: bool) -> &mut Self {
self.isolated = isolated; self.isolated = isolated;
self self
} }
pub fn files(&mut self, cwd: &std::path::Path) -> &crate::config::Walk { pub fn walk(&self, cwd: &std::path::Path) -> &crate::config::Walk {
let dir = self let dir = self
.configs .configs
.get(cwd) .get(cwd)
.expect("`init_dir` must be called first"); .expect("`init_dir` must be called first");
self.get_files(dir) 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<'_, '_> { pub fn policy(&self, path: &std::path::Path) -> Policy<'_, '_> {
let dir = self let dir = self.get_dir(path).expect("`walk()` should be called first");
.get_dir(path) let file_config = dir.get_file_config(path);
.expect("`files()` should be called first");
Policy { Policy {
check_filenames: dir.check_filenames, check_filenames: file_config.check_filenames,
check_files: dir.check_files, check_files: file_config.check_files,
binary: dir.binary, binary: file_config.binary,
tokenizer: self.get_tokenizer(dir), tokenizer: self.get_tokenizer(&file_config),
dict: self.get_dict(dir), dict: self.get_dict(&file_config),
} }
} }
fn get_files(&self, dir: &DirConfig) -> &crate::config::Walk { fn get_walk(&self, dir: &DirConfig) -> &crate::config::Walk {
self.files.get(dir.files) self.walk.get(dir.walk)
} }
fn get_tokenizer(&self, dir: &DirConfig) -> &typos::tokens::Tokenizer { fn get_tokenizer(&self, file: &FileConfig) -> &typos::tokens::Tokenizer {
self.tokenizer.get(dir.tokenizer) self.tokenizer.get(file.tokenizer)
} }
fn get_dict(&self, dir: &DirConfig) -> &dyn typos::Dictionary { fn get_dict(&self, file: &FileConfig) -> &dyn typos::Dictionary {
self.dict.get(dir.dict) self.dict.get(file.dict)
} }
fn get_dir(&self, path: &std::path::Path) -> Option<&DirConfig> { fn get_dir(&self, path: &std::path::Path) -> Option<&DirConfig> {
@ -127,11 +127,8 @@ impl<'s> ConfigEngine<'s> {
config.update(&derived); config.update(&derived);
} }
} }
if let Some(custom) = self.custom.as_ref() {
config.update(custom);
}
if let Some(overrides) = self.overrides.as_ref() { if let Some(overrides) = self.overrides.as_ref() {
config.default.update(overrides); config.update(overrides);
} }
Ok(config) Ok(config)
@ -143,14 +140,46 @@ impl<'s> ConfigEngine<'s> {
} }
let config = self.load_config(cwd)?; let config = self.load_config(cwd)?;
let crate::config::Config {
files,
mut default,
type_,
overrides,
} = config;
let crate::config::Config { files, default } = config; let walk = self.walk.intern(files);
let binary = default.binary();
let check_filename = default.check_filename(); let types = type_
let check_file = default.check_file(); .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) -> FileConfig {
let binary = engine.binary();
let check_filename = engine.check_filename();
let check_file = engine.check_file();
let crate::config::EngineConfig { let crate::config::EngineConfig {
tokenizer, dict, .. tokenizer, dict, ..
} = default; } = engine;
let tokenizer_config = let tokenizer_config =
tokenizer.unwrap_or_else(crate::config::TokenizerConfig::from_defaults); tokenizer.unwrap_or_else(crate::config::TokenizerConfig::from_defaults);
let dict_config = dict.unwrap_or_else(crate::config::DictConfig::from_defaults); let dict_config = dict.unwrap_or_else(crate::config::DictConfig::from_defaults);
@ -177,20 +206,15 @@ impl<'s> ConfigEngine<'s> {
); );
let dict = self.dict.intern(dict); let dict = self.dict.intern(dict);
let files = self.files.intern(files);
let tokenizer = self.tokenizer.intern(tokenizer); let tokenizer = self.tokenizer.intern(tokenizer);
let dir = DirConfig { FileConfig {
files,
check_filenames: check_filename, check_filenames: check_filename,
check_files: check_file, check_files: check_file,
binary, binary,
tokenizer, tokenizer,
dict, dict,
}; }
self.configs.insert(cwd.to_owned(), dir);
Ok(())
} }
} }
@ -222,8 +246,29 @@ impl<T> Default for Intern<T> {
} }
} }
#[derive(Clone, Debug)]
struct DirConfig { struct DirConfig {
files: usize, walk: usize,
default: FileConfig,
types: std::collections::HashMap<kstring::KString, FileConfig>,
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, tokenizer: usize,
dict: usize, dict: usize,
check_filenames: bool, check_filenames: bool,