1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::cell::RefCell;
use std::fmt::{self, Debug, Display};
use std::io;
use std::rc::Rc;

/// A writer of var=value pairs.
///
/// See [`ShellWriter::new()`].
#[derive(Clone)]
pub struct ShellWriter<W: io::Write> {
    /// The output stream to write to.
    writer: Rc<RefCell<W>>,

    /// The prefix to add before every key, e.g. `"group_"` or `""`.
    prefix: String,
}

impl<W: io::Write> ShellWriter<W> {
    /// Create a new `ShellWriter`. The `prefix` will be prepended anytime a
    /// var is outputted, e.g. `prefixvar=value`.
    ///
    /// Generally, you will want to use this like:
    ///
    /// ```rust
    /// use git_status_vars::ShellWriter;
    /// ShellWriter::default().group("group").write_var("var", "value");
    /// // or...
    /// let mut buffer: Vec<u8> = vec![];
    /// ShellWriter::new(&mut buffer, "").group("group").write_var("var", "value");
    /// assert_eq!(buffer, b"group_var=value\n");
    /// ```
    #[must_use]
    pub fn new<P: Display>(writer: W, prefix: P) -> Self {
        Self {
            writer: Rc::new(RefCell::new(writer)),
            prefix: prefix.to_string(),
        }
    }

    /// Write var=value with a value that was already quoted.
    fn write_raw<K: Display, V: Display>(&self, var: K, raw: V) {
        writeln!(self.writer.borrow_mut(), "{}{}={}", self.prefix, var, raw)
            .unwrap();
    }

    /// Write var=value. `value` will be turned into a string, then quoted for
    /// safe shell insertion. `var` will be assumed to be a valid name for a
    /// shell variable.
    pub fn write_var<K: Display, V: Display>(&self, var: K, value: V) {
        self.write_raw(var, shell_quote(value));
    }

    /// Write var=value. `value` will be formatted into a string using
    /// [`Debug`], then quoted for safe shell insertion. `var` will be assumed
    /// to be a valid name for a shell variable.
    pub fn write_var_debug<K: Display, V: Debug>(&self, var: K, value: V) {
        self.write_raw(var, shell_quote_debug(value));
    }

    /// Write an object with the [`ShellVars`] trait. Mostly used with
    /// [`Self::group()`] and [`Self::group_n()`].
    pub fn write_vars<V: ShellVars>(&self, vars: &V) {
        vars.write_to_shell(self);
    }

    /// Generate a sub-writer with this group name. Example output:
    ///
    /// ```sh
    /// prefix_group_var=value
    /// ```
    #[must_use]
    pub fn group<G: Display>(&self, group: G) -> Self {
        Self {
            writer: self.writer.clone(),
            prefix: format!("{}{}_", self.prefix, group),
        }
    }

    /// Generate a sub-writer with this group name and number. Example output:
    ///
    /// ```sh
    /// prefix_groupN_var=value
    /// ```
    #[must_use]
    pub fn group_n<P: Display, N: Display>(&self, prefix: P, n: N) -> Self {
        self.group(format!("{prefix}{n}"))
    }
}

impl ShellWriter<io::Stdout> {
    /// Create a new `ShellWriter` for [`io::stdout()`] and a prefix.
    #[must_use]
    pub fn with_prefix<P: Display>(prefix: P) -> Self {
        Self::new(io::stdout(), prefix)
    }
}

impl Default for ShellWriter<io::Stdout> {
    /// Create a new `ShellWriter` for [`io::stdout()`] and no prefix.
    fn default() -> Self {
        Self::new(io::stdout(), "")
    }
}

impl<W: io::Write + Debug> Debug for ShellWriter<W> {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.debug_struct("ShellWriter")
            .field("writer", &self.writer)
            .field("prefix", &self.prefix)
            .finish()
    }
}

/// An object that can be written as a group of shell variables.
pub trait ShellVars {
    /// Write `self` to the shell writer `out`.
    fn write_to_shell<W: io::Write>(&self, out: &ShellWriter<W>);
}

/// Quote a value for safe shell insertion.
///
/// ```rust
/// use git_status_vars::shell_quote;
/// assert_eq!(shell_quote("a $b `c`\nd"), "'a $b `c`\nd'");
/// ```
pub fn shell_quote<V: Display>(value: V) -> String {
    shell_words::quote(&value.to_string()).into()
}

/// Format a value with [`Debug`] and quote it for safe shell insertion.
pub fn shell_quote_debug<V: Debug>(value: V) -> String {
    shell_words::quote(&format!("{value:?}")).into()
}