summaryrefslogtreecommitdiff
path: root/src/package_installer.rs
blob: c5c64518f349090818a1e3ca91c005545819f923 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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<Path>) -> Result<Self> {
        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<String>,
    selectors: Vec<String>,
}

impl PackageRequest {
    pub fn new(
        workdir: impl AsRef<Path>,
        apt_config: AptConfig,
        packages: impl IntoIterator<Item = impl Into<String>>,
        selectors: impl IntoIterator<Item = impl Into<String>>,
    ) -> Result<Self> {
        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<Item = impl Into<String>>,
) -> Result<Vec<String>> {
    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<()>;
}

impl<T: PackageInstaller + ?Sized> PackageInstaller for &T {
    fn install(&self, request: &PackageRequest) -> Result<()> {
        (*self).install(request)
    }
}