use std::path::{Path, PathBuf}; use anyhow::{bail, Result}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct AptConfig(PathBuf); impl AptConfig { pub fn new(path: impl AsRef) -> Result { let path = path.as_ref(); if path.as_os_str().is_empty() || path.is_dir() { bail!("APT configuration must be a file path: {}", path.display()); } Ok(Self(path.to_path_buf())) } pub fn as_path(&self) -> &Path { &self.0 } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct PackageRequest { workdir: PathBuf, apt_config: AptConfig, packages: Vec, selectors: Vec, } impl PackageRequest { pub fn new( workdir: impl AsRef, apt_config: AptConfig, packages: impl IntoIterator>, selectors: impl IntoIterator>, ) -> Result { let workdir = workdir.as_ref(); if workdir.as_os_str().is_empty() { bail!("Hasher workdir cannot be empty"); } let mut packages = normalize("package", packages)?; let mut selectors = normalize("package selector", selectors)?; packages.sort(); packages.dedup(); selectors.sort(); selectors.dedup(); Ok(Self { workdir: workdir.to_path_buf(), apt_config, packages, selectors, }) } pub fn workdir(&self) -> &Path { &self.workdir } pub fn apt_config(&self) -> &AptConfig { &self.apt_config } pub fn packages(&self) -> &[String] { &self.packages } pub fn selectors(&self) -> &[String] { &self.selectors } } fn normalize( kind: &str, values: impl IntoIterator>, ) -> Result> { values .into_iter() .map(Into::into) .map(|value: String| { if value.trim().is_empty() { bail!("{kind} cannot be empty"); } Ok(value) }) .collect() } pub trait PackageInstaller { fn install(&self, request: &PackageRequest) -> Result<()>; }