diff --git a/src/commands.rs b/src/commands.rs index 3a3d79fc..6eeaa894 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1171,7 +1171,7 @@ mod tests { fn test_build_new() { let installable = Installable::Flake { reference: "github:user/repo".to_string(), - attribute: vec!["package".to_string()], + attribute: "package".to_string(), }; let build = Build::new(installable.clone()); @@ -1187,7 +1187,7 @@ mod tests { fn test_build_builder_pattern() { let installable = Installable::Flake { reference: "github:user/repo".to_string(), - attribute: vec!["package".to_string()], + attribute: "package".to_string(), }; let build = Build::new(installable) diff --git a/src/darwin.rs b/src/darwin.rs index 98fc4bbf..10602f88 100644 --- a/src/darwin.rs +++ b/src/darwin.rs @@ -87,10 +87,7 @@ impl DarwinRebuildArgs { Some(r) => r.to_owned(), None => return Err(eyre!("NH_DARWIN_FLAKE missing reference part")), }; - let attribute = elems - .next() - .map(crate::installable::parse_attribute) - .unwrap_or_default(); + let attribute = elems.next().unwrap_or_default().to_string(); Installable::Flake { reference, @@ -108,8 +105,7 @@ impl DarwinRebuildArgs { // If user explicitly selects some other attribute, don't push // darwinConfigurations if attribute.is_empty() { - attribute.push(String::from("darwinConfigurations")); - attribute.push(hostname.clone()); + *attribute = format!("darwinConfigurations.{hostname}"); } } @@ -206,10 +202,7 @@ impl DarwinReplArgs { Some(r) => r.to_owned(), None => return Err(eyre!("NH_DARWIN_FLAKE missing reference part")), }; - let attribute = elems - .next() - .map(crate::installable::parse_attribute) - .unwrap_or_default(); + let attribute = elems.next().unwrap_or_default().to_string(); Installable::Flake { reference, @@ -230,8 +223,7 @@ impl DarwinReplArgs { } = target_installable { if attribute.is_empty() { - attribute.push(String::from("darwinConfigurations")); - attribute.push(hostname); + *attribute = format!("darwinConfigurations.{hostname}"); } } diff --git a/src/home.rs b/src/home.rs index a9d75635..a18f253f 100644 --- a/src/home.rs +++ b/src/home.rs @@ -70,10 +70,7 @@ impl HomeRebuildArgs { Some(r) => r.to_owned(), None => return Err(eyre!("NH_HOME_FLAKE missing reference part")), }; - let attribute = elems - .next() - .map(crate::installable::parse_attribute) - .unwrap_or_default(); + let attribute = elems.next().unwrap_or_default().to_string(); Installable::Flake { reference, @@ -224,7 +221,7 @@ where return Ok(res); } - attribute.push(String::from("homeConfigurations")); + *attribute = String::from("homeConfigurations"); let flake_reference = reference.clone(); let mut found_config = false; @@ -255,7 +252,7 @@ where if check_res.map(|s| s.trim().to_owned()).as_deref() == Some("true") { debug!("Using explicit configuration from flag: {config_name:?}"); - attribute.push(config_name); + attribute.push_str(&config_name); if push_drv { attribute.extend(toplevel.clone()); } @@ -265,7 +262,7 @@ where // Explicit config provided but not found let tried_attr_path = { let mut attr_path = attribute.clone(); - attr_path.push(config_name); + attr_path.push_str(&config_name); Installable::Flake { reference: flake_reference, attribute: attr_path, @@ -310,7 +307,7 @@ where let current_try_attr = { let mut attr_path = attribute.clone(); - attr_path.push(attr_name.clone()); + attr_path.push_str(&attr_name); attr_path }; tried.push(current_try_attr.clone()); @@ -319,7 +316,7 @@ where check_res.map(|s| s.trim().to_owned()).as_deref() { debug!("Using automatically detected configuration: {}", attr_name); - attribute.push(attr_name); + attribute.push_str(&attr_name); if push_drv { attribute.extend(toplevel.clone()); } @@ -380,10 +377,7 @@ impl HomeReplArgs { Some(r) => r.to_owned(), None => return Err(eyre!("NH_HOME_FLAKE missing reference part")), }; - let attribute = elems - .next() - .map(crate::installable::parse_attribute) - .unwrap_or_default(); + let attribute = elems.next().unwrap_or_default().to_string(); Installable::Flake { reference, diff --git a/src/installable.rs b/src/installable.rs index cc63aeb1..00777903 100644 --- a/src/installable.rs +++ b/src/installable.rs @@ -10,18 +10,18 @@ use yansi::{Color, Paint}; pub enum Installable { Flake { reference: String, - attribute: Vec, + attribute: String, }, File { path: PathBuf, - attribute: Vec, + attribute: String, }, Store { path: PathBuf, }, Expression { expression: String, - attribute: Vec, + attribute: String, }, } @@ -51,14 +51,14 @@ impl FromArgMatches for Installable { if let Some(f) = file { return Ok(Self::File { path: PathBuf::from(f), - attribute: parse_attribute(installable.cloned().unwrap_or_default()), + attribute: installable.cloned().unwrap_or_default(), }); } if let Some(e) = expr { return Ok(Self::Expression { expression: e.to_string(), - attribute: parse_attribute(installable.cloned().unwrap_or_default()), + attribute: installable.cloned().unwrap_or_default(), }); } @@ -67,12 +67,10 @@ impl FromArgMatches for Installable { let reference = elems.next().unwrap().to_owned(); return Ok(Self::Flake { reference, - attribute: parse_attribute( - elems - .next() - .map(std::string::ToString::to_string) - .unwrap_or_default(), - ), + attribute: elems + .next() + .map(std::string::ToString::to_string) + .unwrap_or_default(), }); } @@ -82,12 +80,10 @@ impl FromArgMatches for Installable { let mut elems = f.splitn(2, '#'); Installable::Flake { reference: elems.next().unwrap().to_owned(), - attribute: parse_attribute( - elems - .next() - .map(std::string::ToString::to_string) - .unwrap_or_default(), - ), + attribute: elems + .next() + .map(std::string::ToString::to_string) + .unwrap_or_default(), } }) } @@ -124,7 +120,7 @@ impl FromArgMatches for Installable { if let Ok(f) = env::var("NH_FILE") { return Ok(Self::File { path: PathBuf::from(f), - attribute: parse_attribute(env::var("NH_ATTRP").unwrap_or_default()), + attribute: env::var("NH_ATTRP").unwrap_or_default(), }); } @@ -141,6 +137,41 @@ impl FromArgMatches for Installable { impl Args for Installable { fn augment_args(cmd: clap::Command) -> clap::Command { + let nh_flake = env::var("NH_FLAKE").unwrap_or_default(); + let nh_os_flake = env::var("NH_OS_FLAKE").unwrap_or_default(); + let nh_home_flake = env::var("NH_HOME_FLAKE").unwrap_or_default(); + let nh_darwin_flake = env::var("NH_DARWIN_FLAKE").unwrap_or_default(); + let nh_file = env::var("NH_FILE").unwrap_or_default(); + let nh_attr = env::var("NH_ATTR").unwrap_or_default(); + + let long_help = format!( + r"Which installable to use. +Nix accepts various kinds of installables: + +[FLAKEREF[#ATTRPATH]] + Flake reference with an optional attribute path. + [env: NH_FLAKE={nh_flake}] + [env: NH_OS_FLAKE={nh_os_flake}] + [env: NH_HOME_FLAKE={nh_home_flake}] + [env: NH_DARWIN_FLAKE={nh_darwin_flake}] + +{f_short}, {f_long} [ATTRPATH] + Path to file with an optional attribute path. + [env: NH_FILE={nh_file}] + [env: NH_ATTRP={nh_attr}] + +{e_short}, {e_long} [ATTRPATH] + Nix expression with an optional attribute path. + +[PATH] + Path or symlink to a /nix/store path +", + f_short = "-f".yellow(), + f_long = "--file".yellow(), + e_short = "-e".yellow(), + e_long = "--expr".yellow(), + ); + cmd .arg( Arg::new("file") @@ -153,48 +184,16 @@ impl Args for Installable { Arg::new("expr") .short('E') .long("expr") - .conflicts_with("file") + .action(ArgAction::Set) .hide(true) - .action(ArgAction::Set), + .conflicts_with("file"), ) .arg( Arg::new("installable") .action(ArgAction::Set) .value_name("INSTALLABLE") .help("Which installable to use") - .long_help(format!( - r"Which installable to use. -Nix accepts various kinds of installables: - -[FLAKEREF[#ATTRPATH]] - Flake reference with an optional attribute path. - [env: NH_FLAKE={}] - [env: NH_OS_FLAKE={}] - [env: NH_HOME_FLAKE={}] - [env: NH_DARWIN_FLAKE={}] - -{}, {} [ATTRPATH] - Path to file with an optional attribute path. - [env: NH_FILE={}] - [env: NH_ATTRP={}] - -{}, {} [ATTRPATH] - Nix expression with an optional attribute path. - -[PATH] - Path or symlink to a /nix/store path -", - env::var("NH_FLAKE").unwrap_or_default(), - env::var("NH_OS_FLAKE").unwrap_or_default(), - env::var("NH_HOME_FLAKE").unwrap_or_default(), - env::var("NH_DARWIN_FLAKE").unwrap_or_default(), - Paint::new("-f").fg(Color::Yellow), - Paint::new("--file").fg(Color::Yellow), - env::var("NH_FILE").unwrap_or_default(), - env::var("NH_ATTR").unwrap_or_default(), - Paint::new("-e").fg(Color::Yellow), - Paint::new("--expr").fg(Color::Yellow), - )), + .long_help(long_help), ) } @@ -203,82 +202,35 @@ Nix accepts various kinds of installables: } } -// TODO: should handle quoted attributes, like foo."bar.baz" -> ["foo", -// "bar.baz"] maybe use chumsky? -pub fn parse_attribute(s: S) -> Vec -where - S: AsRef, -{ - let s = s.as_ref(); - let mut res = Vec::new(); - - if s.is_empty() { - return res; - } - - let mut in_quote = false; - - let mut elem = String::new(); - for char in s.chars() { - match char { - '.' => { - if in_quote { - elem.push(char); - } else { - res.push(elem.clone()); - elem = String::new(); - } - }, - '"' => { - in_quote = !in_quote; - }, - _ => elem.push(char), - } - } - - res.push(elem); - - assert!(!in_quote, "Failed to parse attribute: {s}"); - - res -} - -#[test] -fn test_parse_attribute() { - assert_eq!(parse_attribute(r"foo.bar"), vec!["foo", "bar"]); - assert_eq!(parse_attribute(r#"foo."bar.baz""#), vec!["foo", "bar.baz"]); - let v: Vec = vec![]; - assert_eq!(parse_attribute(""), v); -} - impl Installable { #[must_use] pub fn to_args(&self) -> Vec { - let mut res = Vec::new(); match self { Self::Flake { reference, attribute, } => { - res.push(format!("{reference}#{}", join_attribute(attribute))); + vec![format!("{reference}#{attribute}")] }, Self::File { path, attribute } => { - res.push(String::from("--file")); - res.push(path.to_str().unwrap().to_string()); - res.push(join_attribute(attribute)); + vec![ + String::from("--file"), + path.to_str().unwrap().to_string(), + attribute.to_string(), + ] }, Self::Expression { expression, attribute, } => { - res.push(String::from("--expr")); - res.push(expression.to_string()); - res.push(join_attribute(attribute)); + vec![ + String::from("--expr"), + expression.to_string(), + attribute.to_string(), + ] }, - Self::Store { path } => res.push(path.to_str().unwrap().to_string()), + Self::Store { path } => vec![path.to_str().unwrap().to_string()], } - - res } } @@ -303,38 +255,6 @@ fn test_installable_to_args() { ); } -fn join_attribute(attribute: I) -> String -where - I: IntoIterator, - I::Item: AsRef, -{ - let mut res = String::new(); - let mut first = true; - for elem in attribute { - if first { - first = false; - } else { - res.push('.'); - } - - let s = elem.as_ref(); - - if s.contains('.') { - res.push_str(&format!(r#""{s}""#)); - } else { - res.push_str(s); - } - } - - res -} - -#[test] -fn test_join_attribute() { - assert_eq!(join_attribute(vec!["foo", "bar"]), "foo.bar"); - assert_eq!(join_attribute(vec!["foo", "bar.baz"]), r#"foo."bar.baz""#); -} - impl Installable { #[must_use] pub const fn str_kind(&self) -> &str { diff --git a/src/nixos.rs b/src/nixos.rs index 02bf7b83..f49285c4 100644 --- a/src/nixos.rs +++ b/src/nixos.rs @@ -719,10 +719,7 @@ fn get_nh_os_flake_env() -> Result> { .next() .ok_or_else(|| eyre!("NH_OS_FLAKE missing reference part"))? .to_owned(); - let attribute = elems - .next() - .map(crate::installable::parse_attribute) - .unwrap_or_default(); + let attribute = elems.next().unwrap_or_default().to_string(); Ok(Some(Installable::Flake { reference, @@ -841,9 +838,7 @@ pub fn toplevel_for>( let mut res = installable; let hostname_str = hostname.as_ref(); - let toplevel = ["config", "system", "build", final_attr] - .into_iter() - .map(String::from); + let toplevel = format!(".config.system.build.{final_attr}"); match res { Installable::Flake { @@ -852,17 +847,16 @@ pub fn toplevel_for>( // If user explicitly selects some other attribute, don't push // nixosConfigurations if attribute.is_empty() { - attribute.push(String::from("nixosConfigurations")); - attribute.push(hostname_str.to_owned()); + *attribute = format!("nixosConfigurations.{hostname_str}"); } - attribute.extend(toplevel); + attribute.push_str(&toplevel); }, Installable::File { ref mut attribute, .. } | Installable::Expression { ref mut attribute, .. - } => attribute.extend(toplevel), + } => attribute.push_str(&toplevel), Installable::Store { .. } => {}, } @@ -891,8 +885,7 @@ impl OsReplArgs { } = target_installable { if attribute.is_empty() { - attribute.push(String::from("nixosConfigurations")); - attribute.push(hostname); + *attribute = format!("nixosConfigurations.{hostname}"); } }