Skip to main content

kawauso_process/invocation/
variable_value.rs

1//! The value of an environment variable of an external command
2
3use std::ffi::OsStr;
4use std::ffi::OsString;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::fmt::Result;
8use std::path::Path;
9use std::path::PathBuf;
10
11/// The value of an environment variable of an external command
12///
13/// The value holds what the program reads, as the caller wrote it. Nothing
14/// splits it at a space or expands a character such as `$`, because no shell
15/// reads the command. A value that holds a space is one value.
16///
17/// The value is an [`OsString`], because the environment of a program holds
18/// the strings of the operating system and not the strings of Rust. A value is
19/// often a path, such as the directory that a build tool writes to, and a
20/// path becomes a value without a conversion of its own.
21///
22/// # Examples
23///
24/// ```
25/// use kawauso_process::invocation::VariableValue;
26///
27/// let value = VariableValue::from("-D warnings");
28///
29/// assert_eq!(value.get(), "-D warnings");
30/// ```
31#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
32pub struct VariableValue(OsString);
33
34impl VariableValue {
35    /// Creates a value from anything that becomes an [`OsString`]
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// use kawauso_process::invocation::VariableValue;
41    ///
42    /// let value = VariableValue::new("always");
43    ///
44    /// assert_eq!(value.get(), "always");
45    /// ```
46    pub fn new(value: impl Into<OsString>) -> Self {
47        Self(value.into())
48    }
49
50    /// Returns the value as the caller wrote it
51    pub fn get(&self) -> &OsStr {
52        &self.0
53    }
54}
55
56/// Shows the value for a reader
57///
58/// A value that is not valid UTF-8 shows the replacement character `U+FFFD`
59/// for each byte that has no character. The text is for a person, and no
60/// caller reads it back into a value.
61impl Display for VariableValue {
62    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
63        write!(formatter, "{}", self.0.to_string_lossy())
64    }
65}
66
67/// Creates a value from a borrowed string, such as a literal
68impl From<&str> for VariableValue {
69    fn from(value: &str) -> Self {
70        Self::new(value)
71    }
72}
73
74/// Creates a value from a string that the application built at runtime
75impl From<String> for VariableValue {
76    fn from(value: String) -> Self {
77        Self::new(value)
78    }
79}
80
81/// Creates a value from a borrowed string of the operating system
82impl From<&OsStr> for VariableValue {
83    fn from(value: &OsStr) -> Self {
84        Self::new(value)
85    }
86}
87
88/// Creates a value from a string of the operating system
89impl From<OsString> for VariableValue {
90    fn from(value: OsString) -> Self {
91        Self::new(value)
92    }
93}
94
95/// Creates a value from a borrowed path, such as the root of a project
96impl From<&Path> for VariableValue {
97    fn from(value: &Path) -> Self {
98        Self::new(value)
99    }
100}
101
102/// Creates a value from a path that the application built at runtime
103impl From<PathBuf> for VariableValue {
104    fn from(value: PathBuf) -> Self {
105        Self::new(value)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    // An assertion in a test panics by design. A `# Panics` section on every
112    // test would repeat that and give the reader no information.
113    #![allow(clippy::missing_panics_doc)]
114
115    use super::*;
116
117    // An application that reads a value from the environment holds an
118    // `OsString`, and the value takes it without a conversion of its own.
119    #[test]
120    fn from_os_string_keeps_the_value() {
121        let value = VariableValue::from(OsString::from("always"));
122
123        assert_eq!(value.get(), "always");
124    }
125
126    // An application that names a directory for a command holds a path, and
127    // the value takes it without a conversion of its own.
128    #[test]
129    fn from_path_keeps_the_path() {
130        let value = VariableValue::from(Path::new("target/coverage"));
131
132        assert_eq!(value.get(), "target/coverage");
133    }
134
135    // The text of a value reaches a log line through `Display`, and a caller
136    // that shows one value gets it without quotation marks.
137    #[test]
138    fn to_string_returns_the_value() {
139        let value = VariableValue::from("-D warnings");
140
141        assert_eq!(value.to_string(), "-D warnings");
142    }
143}