use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FlakeRef {
pub flake_dir: PathBuf,
pub attribute: String,
}
impl FlakeRef {
pub fn parse(input: &str) -> Result<Self, FlakeRefError> {
if let Some((path_part, attr)) = input.split_once('#') {
let dir = if path_part == "." || path_part.is_empty() {
std::env::current_dir()
.map_err(|e| FlakeRefError::InvalidPath(e.to_string()))?
} else {
let raw = path_part.strip_prefix("path:").unwrap_or(path_part);
PathBuf::from(raw)
};
Ok(Self {
flake_dir: dir,
attribute: attr.to_string(),
})
} else {
Err(FlakeRefError::MissingAttribute(input.to_string()))
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum FlakeRefError {
#[error("flake reference missing '#attribute': {0}")]
MissingAttribute(String),
#[error("invalid flake path: {0}")]
InvalidPath(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_dot_hash_cid() {
let fr = FlakeRef::parse(".#cid").unwrap();
assert_eq!(fr.flake_dir, std::env::current_dir().unwrap());
assert_eq!(fr.attribute, "cid");
}
#[test]
fn parse_absolute_path() {
let fr = FlakeRef::parse("/absolute/path#attr").unwrap();
assert_eq!(fr.flake_dir, PathBuf::from("/absolute/path"));
assert_eq!(fr.attribute, "attr");
}
#[test]
fn parse_relative_path() {
let fr = FlakeRef::parse("relative/path#attr").unwrap();
assert_eq!(fr.flake_dir, PathBuf::from("relative/path"));
assert_eq!(fr.attribute, "attr");
}
#[test]
fn parse_missing_hash_returns_error() {
let err = FlakeRef::parse("no-hash-here").unwrap_err();
assert!(matches!(err, FlakeRefError::MissingAttribute(_)));
assert!(err.to_string().contains("no-hash-here"));
}
#[test]
fn parse_empty_attribute_allowed() {
let fr = FlakeRef::parse(".#").unwrap();
assert_eq!(fr.attribute, "");
}
#[test]
fn parse_empty_path_uses_cwd() {
let fr = FlakeRef::parse("#attr").unwrap();
assert_eq!(fr.flake_dir, std::env::current_dir().unwrap());
assert_eq!(fr.attribute, "attr");
}
#[test]
fn parse_dotted_attribute() {
let fr = FlakeRef::parse("/nix#darwinConfigurations.cid.system").unwrap();
assert_eq!(fr.flake_dir, PathBuf::from("/nix"));
assert_eq!(fr.attribute, "darwinConfigurations.cid.system");
}
#[test]
fn parse_strips_path_scheme() {
let fr = FlakeRef::parse("path:/etc/nixos#nixosConfigurations.rio").unwrap();
assert_eq!(fr.flake_dir, PathBuf::from("/etc/nixos"));
assert_eq!(fr.attribute, "nixosConfigurations.rio");
}
#[test]
fn parse_strips_path_scheme_relative() {
let fr = FlakeRef::parse("path:./config#attr").unwrap();
assert_eq!(fr.flake_dir, PathBuf::from("./config"));
assert_eq!(fr.attribute, "attr");
}
#[test]
fn parse_multiple_hashes_splits_on_first() {
let fr = FlakeRef::parse("/path#attr#extra").unwrap();
assert_eq!(fr.flake_dir, PathBuf::from("/path"));
assert_eq!(fr.attribute, "attr#extra");
}
#[test]
fn error_display_missing_attribute() {
let err = FlakeRefError::MissingAttribute("foo".into());
assert!(err.to_string().contains("missing '#attribute'"));
assert!(err.to_string().contains("foo"));
}
#[test]
fn error_display_invalid_path() {
let err = FlakeRefError::InvalidPath("bad path".into());
assert!(err.to_string().contains("invalid flake path"));
assert!(err.to_string().contains("bad path"));
}
}