command_error/
utf8_program_and_args.rs

1use std::borrow::Cow;
2use std::fmt::Display;
3use std::process::Command;
4
5use crate::CommandDisplay;
6
7/// A program name and arguments stored as UTF-8 [`String`]s.
8///
9/// The program name and arguments are shell-quoted when [`Display`]ed, so that spaces are escaped
10/// and the displayed command can generally be pasted directly into a shell.
11///
12/// ```
13/// # use std::process::Command;
14/// # use command_error::Utf8ProgramAndArgs;
15/// # use command_error::CommandDisplay;
16/// let mut command = Command::new("echo");
17/// command.arg("puppy doggy");
18/// let displayed: Utf8ProgramAndArgs = (&command).into();
19/// assert_eq!(
20///     displayed.to_string(),
21///     "echo 'puppy doggy'"
22/// );
23///
24/// let mut command = Command::new("echo");
25/// command.arg("doggy")
26///     .current_dir("/puppy")
27///     .env("COLOR", "GOLDEN")
28///     .env_remove("STINKY");
29/// let displayed: Utf8ProgramAndArgs = (&command).into();
30/// assert_eq!(
31///     displayed.to_string(),
32///     "cd /puppy && COLOR=GOLDEN STINKY= echo doggy"
33/// );
34/// ```
35#[derive(Debug, Clone)]
36pub struct Utf8ProgramAndArgs {
37    current_dir: Option<String>,
38    envs: Vec<(String, Option<String>)>,
39    program: String,
40    args: Vec<String>,
41}
42
43impl Display for Utf8ProgramAndArgs {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        if let Some(current_dir) = &self.current_dir {
46            write!(f, "cd {} && ", shell_words::quote(current_dir))?;
47        }
48
49        for (key, value) in self.envs.iter() {
50            // TODO: Should I care about spaces in environment variable names???
51            write!(
52                f,
53                "{key}={} ",
54                value
55                    .as_deref()
56                    .map(|value| shell_words::quote(value))
57                    .unwrap_or_default()
58            )?;
59        }
60
61        write!(f, "{}", shell_words::quote(&self.program))?;
62        if !self.args.is_empty() {
63            write!(f, " {}", shell_words::join(&self.args))?;
64        }
65        Ok(())
66    }
67}
68
69impl CommandDisplay for Utf8ProgramAndArgs {
70    fn program(&self) -> std::borrow::Cow<'_, str> {
71        Cow::Borrowed(&self.program)
72    }
73
74    fn program_quoted(&self) -> Cow<'_, str> {
75        shell_words::quote(&self.program)
76    }
77
78    fn args(&self) -> Box<(dyn Iterator<Item = Cow<'_, str>> + '_)> {
79        Box::new(self.args.iter().map(|arg| Cow::Borrowed(arg.as_str())))
80    }
81}
82
83impl<'a> From<&'a Command> for Utf8ProgramAndArgs {
84    fn from(command: &'a Command) -> Self {
85        Utf8ProgramAndArgs {
86            current_dir: command
87                .get_current_dir()
88                .map(|path| path.to_string_lossy().into_owned()),
89            envs: command
90                .get_envs()
91                .map(|(key, value)| {
92                    (
93                        key.to_string_lossy().into_owned(),
94                        value.map(|value| value.to_string_lossy().into_owned()),
95                    )
96                })
97                .collect(),
98            program: command.get_program().to_string_lossy().into_owned(),
99            args: command
100                .get_args()
101                .map(|arg| arg.to_string_lossy().into_owned())
102                .collect(),
103        }
104    }
105}