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}