nix_compat/derived_path/
output_spec.rs1use std::collections::BTreeSet;
2use std::fmt;
3use std::str::FromStr;
4
5use crate::derivation::{OutputName, ParseOutputNameError};
6
7#[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}