typos/src/main.rs

359 lines
10 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;
mod config;
2019-06-14 08:43:21 -04:00
arg_enum! {
2019-01-23 09:33:51 -05:00
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Format {
Silent,
Brief,
Long,
Json,
}
}
impl Format {
2019-07-03 21:22:36 -04:00
fn report(self) -> typos::report::Report {
2019-01-23 09:33:51 -05:00
match self {
2019-07-03 21:22:36 -04:00
Format::Silent => typos::report::print_silent,
Format::Brief => typos::report::print_brief,
Format::Long => typos::report::print_long,
Format::Json => typos::report::print_json,
2019-01-23 09:33:51 -05:00
}
}
}
impl Default for Format {
fn default() -> Self {
Format::Long
}
}
2019-01-22 17:01:33 -05:00
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")]
2019-08-07 12:09:01 -04:00
struct Args {
2019-01-23 09:34:05 -05:00
#[structopt(parse(from_os_str), default_value = ".")]
2019-01-22 17:01:33 -05:00
/// Paths to check
path: Vec<std::path::PathBuf>,
2019-08-07 10:55:17 -04:00
#[structopt(short = "c", long = "config")]
/// Custom config file
custom_config: Option<std::path::PathBuf>,
2019-08-07 10:55:17 -04:00
#[structopt(long = "isolated")]
/// Ignore implicit configuration files.
isolated: bool,
#[structopt(flatten)]
overrides: FileArgs,
2019-06-14 08:43:21 -04:00
#[structopt(
long = "format",
raw(possible_values = "&Format::variants()", case_insensitive = "true"),
default_value = "long"
)]
2019-01-23 09:33:51 -05:00
pub format: Format,
#[structopt(flatten)]
config: ConfigArgs,
2019-07-19 23:45:41 -04:00
#[structopt(flatten)]
verbose: clap_verbosity_flag::Verbosity,
2019-01-22 17:01:33 -05:00
}
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab-case")]
pub struct FileArgs {
#[structopt(long, raw(overrides_with = r#""check-filenames""#))]
/// Skip verifying spelling in file names.
no_check_filenames: bool,
#[structopt(
long,
raw(overrides_with = r#""no-check-filenames""#),
raw(hidden = "true")
)]
check_filenames: bool,
#[structopt(long, raw(overrides_with = r#""check-files""#))]
/// Skip verifying spelling in filess.
no_check_files: bool,
#[structopt(
long,
raw(overrides_with = r#""no-check-files""#),
raw(hidden = "true")
)]
check_files: bool,
#[structopt(long, raw(overrides_with = r#""hex""#))]
/// Don't try to detect that an identifier looks like hex
no_hex: bool,
#[structopt(long, raw(overrides_with = r#""no-hex""#), raw(hidden = "true"))]
hex: bool,
}
impl config::FileSource for FileArgs {
fn check_filename(&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"),
}
}
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""#))]
/// Search binary files.
binary: bool,
#[structopt(long, raw(overrides_with = r#""binary""#), raw(hidden = "true"))]
no_binary: bool,
#[structopt(long, raw(overrides_with = r#""no-hidden""#))]
/// Search hidden files and directories.
hidden: bool,
#[structopt(long, raw(overrides_with = r#""hidden""#), raw(hidden = "true"))]
no_hidden: bool,
#[structopt(long, raw(overrides_with = r#""ignore""#))]
/// Don't respect ignore files.
no_ignore: bool,
#[structopt(long, raw(overrides_with = r#""no-ignore""#), raw(hidden = "true"))]
ignore: bool,
#[structopt(long, raw(overrides_with = r#""ignore-dot""#))]
/// Don't respect .ignore files.
no_ignore_dot: bool,
#[structopt(long, raw(overrides_with = r#""no-ignore-dot""#), raw(hidden = "true"))]
ignore_dot: bool,
#[structopt(long, raw(overrides_with = r#""ignore-global""#))]
/// Don't respect global ignore files.
no_ignore_global: bool,
#[structopt(
long,
raw(overrides_with = r#""no-ignore-global""#),
raw(hidden = "true")
)]
ignore_global: bool,
#[structopt(long, raw(overrides_with = r#""ignore-parent""#))]
/// Don't respect ignore files in parent directories.
no_ignore_parent: bool,
#[structopt(
long,
raw(overrides_with = r#""no-ignore-parent""#),
raw(hidden = "true")
)]
ignore_parent: bool,
#[structopt(long, raw(overrides_with = r#""ignore-vcs""#))]
/// Don't respect ignore files in vcs directories.
no_ignore_vcs: bool,
#[structopt(long, raw(overrides_with = r#""no-ignore-vcs""#), raw(hidden = "true"))]
ignore_vcs: bool,
}
impl config::WalkSource for WalkArgs {
fn binary(&self) -> Option<bool> {
match (self.binary, self.no_binary) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn ignore_hidden(&self) -> Option<bool> {
match (self.hidden, self.no_hidden) {
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
2019-07-10 22:12:14 -04:00
fn ignore_files(&self) -> Option<bool> {
match (self.no_ignore, self.ignore) {
2019-07-10 22:12:14 -04:00
(true, false) => Some(false),
(false, true) => Some(true),
2019-07-11 23:56:27 -04:00
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn ignore_dot(&self) -> Option<bool> {
match (self.no_ignore_dot, self.ignore_dot) {
2019-07-12 23:36:32 -04:00
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn ignore_vcs(&self) -> Option<bool> {
match (self.no_ignore_vcs, self.ignore_vcs) {
2019-07-12 23:39:38 -04:00
(true, false) => Some(false),
(false, true) => Some(true),
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
fn ignore_global(&self) -> Option<bool> {
match (self.no_ignore_global, self.ignore_global) {
2019-07-12 23:43:18 -04:00
(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) {
2019-07-11 23:56:27 -04:00
(true, false) => Some(false),
(false, true) => Some(true),
2019-07-10 22:12:14 -04:00
(false, false) => None,
(_, _) => unreachable!("StructOpt should make this impossible"),
}
}
2019-01-22 17:01:33 -05:00
}
2019-07-19 23:45:41 -04:00
pub fn get_logging(level: log::Level) -> env_logger::Builder {
let mut builder = env_logger::Builder::new();
builder.filter(None, level.to_level_filter());
if level == log::LevelFilter::Trace {
builder.default_format_timestamp(false);
} else {
builder.format(|f, record| {
writeln!(
f,
"[{}] {}",
record.level().to_string().to_lowercase(),
record.args()
)
});
}
builder
}
fn run() -> Result<i32, failure::Error> {
2019-08-08 09:32:17 -04:00
let args = Args::from_args();
2019-01-22 17:01:33 -05:00
2019-08-07 12:09:01 -04:00
let mut builder = get_logging(args.verbose.log_level());
2019-07-19 23:45:41 -04:00
builder.init();
let mut config = config::Config::default();
2019-08-07 12:09:01 -04:00
if let Some(path) = args.custom_config.as_ref() {
let custom = config::Config::from_file(path)?;
config.update(&custom);
2019-01-22 17:01:33 -05:00
}
let config = config;
let mut typos_found = false;
2019-08-07 12:09:01 -04:00
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();
2019-08-07 12:09:01 -04:00
if !args.isolated {
let derived = config::Config::derive(cwd)?;
config.update(&derived);
}
2019-08-07 12:09:01 -04:00
config.update(&args.config);
config.default.update(&args.overrides);
let config = config;
let dictionary = typos::BuiltIn::new();
let parser = typos::tokens::ParserBuilder::new()
.ignore_hex(config.default.ignore_hex())
.include_digits(config.default.identifier_include_digits())
.include_chars(config.default.identifier_include_chars().to_owned())
.build();
let checks = typos::checks::CheckSettings::new()
.check_filenames(config.default.check_filename())
.check_files(config.default.check_file())
.binary(config.files.binary())
.build(&dictionary, &parser);
let mut walk = ignore::WalkBuilder::new(path);
walk.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());
for entry in walk.build() {
let entry = entry?;
if entry.file_type().map(|t| t.is_file()).unwrap_or(true) {
let explicit = entry.depth() == 0;
2019-08-07 12:09:01 -04:00
if checks.check_filename(entry.path(), args.format.report())? {
typos_found = true;
}
2019-08-07 12:09:01 -04:00
if checks.check_file(entry.path(), explicit, args.format.report())? {
typos_found = true;
}
}
2019-01-22 17:01:33 -05:00
}
}
if typos_found {
Ok(1)
} else {
Ok(0)
}
2019-01-22 17:01:33 -05:00
}
fn main() {
let code = run().unwrap();
std::process::exit(code);
2019-01-22 17:01:33 -05:00
}