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}