Skip to main content

nix_compat/derived_path/
output_spec.rs

1use std::collections::BTreeSet;
2use std::fmt;
3use std::str::FromStr;
4
5use crate::derivation::{OutputName, ParseOutputNameError};
6
7// FUTUREWORK: reduce the amount of heap allocation needed for this small set of small strings.
8/// An output selection spec.
9///
10/// This is either all outputs (formatted as '*' when displaying or parsing) or
11/// a set of [`OutputName`] with the outputs that is to be selected.
12///
13/// This is used in [`super::DerivedPath`] to perform selection of the outputs to make
14/// sure, while building, are valid or substituted.
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(
17    feature = "serde",
18    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
19)]
20pub enum OutputSpec {
21    All,
22    Named(BTreeSet<OutputName>),
23}
24
25impl OutputSpec {
26    pub fn single(output_name: OutputName) -> Self {
27        let mut set = BTreeSet::new();
28        set.insert(output_name);
29        Self::Named(set)
30    }
31}
32
33impl fmt::Display for OutputSpec {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            OutputSpec::All => f.write_str("*")?,
37            OutputSpec::Named(outputs) => {
38                let mut it = outputs.iter();
39                if let Some(output) = it.next() {
40                    write!(f, "{output}")?;
41                    for output in it {
42                        write!(f, ",{output}")?;
43                    }
44                }
45            }
46        }
47        Ok(())
48    }
49}
50
51impl FromStr for OutputSpec {
52    type Err = ParseOutputSpecError;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        if s == "*" {
56            Ok(OutputSpec::All)
57        } else {
58            let mut outputs = BTreeSet::new();
59            for (idx, name) in s.split(",").enumerate() {
60                let output = name
61                    .parse()
62                    .map_err(|err| ParseOutputSpecError::OutputName { idx, err })?;
63                outputs.insert(output);
64            }
65            Ok(OutputSpec::Named(outputs))
66        }
67    }
68}
69
70impl From<OutputName> for OutputSpec {
71    fn from(output_name: OutputName) -> Self {
72        Self::single(output_name)
73    }
74}
75
76#[derive(thiserror::Error, Debug)]
77pub enum ParseOutputSpecError {
78    #[error("Invalid Output Name at index {idx}")]
79    OutputName {
80        idx: usize,
81        #[source]
82        err: ParseOutputNameError,
83    },
84}
85
86#[cfg(test)]
87mod tests {
88    use rstest::rstest;
89
90    use crate::{btree_set, derived_path::OutputSpec};
91
92    #[rstest]
93    #[case("*", OutputSpec::All)]
94    #[case("out", OutputSpec::Named(btree_set!("out")))]
95    #[case("bin,dev,out", OutputSpec::Named(btree_set!("bin", "dev", "out")))]
96    #[case::unordered("dev,bin,out", OutputSpec::Named(btree_set!("bin", "dev", "out")))]
97    #[case::duplicates("bin,dev,dev", OutputSpec::Named(btree_set!("bin", "dev")))]
98    fn parse(#[case] value: &str, #[case] expected: OutputSpec) {
99        let actual = value.parse::<OutputSpec>().unwrap();
100        assert_eq!(actual, expected);
101    }
102
103    #[rstest]
104    #[should_panic]
105    #[case("out,bin{n")]
106    #[should_panic]
107    #[case::too_long(
108        "test-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
109    )]
110    fn parse_fail(#[case] value: &str) {
111        value.parse::<OutputSpec>().unwrap();
112    }
113
114    #[rstest]
115    #[case(OutputSpec::All, "*")]
116    #[case(OutputSpec::Named(btree_set!("out")), "out")]
117    #[case(OutputSpec::Named(btree_set!("bin", "dev", "out")), "bin,dev,out")]
118    fn display(#[case] value: OutputSpec, #[case] expected: &str) {
119        assert_eq!(value.to_string(), expected);
120    }
121}