typos/src/main.rs

200 lines
6.2 KiB
Rust
Raw Normal View History

2019-01-23 09:33:51 -05:00
// 2015-edition macros.
#[macro_use]
extern crate clap;
2019-07-19 23:45:41 -04:00
use std::io::Write;
2019-01-22 17:01:33 -05:00
use structopt::StructOpt;
2020-03-23 21:33:59 -04:00
mod args;
2020-03-23 21:37:06 -04:00
mod checks;
mod config;
mod dict;
2020-11-11 19:19:26 -05:00
mod diff;
mod replace;
2020-11-16 21:02:10 -05:00
use proc_exit::WithCodeResultExt;
2020-03-23 21:38:11 -04:00
fn main() {
2020-11-23 13:40:55 -05:00
human_panic::setup_panic!();
2020-11-23 11:08:38 -05:00
let result = run();
proc_exit::exit(result);
2019-07-19 23:45:41 -04:00
}
2020-11-23 11:08:38 -05:00
fn run() -> proc_exit::ExitResult {
// clap's `get_matches` uses Failure rather than Usage, so bypass it for `get_matches_safe`.
let args = match args::Args::from_args_safe() {
Ok(args) => args,
Err(e) if e.use_stderr() => {
2020-11-16 21:02:10 -05:00
return Err(proc_exit::Code::USAGE_ERR.with_message(e));
}
Err(e) => {
2020-11-16 21:02:10 -05:00
writeln!(std::io::stdout(), "{}", e)?;
2020-11-23 11:08:38 -05:00
return proc_exit::Code::SUCCESS.ok();
}
};
2019-01-22 17:01:33 -05:00
2019-10-17 22:49:26 -04:00
init_logging(args.verbose.log_level());
2019-07-19 23:45:41 -04:00
2020-03-21 15:33:51 -04:00
let config = if let Some(path) = args.custom_config.as_ref() {
2020-11-16 21:02:10 -05:00
config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?
2020-03-21 15:33:51 -04:00
} else {
config::Config::default()
};
let mut typos_found = false;
let mut errors_found = false;
2019-08-07 12:09:01 -04:00
for path in args.path.iter() {
2020-11-16 21:02:10 -05:00
let path = path.canonicalize().with_code(proc_exit::Code::USAGE_ERR)?;
let cwd = if path.is_file() {
path.parent().unwrap()
} else {
path.as_path()
};
let mut config = config.clone();
2019-08-07 12:09:01 -04:00
if !args.isolated {
2020-11-16 21:02:10 -05:00
let derived = config::Config::derive(cwd).with_code(proc_exit::Code::CONFIG_ERR)?;
config.update(&derived);
}
2019-08-07 12:09:01 -04:00
config.update(&args.config);
config.default.update(&args.overrides);
let config = config;
let parser = typos::tokens::TokenizerBuilder::new()
.ignore_hex(config.default.ignore_hex())
.leading_digits(config.default.identifier_leading_digits())
.leading_chars(config.default.identifier_leading_chars().to_owned())
.include_digits(config.default.identifier_include_digits())
.include_chars(config.default.identifier_include_chars().to_owned())
.build();
let dictionary = crate::dict::BuiltIn::new(config.default.locale());
let mut dictionary = crate::dict::Override::new(dictionary);
dictionary.identifiers(config.default.extend_identifiers());
dictionary.words(config.default.extend_words());
2019-11-14 22:09:56 -05:00
let mut settings = typos::checks::TyposSettings::new();
settings
.check_filenames(config.default.check_filename())
.check_files(config.default.check_file())
.binary(config.files.binary());
let threads = if path.is_file() { 1 } else { args.threads };
let single_threaded = threads == 1;
let mut walk = ignore::WalkBuilder::new(path);
2019-10-25 18:34:21 -04:00
walk.threads(args.threads)
.hidden(config.files.ignore_hidden())
.ignore(config.files.ignore_dot())
.git_global(config.files.ignore_global())
.git_ignore(config.files.ignore_vcs())
.git_exclude(config.files.ignore_vcs())
.parents(config.files.ignore_parent());
2020-11-16 21:02:10 -05:00
// HACK: Diff doesn't handle mixing content
let output_reporter = if args.diff {
&args::PRINT_SILENT
} else {
args.format.reporter()
};
let status_reporter = typos::report::MessageStatus::new(output_reporter);
let mut reporter: &dyn typos::report::Report = &status_reporter;
let replace_reporter = replace::Replace::new(reporter);
2020-11-11 19:19:26 -05:00
let diff_reporter = diff::Diff::new(reporter);
if args.diff {
reporter = &diff_reporter;
} else if args.write_changes {
reporter = &replace_reporter;
}
2020-11-10 07:26:42 -05:00
let (files, identifier_parser, word_parser, checks);
let selected_checks: &dyn typos::checks::Check = if args.files {
files = settings.build_files();
&files
} else if args.identifiers {
identifier_parser = settings.build_identifier_parser();
&identifier_parser
} else if args.words {
word_parser = settings.build_word_parser();
&word_parser
2020-03-23 19:39:45 -04:00
} else {
2020-11-10 07:26:42 -05:00
checks = settings.build_typos();
&checks
};
2020-11-16 21:02:10 -05:00
if single_threaded {
2020-11-10 07:26:42 -05:00
checks::check_path(
walk.build(),
selected_checks,
&parser,
&dictionary,
reporter,
)
} else {
checks::check_path_parallel(
walk.build_parallel(),
selected_checks,
&parser,
&dictionary,
reporter,
)
2020-11-16 21:02:10 -05:00
}
2020-11-23 11:08:38 -05:00
.map_err(|e| {
e.io_error()
.map(|i| proc_exit::Code::from(i.kind()))
.unwrap_or_default()
.with_message(e)
})?;
2020-11-16 21:02:10 -05:00
if status_reporter.typos_found() {
2020-11-10 07:26:42 -05:00
typos_found = true;
}
2020-11-16 21:02:10 -05:00
if status_reporter.errors_found() {
2020-11-10 07:26:42 -05:00
errors_found = true;
2019-01-22 17:01:33 -05:00
}
2020-11-11 19:19:26 -05:00
if args.diff {
2020-11-16 21:02:10 -05:00
diff_reporter.show().with_code(proc_exit::Code::FAILURE)?;
2020-11-11 19:19:26 -05:00
} else if args.write_changes {
2020-11-16 21:02:10 -05:00
replace_reporter
.write()
.with_code(proc_exit::Code::FAILURE)?;
}
2019-01-22 17:01:33 -05:00
}
if errors_found {
2020-11-16 21:02:10 -05:00
proc_exit::Code::FAILURE.ok()
} else if typos_found {
2020-11-16 21:02:10 -05:00
// Can;'t use `Failure` since its so prevalent, it could be easy to get a
// `Failure` from something else and get it mixed up with typos.
//
// Can't use DataErr or anything else an std::io::ErrorKind might map to.
proc_exit::Code::UNKNOWN.ok()
} else {
2020-11-16 21:02:10 -05:00
proc_exit::Code::SUCCESS.ok()
}
2019-01-22 17:01:33 -05:00
}
2020-03-23 21:38:11 -04:00
fn init_logging(level: Option<log::Level>) {
if let Some(level) = level {
let mut builder = env_logger::Builder::new();
builder.filter(None, level.to_level_filter());
if level == log::LevelFilter::Trace {
builder.format_timestamp_secs();
} else {
builder.format(|f, record| {
writeln!(
f,
"[{}] {}",
record.level().to_string().to_lowercase(),
record.args()
)
});
}
builder.init();
}
2019-01-22 17:01:33 -05:00
}