use std::io::Write;
use clap::Parser;
mod args;
mod report;
use proc_exit::prelude::*;
fn main() {
human_panic::setup_panic!();
let result = run();
proc_exit::exit(result);
}
fn run() -> proc_exit::ExitResult {
let args = match args::Args::try_parse() {
Ok(args) => args,
Err(e) if e.use_stderr() => {
let _ = e.print();
return proc_exit::sysexits::USAGE_ERR.ok();
}
Err(e) => {
let _ = e.print();
return proc_exit::Code::SUCCESS.ok();
}
};
args.color.apply();
init_logging(args.verbose.log_level());
let stdout_palette = if concolor::get(concolor::Stream::Stdout).ansi_color() {
report::Palette::colored()
} else {
report::Palette::plain()
};
let stderr_palette = if concolor::get(concolor::Stream::Stderr).ansi_color() {
report::Palette::colored()
} else {
report::Palette::plain()
};
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, stdout_palette, stderr_palette)
}
}
fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exit::ExitResult {
let global_cwd = std::env::current_dir().to_sysexits()?;
let path = &args.path[0];
let cwd = if path == std::path::Path::new("-") {
global_cwd
} else if path.is_file() {
let mut cwd = path
.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?;
cwd.pop();
cwd
} else {
path.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?
};
let cwd = cwd
.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?;
let storage = typos_cli::policy::ConfigStorage::new();
let mut engine = typos_cli::policy::ConfigEngine::new(&storage);
engine.set_isolated(args.isolated);
let mut overrides = typos_cli::config::Config::default();
if let Some(path) = args.custom_config.as_ref() {
let custom = typos_cli::config::Config::from_file(path)
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
overrides.update(&custom);
}
overrides.update(&args.config.to_config());
engine.set_overrides(overrides);
let config = engine
.load_config(&cwd)
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
let mut defaulted_config = typos_cli::config::Config::from_defaults();
defaulted_config.update(&config);
let output =
toml_edit::easy::to_string_pretty(&defaulted_config).with_code(proc_exit::Code::FAILURE)?;
if output_path == std::path::Path::new("-") {
std::io::stdout()
.write_all(output.as_bytes())
.to_sysexits()?;
} else {
std::fs::write(output_path, &output).to_sysexits()?;
}
Ok(())
}
fn run_type_list(args: &args::Args) -> proc_exit::ExitResult {
let global_cwd = std::env::current_dir().to_sysexits()?;
let path = &args.path[0];
let cwd = if path == std::path::Path::new("-") {
global_cwd
} else if path.is_file() {
let mut cwd = path
.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?;
cwd.pop();
cwd
} else {
path.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?
};
let cwd = cwd
.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?;
let storage = typos_cli::policy::ConfigStorage::new();
let mut engine = typos_cli::policy::ConfigEngine::new(&storage);
engine.set_isolated(args.isolated);
let mut overrides = typos_cli::config::Config::default();
if let Some(path) = args.custom_config.as_ref() {
let custom = typos_cli::config::Config::from_file(path)
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
overrides.update(&custom);
}
overrides.update(&args.config.to_config());
engine.set_overrides(overrides);
engine
.init_dir(&cwd)
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
let definitions = engine.file_types(&cwd);
let stdout = std::io::stdout();
let mut handle = stdout.lock();
for (name, globs) in definitions {
writeln!(handle, "{}: {}", name, itertools::join(globs, ", ")).to_sysexits()?;
}
Ok(())
}
fn run_checks(
args: &args::Args,
stdout_palette: report::Palette,
stderr_palette: report::Palette,
) -> proc_exit::ExitResult {
let global_cwd = std::env::current_dir().to_sysexits()?;
let storage = typos_cli::policy::ConfigStorage::new();
let mut engine = typos_cli::policy::ConfigEngine::new(&storage);
engine.set_isolated(args.isolated);
let mut overrides = typos_cli::config::Config::default();
if let Some(path) = args.custom_config.as_ref() {
let custom = typos_cli::config::Config::from_file(path)
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
overrides.update(&custom);
}
overrides.update(&args.config.to_config());
engine.set_overrides(overrides);
let mut typos_found = false;
let mut errors_found = false;
for path in args.path.iter() {
let cwd = if path == std::path::Path::new("-") {
global_cwd.clone()
} else if path.is_file() {
let mut cwd = path
.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?;
cwd.pop();
cwd
} else {
path.canonicalize()
.with_code(proc_exit::sysexits::USAGE_ERR)?
};
engine
.init_dir(&cwd)
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
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)
.skip_stdout(true)
.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());
if !walk_policy.extend_exclude.is_empty() {
let mut overrides = ignore::overrides::OverrideBuilder::new(".");
for pattern in walk_policy.extend_exclude.iter() {
overrides
.add(&format!("!{}", pattern))
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
}
let overrides = overrides
.build()
.with_code(proc_exit::sysexits::CONFIG_ERR)?;
walk.overrides(overrides);
}
let output_reporter = if args.diff {
Box::new(crate::report::PrintSilent)
} else {
args.format.reporter(stdout_palette, stderr_palette)
};
let status_reporter = report::MessageStatus::new(output_reporter.as_ref());
let reporter: &dyn typos_cli::report::Report = &status_reporter;
let selected_checks: &dyn typos_cli::file::FileChecker = if args.files {
&typos_cli::file::FoundFiles
} else if args.file_types {
&typos_cli::file::FileTypes
} else if args.identifiers {
&typos_cli::file::Identifiers
} else if args.words {
&typos_cli::file::Words
} else if args.write_changes {
&typos_cli::file::FixTypos
} else if args.diff {
&typos_cli::file::DiffTypos
} else {
&typos_cli::file::Typos
};
if single_threaded {
typos_cli::file::walk_path(walk.build(), selected_checks, &engine, reporter)
} else {
typos_cli::file::walk_path_parallel(
walk.build_parallel(),
selected_checks,
&engine,
reporter,
)
}
.map_err(|e| {
e.io_error()
.map(|i| {
let kind = i.kind();
proc_exit::sysexits::io_to_sysexists(kind)
.or_else(|| proc_exit::bash::io_to_signal(kind))
.unwrap_or(proc_exit::sysexits::IO_ERR)
})
.unwrap_or_default()
.with_message(e)
})?;
if status_reporter.typos_found() {
typos_found = true;
}
if status_reporter.errors_found() {
errors_found = true;
}
}
if errors_found {
proc_exit::Code::FAILURE.ok()
} else if typos_found {
proc_exit::Code::new(2).ok()
} else {
proc_exit::Code::SUCCESS.ok()
}
}
fn init_logging(level: Option<log::Level>) {
if let Some(level) = level {
let mut builder = env_logger::Builder::new();
let colored = concolor::get(concolor::Stream::Stderr).ansi_color();
builder.write_style(if colored {
env_logger::WriteStyle::Always
} else {
env_logger::WriteStyle::Never
});
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();
}
}