git_status_vars/
shell_writer.rs

1use std::cell::RefCell;
2use std::fmt::{self, Debug, Display};
3use std::io;
4use std::rc::Rc;
5
6/// A writer of var=value pairs.
7///
8/// See [`ShellWriter::new()`].
9#[derive(Clone)]
10pub struct ShellWriter<W: io::Write> {
11    /// The output stream to write to.
12    writer: Rc<RefCell<W>>,
13
14    /// The prefix to add before every key, e.g. `"group_"` or `""`.
15    prefix: String,
16}
17
18impl<W: io::Write> ShellWriter<W> {
19    /// Create a new `ShellWriter`. The `prefix` will be prepended anytime a
20    /// var is outputted, e.g. `prefixvar=value`.
21    ///
22    /// Generally, you will want to use this like:
23    ///
24    /// ```rust
25    /// use git_status_vars::ShellWriter;
26    /// ShellWriter::default().group("group").write_var("var", "value");
27    /// // or...
28    /// let mut buffer: Vec<u8> = vec![];
29    /// ShellWriter::new(&mut buffer, "").group("group").write_var("var", "value");
30    /// assert_eq!(buffer, b"group_var=value\n");
31    /// ```
32    #[must_use]
33    pub fn new<P: Display>(writer: W, prefix: P) -> Self {
34        Self {
35            writer: Rc::new(RefCell::new(writer)),
36            prefix: prefix.to_string(),
37        }
38    }
39
40    /// Write var=value with a value that was already quoted.
41    fn write_raw<K: Display, V: Display>(&self, var: K, raw: V) {
42        writeln!(self.writer.borrow_mut(), "{}{}={}", self.prefix, var, raw)
43            .unwrap();
44    }
45
46    /// Write var=value. `value` will be turned into a string, then quoted for
47    /// safe shell insertion. `var` will be assumed to be a valid name for a
48    /// shell variable.
49    pub fn write_var<K: Display, V: Display>(&self, var: K, value: V) {
50        self.write_raw(var, shell_quote(value));
51    }
52
53    /// Write var=value. `value` will be formatted into a string using
54    /// [`Debug`], then quoted for safe shell insertion. `var` will be assumed
55    /// to be a valid name for a shell variable.
56    pub fn write_var_debug<K: Display, V: Debug>(&self, var: K, value: V) {
57        self.write_raw(var, shell_quote_debug(value));
58    }
59
60    /// Write an object with the [`ShellVars`] trait. Mostly used with
61    /// [`Self::group()`] and [`Self::group_n()`].
62    pub fn write_vars<V: ShellVars>(&self, vars: &V) {
63        vars.write_to_shell(self);
64    }
65
66    /// Generate a sub-writer with this group name. Example output:
67    ///
68    /// ```sh
69    /// prefix_group_var=value
70    /// ```
71    #[must_use]
72    pub fn group<G: Display>(&self, group: G) -> Self {
73        Self {
74            writer: self.writer.clone(),
75            prefix: format!("{}{}_", self.prefix, group),
76        }
77    }
78
79    /// Generate a sub-writer with this group name and number. Example output:
80    ///
81    /// ```sh
82    /// prefix_groupN_var=value
83    /// ```
84    #[must_use]
85    pub fn group_n<P: Display, N: Display>(&self, prefix: P, n: N) -> Self {
86        self.group(format!("{prefix}{n}"))
87    }
88}
89
90impl ShellWriter<io::Stdout> {
91    /// Create a new `ShellWriter` for [`io::stdout()`] and a prefix.
92    #[must_use]
93    pub fn with_prefix<P: Display>(prefix: P) -> Self {
94        Self::new(io::stdout(), prefix)
95    }
96}
97
98impl Default for ShellWriter<io::Stdout> {
99    /// Create a new `ShellWriter` for [`io::stdout()`] and no prefix.
100    fn default() -> Self {
101        Self::new(io::stdout(), "")
102    }
103}
104
105impl<W: io::Write + Debug> Debug for ShellWriter<W> {
106    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
107        fmt.debug_struct("ShellWriter")
108            .field("writer", &self.writer)
109            .field("prefix", &self.prefix)
110            .finish()
111    }
112}
113
114/// An object that can be written as a group of shell variables.
115pub trait ShellVars {
116    /// Write `self` to the shell writer `out`.
117    fn write_to_shell<W: io::Write>(&self, out: &ShellWriter<W>);
118}
119
120/// Quote a value for safe shell insertion.
121///
122/// ```rust
123/// use git_status_vars::shell_quote;
124/// assert_eq!(shell_quote("a $b `c`\nd"), "'a $b `c`\nd'");
125/// ```
126pub fn shell_quote<V: Display>(value: V) -> String {
127    shell_words::quote(&value.to_string()).into()
128}
129
130/// Format a value with [`Debug`] and quote it for safe shell insertion.
131pub fn shell_quote_debug<V: Debug>(value: V) -> String {
132    shell_words::quote(&format!("{value:?}")).into()
133}