diff --git a/Cargo.lock b/Cargo.lock index e49ff1b..c1a8437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,13 +83,28 @@ checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a" dependencies = [ "atty", "bitflags", + "clap_derive", "clap_lex", "indexmap", + "once_cell", "strsim", "termcolor", "textwrap", ] +[[package]] +name = "clap_derive" +version = "3.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.2.3" @@ -168,6 +183,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -263,6 +284,48 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rayon" version = "1.5.3" @@ -371,6 +434,17 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -417,6 +491,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + [[package]] name = "unicode-segmentation" version = "1.9.0" @@ -429,6 +509,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "walkdir" version = "2.3.2" diff --git a/src/alejandra_cli/Cargo.toml b/src/alejandra_cli/Cargo.toml index 353fd16..061f29f 100644 --- a/src/alejandra_cli/Cargo.toml +++ b/src/alejandra_cli/Cargo.toml @@ -5,7 +5,7 @@ path = "src/main.rs" [dependencies] alejandra = { path = "../alejandra" } atty = "*" -clap = "*" +clap = { version = "3", features = ["derive"] } indoc = "*" rayon = "*" termion = "*" diff --git a/src/alejandra_cli/src/cli.rs b/src/alejandra_cli/src/cli.rs index 0003632..e508d19 100644 --- a/src/alejandra_cli/src/cli.rs +++ b/src/alejandra_cli/src/cli.rs @@ -1,68 +1,45 @@ +use clap::Parser; + #[derive(Clone)] pub(crate) struct FormattedPath { pub path: String, pub status: alejandra::format::Status, } -pub(crate) fn parse(args: Vec) -> clap::ArgMatches { - clap::Command::new("Alejandra") - .about("The Uncompromising Nix Code Formatter.") - .version(alejandra::version::VERSION) - .arg( - clap::Arg::new("include") - .help("Files or directories, or none to format stdin.") - .multiple_values(true), - ) - .arg( - clap::Arg::new("exclude") - .short('e') - .help("Files or directories to exclude from formatting.") - .long("exclude") - .multiple_occurrences(true) - .takes_value(true), - ) - .arg( - clap::Arg::new("check") - .help( - "Check if the input is already formatted and disable \ - writing in-place the modified content.", - ) - .long("--check") - .short('c'), - ) - .arg( - clap::Arg::new("threads") - .default_value("0") - .help( - "Number of formatting threads to spawn. Defaults to the \ - number of logical CPUs.", - ) - .long("--threads") - .short('t') - .takes_value(true), - ) - .arg( - clap::Arg::new("quiet") - .help("Hide the details, only show error messages.") - .long("--quiet") - .short('q'), - ) - .term_width(80) - .after_help( - #[cfg_attr(rustfmt, rustfmt_skip)] - indoc::indoc!( - " - The program will exit with status code: - 1, if any error occurs. - 2, if --check was used and any file was changed. - 0, otherwise. +const AFTER_HELP: &str = indoc::indoc! {" + The program will exit with status code: + 1, if any error occurs. + 2, if --check was used and any file was changed. + 0, otherwise. - Your star and feedback is very much appreciated! - https://github.com/kamadorueda/alejandra - " - ), - ) - .get_matches_from(args) + Your star and feedback is very much appreciated! + https://github.com/kamadorueda/alejandra + " +}; + +/// The Uncompromising Nix Code Formatter +#[derive(Debug, Parser)] +#[clap(version, after_help = AFTER_HELP, term_width = 80)] +struct Args { + /// Files or directories, or none to format stdin + #[clap(multiple_values = true)] + include: Vec, + + /// Files or directories to exclude from formatting + #[clap(long, short, multiple_occurrences = true)] + exclude: Vec, + + /// Check if the input is already formatted and disable writing in-place the modified content + #[clap(long, short)] + check: bool, + + /// Number of formatting threads to spawn. Defaults to the number of logical CPUs. + #[clap(long, short)] + threads: Option, + + /// Hide the details, only show error messages. + #[clap(long, short)] + quiet: bool, } pub(crate) fn stdin(quiet: bool) -> FormattedPath { @@ -99,8 +76,7 @@ pub(crate) fn simple( paths .par_iter() .map(|path| { - let status = - alejandra::format::in_fs(path.clone(), in_place); + let status = alejandra::format::in_fs(path.clone(), in_place); if let alejandra::format::Status::Changed(changed) = status { if changed && !quiet { @@ -171,8 +147,7 @@ pub(crate) fn tui( let sender_finished = sender; std::thread::spawn(move || { paths.into_par_iter().for_each_with(sender_paths, |sender, path| { - let status = - alejandra::format::in_fs(path.clone(), in_place); + let status = alejandra::format::in_fs(path.clone(), in_place); if let Err(error) = sender .send(Event::FormattedPath(FormattedPath { path, status })) @@ -197,9 +172,7 @@ pub(crate) fn tui( match event { Event::FormattedPath(formatted_path) => { match &formatted_path.status { - alejandra::format::Status::Changed( - changed, - ) => { + alejandra::format::Status::Changed(changed) => { if *changed { paths_changed += 1; } else { @@ -304,21 +277,21 @@ pub(crate) fn tui( .map(|formatted_path| { tui::text::Spans::from(vec![ match formatted_path.status { - alejandra::format::Status::Changed( - changed, - ) => tui::text::Span::styled( - if changed { - if in_place { - "CHANGED " + alejandra::format::Status::Changed(changed) => { + tui::text::Span::styled( + if changed { + if in_place { + "CHANGED " + } else { + "WOULD BE CHANGED " + } } else { - "WOULD BE CHANGED " - } - } else { - "OK " - }, - tui::style::Style::default() - .fg(tui::style::Color::Green), - ), + "OK " + }, + tui::style::Style::default() + .fg(tui::style::Color::Green), + ) + } alejandra::format::Status::Error(_) => { tui::text::Span::styled( "ERROR ", @@ -349,48 +322,39 @@ pub(crate) fn tui( } pub fn main() -> std::io::Result<()> { - let matches = crate::cli::parse(std::env::args().collect()); + let args = Args::parse(); - let in_place = !matches.is_present("check"); - let threads = matches.value_of("threads").unwrap(); - let threads: usize = threads.parse().unwrap(); - let quiet = matches.is_present("quiet"); + let in_place = !args.check; + let threads = args.threads.unwrap_or(0); rayon::ThreadPoolBuilder::new() .num_threads(threads) .build_global() .unwrap(); - let formatted_paths = match matches.values_of("include") { - Some(include) => { - let include = include.collect(); - let exclude = match matches.values_of("exclude") { - Some(exclude) => exclude.collect(), - None => vec![], - }; + let formatted_paths = match &args.include[..] { + &[] => { + vec![crate::cli::stdin(args.quiet)] + } + include => { + let paths = crate::find::nix_files(include, &args.exclude); - let paths: Vec = crate::find::nix_files(include, exclude); - - if !quiet + if !args.quiet && atty::is(atty::Stream::Stderr) && atty::is(atty::Stream::Stdin) && atty::is(atty::Stream::Stdout) { crate::cli::tui(paths, in_place)? } else { - crate::cli::simple(paths, in_place, quiet) + crate::cli::simple(paths, in_place, args.quiet) } } - None => vec![crate::cli::stdin(quiet)], }; let errors = formatted_paths .iter() .filter(|formatted_path| { - matches!( - formatted_path.status, - alejandra::format::Status::Error(_) - ) + matches!(formatted_path.status, alejandra::format::Status::Error(_)) }) .count(); @@ -420,7 +384,7 @@ pub fn main() -> std::io::Result<()> { .count(); if changed > 0 { - if !quiet { + if !args.quiet { eprintln!(); eprintln!( "Success! {} file{} {}", @@ -437,7 +401,7 @@ pub fn main() -> std::io::Result<()> { std::process::exit(if in_place { 0 } else { 2 }); } - if !quiet { + if !args.quiet { eprintln!(); eprintln!("Success! Your code complies the Alejandra style"); } diff --git a/src/alejandra_cli/src/find.rs b/src/alejandra_cli/src/find.rs index 80833ba..9356ca8 100644 --- a/src/alejandra_cli/src/find.rs +++ b/src/alejandra_cli/src/find.rs @@ -1,17 +1,18 @@ -pub(crate) fn nix_files(include: Vec<&str>, exclude: Vec<&str>) -> Vec { - let include: std::collections::HashSet = - include.iter().flat_map(nix_files_in_path).collect(); - let exclude: std::collections::HashSet = - exclude.iter().flat_map(nix_files_in_path).collect(); +use std::collections::HashSet; - let mut paths: Vec = - include.difference(&exclude).cloned().collect(); +pub(crate) fn nix_files(include: &[String], exclude: &[String]) -> Vec { + let include: HashSet<_> = + include.iter().flat_map(|s| nix_files_in_path(s)).collect(); + let exclude: HashSet<_> = + exclude.iter().flat_map(|s| nix_files_in_path(s)).collect(); - paths.sort(); + let mut paths: Vec<_> = include.difference(&exclude).cloned().collect(); + + paths.sort_unstable(); paths } -fn nix_files_in_path(path: &&str) -> std::collections::HashSet { +fn nix_files_in_path(path: &str) -> HashSet { walkdir::WalkDir::new(path) .into_iter() .filter_entry(is_nix_file_or_dir)