mirror of
https://github.com/crate-ci/typos.git
synced 2024-11-25 10:31:02 -05:00
feat: add --interactive option to prompt for each change
This commit is contained in:
parent
85ec8ca2c8
commit
b435635f0e
5 changed files with 228 additions and 4 deletions
104
Cargo.lock
generated
104
Cargo.lock
generated
|
@ -179,6 +179,12 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.18"
|
version = "4.5.18"
|
||||||
|
@ -275,6 +281,19 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af"
|
checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "content_inspector"
|
name = "content_inspector"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
@ -338,6 +357,16 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctrlc"
|
||||||
|
version = "3.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
|
||||||
|
dependencies = [
|
||||||
|
"nix",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.3"
|
version = "0.20.3"
|
||||||
|
@ -406,6 +435,19 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dialoguer"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"shell-words",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dictgen"
|
name = "dictgen"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -477,6 +519,12 @@ version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.34"
|
version = "0.8.34"
|
||||||
|
@ -731,10 +779,16 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "lazy_static"
|
||||||
version = "0.2.149"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.161"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
|
@ -790,6 +844,18 @@ dependencies = [
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "normalize-line-endings"
|
name = "normalize-line-endings"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -1117,6 +1183,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shell-words"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -1348,9 +1420,12 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"clap-verbosity-flag",
|
"clap-verbosity-flag",
|
||||||
"colorchoice-clap",
|
"colorchoice-clap",
|
||||||
|
"console",
|
||||||
"content_inspector",
|
"content_inspector",
|
||||||
|
"ctrlc",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"derive_setters",
|
"derive_setters",
|
||||||
|
"dialoguer",
|
||||||
"difflib",
|
"difflib",
|
||||||
"divan",
|
"divan",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
|
@ -1378,7 +1453,7 @@ dependencies = [
|
||||||
"unic-emoji-char",
|
"unic-emoji-char",
|
||||||
"unicase",
|
"unicase",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width 0.2.0",
|
||||||
"varcon-core",
|
"varcon-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1473,6 +1548,12 @@ version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -1600,6 +1681,15 @@ dependencies = [
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
@ -1758,3 +1848,9 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||||
|
|
|
@ -77,6 +77,9 @@ colorchoice-clap = "1.0.3"
|
||||||
serde_regex = "1.1.0"
|
serde_regex = "1.1.0"
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
encoding_rs = "0.8.34"
|
encoding_rs = "0.8.34"
|
||||||
|
dialoguer = "0.11.0"
|
||||||
|
console = "0.15.8"
|
||||||
|
ctrlc = "3.4.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_fs = "1.1"
|
assert_fs = "1.1"
|
||||||
|
|
|
@ -67,6 +67,10 @@ pub(crate) struct Args {
|
||||||
#[arg(long, short = 'w', group = "mode", help_heading = "Mode")]
|
#[arg(long, short = 'w', group = "mode", help_heading = "Mode")]
|
||||||
pub(crate) write_changes: bool,
|
pub(crate) write_changes: bool,
|
||||||
|
|
||||||
|
/// Prompt for each suggested correction whether to write the fix
|
||||||
|
#[arg(long, short = 'i', group = "mode", help_heading = "Mode")]
|
||||||
|
pub(crate) interactive: bool,
|
||||||
|
|
||||||
/// Debug: Print each file that would be spellchecked.
|
/// Debug: Print each file that would be spellchecked.
|
||||||
#[arg(long, group = "mode", help_heading = "Mode")]
|
#[arg(long, group = "mode", help_heading = "Mode")]
|
||||||
pub(crate) files: bool,
|
pub(crate) files: bool,
|
||||||
|
|
|
@ -32,6 +32,14 @@ fn run() -> proc_exit::ExitResult {
|
||||||
|
|
||||||
init_logging(args.verbose.log_level());
|
init_logging(args.verbose.log_level());
|
||||||
|
|
||||||
|
// HACK: Ensure the terminal gets reset to a good state if the user hits ctrl-c during a prompt
|
||||||
|
// https://github.com/console-rs/dialoguer/issues/294
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
let _ = console::Term::stdout().show_cursor();
|
||||||
|
std::process::exit(130);
|
||||||
|
})
|
||||||
|
.expect("Failed to set handler for Ctrl+C needed to restore terminal defaults after killing the process");
|
||||||
|
|
||||||
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 {
|
} else if args.type_list {
|
||||||
|
@ -289,6 +297,8 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult {
|
||||||
&typos_cli::file::Identifiers
|
&typos_cli::file::Identifiers
|
||||||
} else if args.words {
|
} else if args.words {
|
||||||
&typos_cli::file::Words
|
&typos_cli::file::Words
|
||||||
|
} else if args.interactive {
|
||||||
|
&typos_cli::file::Interactive
|
||||||
} else if args.write_changes {
|
} else if args.write_changes {
|
||||||
&typos_cli::file::FixTypos
|
&typos_cli::file::FixTypos
|
||||||
} else if args.diff {
|
} else if args.diff {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use bstr::ByteSlice;
|
use bstr::ByteSlice;
|
||||||
|
use dialoguer::{Confirm, Select};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
@ -137,6 +138,87 @@ impl FileChecker for FixTypos {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Interactive;
|
||||||
|
|
||||||
|
impl FileChecker for Interactive {
|
||||||
|
fn check_file(
|
||||||
|
&self,
|
||||||
|
path: &std::path::Path,
|
||||||
|
explicit: bool,
|
||||||
|
policy: &crate::policy::Policy<'_, '_, '_>,
|
||||||
|
reporter: &dyn report::Report,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
if policy.check_files {
|
||||||
|
let (buffer, content_type) = read_file(path, reporter)?;
|
||||||
|
let bc = buffer.clone();
|
||||||
|
if !explicit && !policy.binary && content_type.is_binary() {
|
||||||
|
let msg = report::BinaryFile { path };
|
||||||
|
reporter.report(msg.into())?;
|
||||||
|
} else {
|
||||||
|
let mut fixes = Vec::new();
|
||||||
|
|
||||||
|
let mut accum_line_num = AccumulateLineNum::new();
|
||||||
|
for typo in check_bytes(&bc, policy) {
|
||||||
|
let line_num = accum_line_num.line_num(&buffer, typo.byte_offset);
|
||||||
|
let (line, line_offset) = extract_line(&buffer, typo.byte_offset);
|
||||||
|
let msg = report::Typo {
|
||||||
|
context: Some(report::FileContext { path, line_num }.into()),
|
||||||
|
buffer: std::borrow::Cow::Borrowed(line),
|
||||||
|
byte_offset: line_offset,
|
||||||
|
typo: typo.typo.as_ref(),
|
||||||
|
corrections: typo.corrections.clone(),
|
||||||
|
};
|
||||||
|
// HACK: we use the reporter to display the possible corrections to the user
|
||||||
|
// this will be looking very ugly with the format set to anything else than json
|
||||||
|
// technically we should only report typos when not correcting
|
||||||
|
reporter.report(msg.into())?;
|
||||||
|
|
||||||
|
if let Some(correction_index) = select_fix(&typo) {
|
||||||
|
fixes.push((typo, correction_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fixes.is_empty() || path == std::path::Path::new("-") {
|
||||||
|
let buffer = fix_buffer(buffer, fixes.into_iter());
|
||||||
|
write_file(path, content_type, buffer, reporter)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if policy.check_filenames {
|
||||||
|
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
|
||||||
|
let mut fixes = Vec::new();
|
||||||
|
|
||||||
|
for typo in check_str(file_name, policy) {
|
||||||
|
let msg = report::Typo {
|
||||||
|
context: Some(report::PathContext { path }.into()),
|
||||||
|
buffer: std::borrow::Cow::Borrowed(file_name.as_bytes()),
|
||||||
|
byte_offset: typo.byte_offset,
|
||||||
|
typo: typo.typo.as_ref(),
|
||||||
|
corrections: typo.corrections.clone(),
|
||||||
|
};
|
||||||
|
// HACK: we use the reporter to display the possible corrections to the user
|
||||||
|
// this will be looking very ugly with the format set to anything else than json
|
||||||
|
// technically we should only report typos when not correcting
|
||||||
|
reporter.report(msg.into())?;
|
||||||
|
|
||||||
|
if let Some(correction_index) = select_fix(&typo) {
|
||||||
|
fixes.push((typo, correction_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fixes.is_empty() {
|
||||||
|
let new_path = fix_file_name(path, file_name, fixes.into_iter())?;
|
||||||
|
std::fs::rename(path, new_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct DiffTypos;
|
pub struct DiffTypos;
|
||||||
|
|
||||||
|
@ -675,6 +757,35 @@ fn fix_file_name<'a>(
|
||||||
Ok(new_path)
|
Ok(new_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_fix(typo: &typos::Typo<'_>) -> Option<usize> {
|
||||||
|
let corrections = match &typo.corrections {
|
||||||
|
typos::Status::Corrections(c) => c,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if corrections.len() == 1 {
|
||||||
|
Confirm::new()
|
||||||
|
.with_prompt("Do you want to apply the fix suggested above?")
|
||||||
|
.default(true)
|
||||||
|
.show_default(true)
|
||||||
|
.interact()
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(0)
|
||||||
|
} else {
|
||||||
|
let mut items = corrections.clone();
|
||||||
|
|
||||||
|
items.insert(0, std::borrow::Cow::from("None (skip)"));
|
||||||
|
let selection = Select::new()
|
||||||
|
.with_prompt("Please choose one of the following suggestions")
|
||||||
|
.items(&items)
|
||||||
|
.default(0)
|
||||||
|
.interact()
|
||||||
|
.ok()?;
|
||||||
|
selection.checked_sub(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn walk_path(
|
pub fn walk_path(
|
||||||
walk: ignore::Walk,
|
walk: ignore::Walk,
|
||||||
checks: &dyn FileChecker,
|
checks: &dyn FileChecker,
|
||||||
|
|
Loading…
Reference in a new issue