diff --git a/Cargo.lock b/Cargo.lock index bda8720..610f14f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,33 +86,31 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.2.22" +version = "4.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "4ed45cc2c62a3eff523e718d8576ba762c83a3146151093283ac62ae11933a73" dependencies = [ "atty", "bitflags", "clap_lex", - "indexmap", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_complete" -version = "3.2.5" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +checksum = "11cba7abac9b56dfe2f035098cdb3a43946f276e6db83b72c4e692343f9aab9a" dependencies = [ "clap", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" dependencies = [ "os_str_bytes", ] @@ -355,12 +353,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "heck" version = "0.4.0" @@ -420,16 +412,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "indicatif" version = "0.17.1" @@ -849,12 +831,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "textwrap" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" - [[package]] name = "thiserror" version = "1.0.37" diff --git a/Cargo.toml b/Cargo.toml index 3adfcc8..561f2e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ edition = "2021" async-stream = "0.3.2" async-trait = "0.1.42" atty = "0.2" -clap = "3.0.0" -clap_complete = "3.0.0" +clap = "4.0.11" +clap_complete = "4.0.2" clicolors-control = "1" console = "0.15.0" const_format = "0.2.22" diff --git a/src/cli.rs b/src/cli.rs index 04b9156..0b8b9d9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,7 +2,10 @@ use std::env; -use clap::{value_parser, Arg, ArgMatches, ColorChoice, Command as ClapCommand}; +use clap::{ + builder::PossibleValue, value_parser, Arg, ArgMatches, ColorChoice, Command as ClapCommand, + ValueEnum, +}; use clap_complete::Shell; use const_format::concatcp; use env_logger::fmt::WriteStyle; @@ -93,7 +96,34 @@ macro_rules! handle_command { }; } -pub fn build_cli(include_internal: bool) -> ClapCommand<'static> { +/// When to display color. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ColorWhen { + /// Detect automatically. + Auto, + + /// Always display colors. + Always, + + /// Never display colors. + Never, +} + +impl ValueEnum for ColorWhen { + fn value_variants<'a>() -> &'a [Self] { + &[Self::Auto, Self::Always, Self::Never] + } + + fn to_possible_value<'a>(&self) -> Option { + match self { + Self::Auto => Some(PossibleValue::new("auto")), + Self::Always => Some(PossibleValue::new("always")), + Self::Never => Some(PossibleValue::new("never")), + } + } +} + +pub fn build_cli(include_internal: bool) -> ClapCommand { let version = env!("CARGO_PKG_VERSION"); let mut app = ClapCommand::new("Colmena") .bin_name("colmena") @@ -123,13 +153,13 @@ pub fn build_cli(include_internal: bool) -> ClapCommand<'static> { .help("Show debug information for Nix commands") .long_help("Passes --show-trace to Nix commands") .global(true) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("impure") .long("impure") .help("Allow impure expressions") .long_help("Passes --impure to Nix commands") .global(true) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("color") .long("color") .help("When to colorize the output") @@ -139,7 +169,7 @@ It's also possible to specify the preference using environment variables. See Result { if s == "auto" { - return Ok(()); + return Ok(EvaluationNodeLimit::Heuristic); } match s.parse::() { - Ok(_) => Ok(()), + Ok(0) => Ok(EvaluationNodeLimit::None), + Ok(n) => Ok(EvaluationNodeLimit::Manual(n)), Err(_) => Err(String::from("The value must be a valid number")), } - })) + }))) .arg(Arg::new("parallel") .short('p') .long("parallel") @@ -45,13 +49,8 @@ Set to 0 to disable the limit. Set to 0 to disable parallemism limit. "#) .default_value("10") - .takes_value(true) - .validator(|s| { - match s.parse::() { - Ok(_) => Ok(()), - Err(_) => Err(String::from("The value must be a valid number")), - } - })) + .num_args(1) + .value_parser(value_parser!(usize))) .arg(Arg::new("keep-result") .long("keep-result") .help("Create GC roots for built profiles") @@ -60,13 +59,13 @@ Set to 0 to disable parallemism limit. The built system profiles will be added as GC roots so that they will not be removed by the garbage collector. The links will be created under .gcroots in the directory the Hive configuration is located. "#) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("verbose") .short('v') .long("verbose") .help("Be verbose") .long_help("Deactivates the progress spinner and prints every line of output.") - .takes_value(false)) + .num_args(0)) .arg(Arg::new("no-keys") .long("no-keys") .help("Do not upload keys") @@ -75,23 +74,23 @@ The links will be created under .gcroots in the directory the Hive configuration By default, Colmena will upload keys set in `deployment.keys` before deploying the new profile on a node. To upload keys without building or deploying the rest of the configuration, use `colmena upload-keys`. "#) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("reboot") .long("reboot") .help("Reboot nodes after activation") .long_help("Reboots nodes after activation and waits for them to come back up.") - .takes_value(false)) + .num_args(0)) .arg(Arg::new("no-substitute") .long("no-substitute") .alias("no-substitutes") .help("Do not use substitutes") .long_help("Disables the use of substituters when copying closures to the remote host.") - .takes_value(false)) + .num_args(0)) .arg(Arg::new("no-gzip") .long("no-gzip") .help("Do not use gzip") .long_help("Disables the use of gzip when copying closures to the remote host.") - .takes_value(false)) + .num_args(0)) .arg(Arg::new("build-on-target") .long("build-on-target") .help("Build the system profiles on the target nodes") @@ -101,17 +100,17 @@ If enabled, the system profiles will be built on the target nodes themselves, no This overrides per-node perferences set in `deployment.buildOnTarget`. To temporarily disable remote build on all nodes, use `--no-build-on-target`. "#) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("no-build-on-target") .long("no-build-on-target") .hide(true) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("force-replace-unknown-profiles") .long("force-replace-unknown-profiles") .help("Ignore all targeted nodes deployment.replaceUnknownProfiles setting") .long_help(r#"If `deployment.replaceUnknownProfiles` is set for a target, using this switch will treat deployment.replaceUnknownProfiles as though it was set true and perform unknown profile replacement."#) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("evaluator") .long("evaluator") .help("The evaluator to use (experimental)") @@ -119,10 +118,10 @@ will treat deployment.replaceUnknownProfiles as though it was set true and perfo This is an experimental feature."#) .default_value("chunked") - .possible_values(Evaluator::possible_values())) + .value_parser(value_parser!(EvaluatorType))) } -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { let command = ClapCommand::new("apply") .about("Apply configurations on remote machines") .arg( @@ -141,10 +140,10 @@ Same as the targets for switch-to-configuration, with the following extra pseudo "#, ) .default_value("switch") - .default_value_if("reboot", None, Some("boot")) + .default_value_if("reboot", ArgPredicate::IsPresent, Some("boot")) .default_value("switch") .index(1) - .possible_values(&[ + .value_parser(PossibleValuesParser::new([ "build", "push", "switch", @@ -152,7 +151,7 @@ Same as the targets for switch-to-configuration, with the following extra pseudo "test", "dry-activate", "keys", - ]), + ])), ); let command = register_deploy_args(command); @@ -164,10 +163,15 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from); - let goal_arg = local_args.value_of("goal").unwrap(); + // FIXME: Just get_one:: + let goal_arg = local_args.get_one::("goal").unwrap(); let goal = Goal::from_str(goal_arg).unwrap(); - let filter = local_args.value_of("on").map(NodeFilter::new).transpose()?; + // FIXME: Just get_one:: + let filter = local_args + .get_one::("on") + .map(NodeFilter::new) + .transpose()?; if filter.is_none() && goal != Goal::Build { // User did not specify node, we should check meta and see rules @@ -184,7 +188,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( .await?; let n_targets = targets.len(); - let verbose = local_args.is_present("verbose") || goal == Goal::DryActivate; + let verbose = local_args.get_flag("verbose") || goal == Goal::DryActivate; let mut output = SimpleProgressOutput::new(verbose); let progress = output.get_sender(); @@ -193,22 +197,27 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( // FIXME: Configure limits let options = { let mut options = Options::default(); - options.set_substituters_push(!local_args.is_present("no-substitute")); - options.set_gzip(!local_args.is_present("no-gzip")); - options.set_upload_keys(!local_args.is_present("no-keys")); - options.set_reboot(local_args.is_present("reboot")); + options.set_substituters_push(!local_args.get_flag("no-substitute")); + options.set_gzip(!local_args.get_flag("no-gzip")); + options.set_upload_keys(!local_args.get_flag("no-keys")); + options.set_reboot(local_args.get_flag("reboot")); options.set_force_replace_unknown_profiles( - local_args.is_present("force-replace-unknown-profiles"), + local_args.get_flag("force-replace-unknown-profiles"), + ); + options.set_evaluator( + local_args + .get_one::("evaluator") + .unwrap() + .to_owned(), ); - options.set_evaluator(local_args.value_of_t("evaluator").unwrap()); - if local_args.is_present("keep-result") { + if local_args.get_flag("keep-result") { options.set_create_gc_roots(true); } - if local_args.is_present("no-build-on-target") { + if local_args.get_flag("no-build-on-target") { options.set_force_build_on_target(false); - } else if local_args.is_present("build-on-target") { + } else if local_args.get_flag("build-on-target") { options.set_force_build_on_target(true); } @@ -217,7 +226,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( deployment.set_options(options); - if local_args.is_present("no-keys") && goal == Goal::UploadKeys { + if local_args.get_flag("no-keys") && goal == Goal::UploadKeys { log::error!("--no-keys cannot be used when the goal is to upload keys"); quit::with_code(1); } @@ -225,11 +234,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( let parallelism_limit = { let mut limit = ParallelismLimit::default(); limit.set_apply_limit({ - let limit = local_args - .value_of("parallel") - .unwrap() - .parse::() - .unwrap(); + let limit = local_args.get_one::("parallel").unwrap().to_owned(); if limit == 0 { n_targets } else { @@ -239,17 +244,10 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( limit }; - let evaluation_node_limit = match local_args.value_of("eval-node-limit").unwrap() { - "auto" => EvaluationNodeLimit::Heuristic, - number => { - let number = number.parse::().unwrap(); - if number == 0 { - EvaluationNodeLimit::None - } else { - EvaluationNodeLimit::Manual(number) - } - } - }; + let evaluation_node_limit = local_args + .get_one::("eval-node-limit") + .unwrap() + .to_owned(); deployment.set_parallelism_limit(parallelism_limit); deployment.set_evaluation_node_limit(evaluation_node_limit); diff --git a/src/command/apply_local.rs b/src/command/apply_local.rs index 4171b0f..d4f5543 100644 --- a/src/command/apply_local.rs +++ b/src/command/apply_local.rs @@ -1,7 +1,7 @@ use regex::Regex; use std::collections::HashMap; -use clap::{Arg, ArgMatches, Command as ClapCommand}; +use clap::{builder::PossibleValuesParser, Arg, ArgMatches, Command as ClapCommand}; use tokio::fs; use crate::error::ColmenaError; @@ -10,7 +10,7 @@ use crate::nix::{host::Local as LocalHost, NodeName}; use crate::progress::SimpleProgressOutput; use crate::util; -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { ClapCommand::new("apply-local") .about("Apply configurations on the local machine") .arg(Arg::new("goal") @@ -18,16 +18,24 @@ pub fn subcommand() -> ClapCommand<'static> { .long_help("Same as the targets for switch-to-configuration.\n\"push\" is noop in apply-local.") .default_value("switch") .index(1) - .possible_values(&["push", "switch", "boot", "test", "dry-activate", "keys"])) + .value_parser(PossibleValuesParser::new([ + "push", + "switch", + "boot", + "test", + "dry-activate", + "keys", + ]))) .arg(Arg::new("sudo") .long("sudo") - .help("Attempt to escalate privileges if not run as root")) + .help("Attempt to escalate privileges if not run as root") + .num_args(0)) .arg(Arg::new("verbose") .short('v') .long("verbose") .help("Be verbose") .long_help("Deactivates the progress spinner and prints every line of output.") - .takes_value(false)) + .num_args(0)) .arg(Arg::new("no-keys") .long("no-keys") .help("Do not deploy keys") @@ -35,11 +43,11 @@ pub fn subcommand() -> ClapCommand<'static> { By default, Colmena will deploy keys set in `deployment.keys` before activating the profile on this host. "#) - .takes_value(false)) + .num_args(0)) .arg(Arg::new("node") .long("node") .help("Override the node name to use") - .takes_value(true)) + .num_args(1)) // Removed .arg(Arg::new("sudo-command") @@ -47,11 +55,11 @@ By default, Colmena will deploy keys set in `deployment.keys` before activating .value_name("COMMAND") .help("Removed: Configure deployment.privilegeEscalationCommand in node configuration") .hide(true) - .takes_value(true)) + .num_args(1)) } pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> { - if local_args.occurrences_of("sudo-command") > 0 { + if local_args.contains_id("sudo-command") { log::error!("--sudo-command has been removed. Please configure it in deployment.privilegeEscalationCommand in the node configuration."); quit::with_code(1); } @@ -68,8 +76,8 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( quit::with_code(5); } - let escalate_privileges = local_args.is_present("sudo"); - let verbose = local_args.is_present("verbose") || escalate_privileges; // cannot use spinners with interactive sudo + let escalate_privileges = local_args.get_flag("sudo"); + let verbose = local_args.get_flag("verbose") || escalate_privileges; // cannot use spinners with interactive sudo { let euid: u32 = unsafe { libc::geteuid() }; @@ -81,8 +89,8 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( let hive = util::hive_from_args(local_args).await.unwrap(); let hostname = { - let s = if local_args.is_present("node") { - local_args.value_of("node").unwrap().to_owned() + let s = if local_args.contains_id("node") { + local_args.get_one::("node").unwrap().to_owned() } else { hostname::get() .expect("Could not get hostname") @@ -92,7 +100,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( NodeName::new(s)? }; - let goal = Goal::from_str(local_args.value_of("goal").unwrap()).unwrap(); + let goal = Goal::from_str(local_args.get_one::("goal").unwrap()).unwrap(); let target = { if let Some(info) = hive.deployment_info_single(&hostname).await.unwrap() { @@ -131,7 +139,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( let options = { let mut options = Options::default(); - options.set_upload_keys(!local_args.is_present("no-keys")); + options.set_upload_keys(!local_args.get_flag("no-keys")); options }; diff --git a/src/command/build.rs b/src/command/build.rs index 140ea7a..286b42b 100644 --- a/src/command/build.rs +++ b/src/command/build.rs @@ -1,11 +1,11 @@ -use clap::{Arg, Command as ClapCommand}; +use clap::{builder::PossibleValuesParser, Arg, Command as ClapCommand}; use crate::util; use super::apply; pub use super::apply::run; -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { let command = ClapCommand::new("build") .about("Build configurations but not push to remote machines") .long_about( @@ -17,8 +17,8 @@ This subcommand behaves as if you invoked `apply` with the `build` goal."#, Arg::new("goal") .hide(true) .default_value("build") - .possible_values(&["build"]) - .takes_value(true), + .value_parser(PossibleValuesParser::new(["build"])) + .num_args(1), ); let command = apply::register_deploy_args(command); diff --git a/src/command/eval.rs b/src/command/eval.rs index 301583d..07efb9e 100644 --- a/src/command/eval.rs +++ b/src/command/eval.rs @@ -5,15 +5,15 @@ use clap::{Arg, ArgMatches, Command as ClapCommand}; use crate::error::ColmenaError; use crate::util; -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { subcommand_gen("eval") } -pub fn deprecated_alias() -> ClapCommand<'static> { +pub fn deprecated_alias() -> ClapCommand { subcommand_gen("introspect").hide(true) } -fn subcommand_gen(name: &str) -> ClapCommand<'static> { +fn subcommand_gen(name: &'static str) -> ClapCommand { ClapCommand::new(name) .about("Evaluate an expression using the complete configuration") .long_about(r#"Evaluate an expression using the complete configuration @@ -28,16 +28,16 @@ For example, to retrieve the configuration of one node, you may write something .index(1) .value_name("FILE") .help("The .nix file containing the expression") - .takes_value(true)) + .num_args(1)) .arg(Arg::new("expression") .short('E') .value_name("EXPRESSION") .help("The Nix expression") - .takes_value(true)) + .num_args(1)) .arg(Arg::new("instantiate") .long("instantiate") .help("Actually instantiate the expression") - .takes_value(false)) + .num_args(0)) } pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> { @@ -49,15 +49,21 @@ pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<() let hive = util::hive_from_args(local_args).await?; - if !(local_args.is_present("expression") ^ local_args.is_present("expression_file")) { + if !(local_args.contains_id("expression") ^ local_args.contains_id("expression_file")) { log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both."); quit::with_code(1); } - let expression = if local_args.is_present("expression") { - local_args.value_of("expression").unwrap().to_string() + let expression = if local_args.contains_id("expression") { + local_args + .get_one::("expression") + .unwrap() + .to_owned() } else { - let path: PathBuf = local_args.value_of("expression_file").unwrap().into(); + let path = local_args + .get_one::("expression_file") + .unwrap() + .to_owned(); format!( "import {}", path.canonicalize() @@ -67,7 +73,7 @@ pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<() ) }; - let instantiate = local_args.is_present("instantiate"); + let instantiate = local_args.get_flag("instantiate"); let result = hive.introspect(expression, instantiate).await?; if instantiate { diff --git a/src/command/exec.rs b/src/command/exec.rs index 40d6740..b723f6b 100644 --- a/src/command/exec.rs +++ b/src/command/exec.rs @@ -2,7 +2,7 @@ use std::env; use std::path::PathBuf; use std::sync::Arc; -use clap::{Arg, ArgMatches, Command as ClapCommand}; +use clap::{value_parser, Arg, ArgMatches, Command as ClapCommand}; use futures::future::join_all; use tokio::sync::Semaphore; @@ -12,10 +12,9 @@ use crate::nix::NodeFilter; use crate::progress::SimpleProgressOutput; use crate::util; -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { let command = ClapCommand::new("exec") .about("Run a command on remote machines") - .trailing_var_arg(true) .arg( Arg::new("parallel") .short('p') @@ -29,11 +28,8 @@ In `colmena exec`, the parallelism limit is disabled (0) by default. "#, ) .default_value("0") - .takes_value(true) - .validator(|s| match s.parse::() { - Ok(_) => Ok(()), - Err(_) => Err(String::from("The value must be a valid number")), - }), + .num_args(1) + .value_parser(value_parser!(usize)), ) .arg( Arg::new("verbose") @@ -41,15 +37,15 @@ In `colmena exec`, the parallelism limit is disabled (0) by default. .long("verbose") .help("Be verbose") .long_help("Deactivates the progress spinner and prints every line of output.") - .takes_value(false), + .num_args(0), ) .arg( Arg::new("command") .value_name("COMMAND") - .last(true) + .trailing_var_arg(true) .help("Command") .required(true) - .multiple_occurrences(true) + .num_args(1..) .long_help( r#"Command to run @@ -67,16 +63,16 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( let hive = util::hive_from_args(local_args).await?; let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from); - let filter = local_args.value_of("on").map(NodeFilter::new).transpose()?; + // FIXME: Just get_one:: + let filter = local_args + .get_one::("on") + .map(NodeFilter::new) + .transpose()?; let mut targets = hive.select_nodes(filter, ssh_config, true).await?; let parallel_sp = Arc::new({ - let limit = local_args - .value_of("parallel") - .unwrap() - .parse::() - .unwrap(); + let limit = local_args.get_one::("parallel").unwrap().to_owned(); if limit > 0 { Some(Semaphore::new(limit)) @@ -87,13 +83,13 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<( let command: Arc> = Arc::new( local_args - .values_of("command") + .get_many::("command") .unwrap() - .map(|s| s.to_string()) + .cloned() .collect(), ); - let mut output = SimpleProgressOutput::new(local_args.is_present("verbose")); + let mut output = SimpleProgressOutput::new(local_args.get_flag("verbose")); let (mut monitor, meta) = JobMonitor::new(output.get_sender()); diff --git a/src/command/nix_info.rs b/src/command/nix_info.rs index ae32a17..a4c0643 100644 --- a/src/command/nix_info.rs +++ b/src/command/nix_info.rs @@ -4,7 +4,7 @@ use crate::error::ColmenaError; use crate::nix::evaluator::nix_eval_jobs::get_pinned_nix_eval_jobs; use crate::nix::NixCheck; -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { ClapCommand::new("nix-info").about("Show information about the current Nix installation") } diff --git a/src/command/repl.rs b/src/command/repl.rs index a642367..4816747 100644 --- a/src/command/repl.rs +++ b/src/command/repl.rs @@ -8,7 +8,7 @@ use crate::error::ColmenaError; use crate::nix::info::NixCheck; use crate::util; -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { ClapCommand::new("repl") .about("Start an interactive REPL with the complete configuration") .long_about( diff --git a/src/command/test_progress.rs b/src/command/test_progress.rs index 36b57cb..df21980 100644 --- a/src/command/test_progress.rs +++ b/src/command/test_progress.rs @@ -14,7 +14,7 @@ macro_rules! node { }; } -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { ClapCommand::new("test-progress") .about("Run progress spinner tests") .hide(true) diff --git a/src/command/upload_keys.rs b/src/command/upload_keys.rs index b0ca167..28d9944 100644 --- a/src/command/upload_keys.rs +++ b/src/command/upload_keys.rs @@ -1,11 +1,11 @@ -use clap::{Arg, Command as ClapCommand}; +use clap::{builder::PossibleValuesParser, Arg, Command as ClapCommand}; use crate::util; use super::apply; pub use super::apply::run; -pub fn subcommand() -> ClapCommand<'static> { +pub fn subcommand() -> ClapCommand { let command = ClapCommand::new("upload-keys") .about("Upload keys to remote hosts") .long_about( @@ -17,8 +17,8 @@ This subcommand behaves as if you invoked `apply` with the pseudo `keys` goal."# Arg::new("goal") .hide(true) .default_value("keys") - .possible_values(&["keys"]) - .takes_value(true), + .value_parser(PossibleValuesParser::new(["keys"])) + .num_args(1), ); let command = apply::register_deploy_args(command); diff --git a/src/nix/deployment/mod.rs b/src/nix/deployment/mod.rs index c0363ca..2c1202b 100644 --- a/src/nix/deployment/mod.rs +++ b/src/nix/deployment/mod.rs @@ -8,7 +8,7 @@ pub mod limits; pub use limits::{EvaluationNodeLimit, ParallelismLimit}; pub mod options; -pub use options::{Evaluator, Options}; +pub use options::{EvaluatorType, Options}; use std::collections::HashMap; use std::mem; @@ -162,10 +162,10 @@ impl Deployment { let deployment = DeploymentHandle::new(self); let meta_future = meta.run(|meta| async move { match deployment.options.evaluator { - Evaluator::Chunked => { + EvaluatorType::Chunked => { deployment.execute_chunked(meta.clone(), targets).await?; } - Evaluator::Streaming => { + EvaluatorType::Streaming => { log::warn!("Streaming evaluation is an experimental feature"); deployment.execute_streaming(meta.clone(), targets).await?; } diff --git a/src/nix/deployment/options.rs b/src/nix/deployment/options.rs index 7a403d7..768bd22 100644 --- a/src/nix/deployment/options.rs +++ b/src/nix/deployment/options.rs @@ -1,8 +1,7 @@ //! Deployment options. -use std::str::FromStr; +use clap::{builder::PossibleValue, ValueEnum}; -use crate::error::{ColmenaError, ColmenaResult}; use crate::nix::CopyOptions; /// Options for a deployment. @@ -33,12 +32,12 @@ pub struct Options { pub(super) force_replace_unknown_profiles: bool, /// Which evaluator to use (experimental). - pub(super) evaluator: Evaluator, + pub(super) evaluator: EvaluatorType, } /// Which evaluator to use. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum Evaluator { +pub enum EvaluatorType { Chunked, Streaming, } @@ -72,7 +71,7 @@ impl Options { self.force_replace_unknown_profiles = enable; } - pub fn set_evaluator(&mut self, evaluator: Evaluator) { + pub fn set_evaluator(&mut self, evaluator: EvaluatorType) { self.evaluator = evaluator; } @@ -95,27 +94,20 @@ impl Default for Options { create_gc_roots: false, force_build_on_target: None, force_replace_unknown_profiles: false, - evaluator: Evaluator::Chunked, + evaluator: EvaluatorType::Chunked, } } } -impl FromStr for Evaluator { - type Err = ColmenaError; +impl ValueEnum for EvaluatorType { + fn value_variants<'a>() -> &'a [Self] { + &[Self::Chunked, Self::Streaming] + } - fn from_str(s: &str) -> ColmenaResult { - match s { - "chunked" => Ok(Self::Chunked), - "streaming" => Ok(Self::Streaming), - _ => Err(ColmenaError::Unknown { - message: "Valid values are 'chunked' and 'streaming'".to_string(), - }), + fn to_possible_value<'a>(&self) -> Option { + match self { + Self::Chunked => Some(PossibleValue::new("chunked")), + Self::Streaming => Some(PossibleValue::new("streaming")), } } } - -impl Evaluator { - pub fn possible_values() -> &'static [&'static str] { - &["chunked", "streaming"] - } -} diff --git a/src/troubleshooter.rs b/src/troubleshooter.rs index 9eb189c..ea462a5 100644 --- a/src/troubleshooter.rs +++ b/src/troubleshooter.rs @@ -5,7 +5,7 @@ use std::env; use std::future::Future; -use clap::ArgMatches; +use clap::{parser::ValueSource as ClapValueSource, ArgMatches}; use crate::error::ColmenaError; @@ -48,7 +48,7 @@ fn troubleshoot( // in their Colmena checkout, and encounter NoFlakesSupport // because Colmena always prefers flake.nix when it exists. - if global_args.occurrences_of("config") == 0 { + if let Some(ClapValueSource::DefaultValue) = global_args.value_source("config") { let cwd = env::current_dir()?; if cwd.join("flake.nix").is_file() && cwd.join("hive.nix").is_file() { eprintln!( diff --git a/src/util.rs b/src/util.rs index f2badc5..e72c0aa 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::process::Stdio; use async_trait::async_trait; -use clap::{Arg, ArgMatches, Command as ClapCommand}; +use clap::{parser::ValueSource as ClapValueSource, Arg, ArgMatches, Command as ClapCommand}; use futures::future::join3; use serde::de::DeserializeOwned; use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader}; @@ -193,8 +193,8 @@ impl CommandExt for CommandExecution { } pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult { - let path = match args.occurrences_of("config") { - 0 => { + let path = match args.value_source("config").unwrap() { + ClapValueSource::DefaultValue => { // traverse upwards until we find hive.nix let mut cur = std::env::current_dir()?; let mut file_path = None; @@ -231,9 +231,9 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult { file_path.unwrap() } - _ => { + ClapValueSource::CommandLine => { let path = args - .value_of("config") + .get_one::("config") .expect("The config arg should exist") .to_owned(); let fpath = PathBuf::from(&path); @@ -246,11 +246,11 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult { let hive_path = HivePath::Flake(flake); let mut hive = Hive::new(hive_path).await?; - if args.is_present("show-trace") { + if args.get_flag("show-trace") { hive.set_show_trace(true); } - if args.is_present("impure") { + if args.get_flag("impure") { hive.set_impure(true); } @@ -259,6 +259,7 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult { fpath } + x => panic!("Unexpected value source for config: {:?}", x), }; let hive_path = HivePath::from_path(path).await?; @@ -273,11 +274,11 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult { let mut hive = Hive::new(hive_path).await?; - if args.is_present("show-trace") { + if args.get_flag("show-trace") { hive.set_show_trace(true); } - if args.is_present("impure") { + if args.get_flag("impure") { hive.set_impure(true); } @@ -298,7 +299,7 @@ The list is comma-separated and globs are supported. To match tags, prepend the - edge-* - edge-*,core-* - @a-tag,@tags-can-have-*"#) - .takes_value(true)) + .num_args(1)) } pub async fn capture_stream(