kawauso_process/invocation/argument.rs
1//! One argument 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/// One argument of an external command
12///
13/// An argument holds what the program receives, as the caller wrote it.
14/// Nothing splits it at a space, removes a quotation mark, or expands a
15/// character such as `*`, because no shell reads the command. An argument
16/// that holds a space is one argument, and not two.
17///
18/// The value is an [`OsString`], because a program takes the strings of the
19/// operating system and not the strings of Rust. An argument can therefore
20/// carry a path that is not valid UTF-8, which a file system allows.
21///
22/// # Examples
23///
24/// ```
25/// use kawauso_process::invocation::Argument;
26///
27/// let argument = Argument::from("--message=two words");
28///
29/// assert_eq!(argument.get(), "--message=two words");
30/// ```
31#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
32pub struct Argument(OsString);
33
34impl Argument {
35 /// Creates an argument from anything that becomes an [`OsString`]
36 ///
37 /// # Examples
38 ///
39 /// ```
40 /// use kawauso_process::invocation::Argument;
41 ///
42 /// let argument = Argument::new("--short");
43 ///
44 /// assert_eq!(argument.get(), "--short");
45 /// ```
46 pub fn new(argument: impl Into<OsString>) -> Self {
47 Self(argument.into())
48 }
49
50 /// Returns the argument as the caller wrote it
51 pub fn get(&self) -> &OsStr {
52 &self.0
53 }
54}
55
56/// Shows the argument for a reader
57///
58/// An argument that is not valid UTF-8 shows the replacement character
59/// `U+FFFD` for each byte that has no character. The text is for a person,
60/// and no caller reads it back into an argument.
61impl Display for Argument {
62 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
63 write!(formatter, "{}", self.0.to_string_lossy())
64 }
65}
66
67/// Creates an argument from a borrowed string, such as a literal
68impl From<&str> for Argument {
69 fn from(argument: &str) -> Self {
70 Self::new(argument)
71 }
72}
73
74/// Creates an argument from a string that the application built at runtime
75impl From<String> for Argument {
76 fn from(argument: String) -> Self {
77 Self::new(argument)
78 }
79}
80
81/// Creates an argument from a borrowed string of the operating system
82impl From<&OsStr> for Argument {
83 fn from(argument: &OsStr) -> Self {
84 Self::new(argument)
85 }
86}
87
88/// Creates an argument from a string of the operating system
89impl From<OsString> for Argument {
90 fn from(argument: OsString) -> Self {
91 Self::new(argument)
92 }
93}
94
95/// Creates an argument from a borrowed path, such as the root of a project
96impl From<&Path> for Argument {
97 fn from(argument: &Path) -> Self {
98 Self::new(argument)
99 }
100}
101
102/// Creates an argument from a path that the application built at runtime
103impl From<PathBuf> for Argument {
104 fn from(argument: PathBuf) -> Self {
105 Self::new(argument)
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 an argument from the environment holds an
118 // `OsString`, and the argument takes it without a conversion of its own.
119 #[test]
120 fn from_os_string_keeps_the_string() {
121 let argument = Argument::from(OsString::from("--short"));
122
123 assert_eq!(argument.get(), "--short");
124 }
125
126 // An application that names a file for a command holds a path, and the
127 // argument takes it without a conversion of its own.
128 #[test]
129 fn from_path_keeps_the_path() {
130 let argument = Argument::from(Path::new("src/main.rs"));
131
132 assert_eq!(argument.get(), "src/main.rs");
133 }
134
135 // The text of an argument reaches a log line through `Display`, and a
136 // caller that shows one argument gets it without quotation marks.
137 #[test]
138 fn to_string_returns_the_argument() {
139 let argument = Argument::from("two words");
140
141 assert_eq!(argument.to_string(), "two words");
142 }
143}