Skip to main content

kawauso_process/invocation/
variable_name.rs

1//! The name 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;
8
9/// The name of an environment variable of an external command
10///
11/// The value holds the name as the caller wrote it, and the crate checks
12/// nothing. The rules for a name differ between the platforms, and the
13/// operating system applies them when the command starts. A name that no
14/// program reads is therefore a valid name, and a name that the platform
15/// refuses, such as one that holds a NUL byte, fails when the command starts.
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.
19///
20/// # Examples
21///
22/// ```
23/// use kawauso_process::invocation::VariableName;
24///
25/// let name = VariableName::from("RUSTUP_TOOLCHAIN");
26///
27/// assert_eq!(name.get(), "RUSTUP_TOOLCHAIN");
28/// ```
29#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
30pub struct VariableName(OsString);
31
32impl VariableName {
33    /// Creates a name from anything that becomes an [`OsString`]
34    ///
35    /// # Examples
36    ///
37    /// ```
38    /// use kawauso_process::invocation::VariableName;
39    ///
40    /// let name = VariableName::new("CARGO_TERM_COLOR");
41    ///
42    /// assert_eq!(name.get(), "CARGO_TERM_COLOR");
43    /// ```
44    pub fn new(name: impl Into<OsString>) -> Self {
45        Self(name.into())
46    }
47
48    /// Returns the name as the caller wrote it
49    pub fn get(&self) -> &OsStr {
50        &self.0
51    }
52}
53
54/// Shows the name for a reader
55///
56/// A name that is not valid UTF-8 shows the replacement character `U+FFFD`
57/// for each byte that has no character. The text is for a person, and no
58/// caller reads it back into a name.
59impl Display for VariableName {
60    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
61        write!(formatter, "{}", self.0.to_string_lossy())
62    }
63}
64
65/// Creates a name from a borrowed string, such as a literal
66impl From<&str> for VariableName {
67    fn from(name: &str) -> Self {
68        Self::new(name)
69    }
70}
71
72/// Creates a name from a string that the application built at runtime
73impl From<String> for VariableName {
74    fn from(name: String) -> Self {
75        Self::new(name)
76    }
77}
78
79/// Creates a name from a borrowed string of the operating system
80impl From<&OsStr> for VariableName {
81    fn from(name: &OsStr) -> Self {
82        Self::new(name)
83    }
84}
85
86/// Creates a name from a string of the operating system
87impl From<OsString> for VariableName {
88    fn from(name: OsString) -> Self {
89        Self::new(name)
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    // An assertion in a test panics by design. A `# Panics` section on every
96    // test would repeat that and give the reader no information.
97    #![allow(clippy::missing_panics_doc)]
98
99    use super::*;
100
101    // An application that reads the name of a variable from the environment
102    // holds an `OsString`, and the name takes it without a conversion of its
103    // own.
104    #[test]
105    fn from_os_string_keeps_the_name() {
106        let name = VariableName::from(OsString::from("RUSTUP_TOOLCHAIN"));
107
108        assert_eq!(name.get(), "RUSTUP_TOOLCHAIN");
109    }
110
111    // The text of a name reaches a log line through `Display`, and a caller
112    // that shows one name gets it as the caller wrote it.
113    #[test]
114    fn to_string_returns_the_name() {
115        let name = VariableName::from("RUSTUP_TOOLCHAIN");
116
117        assert_eq!(name.to_string(), "RUSTUP_TOOLCHAIN");
118    }
119}