Skip to main content

jaq_json/
write.rs

1//! Functions and macros for writing (parts of) values.
2//!
3//! We use macros so that we can create both
4//! formatters ([core::fmt::Formatter]) and
5//! writers ([std::io::Write]) from the same code.
6
7use crate::Val;
8use alloc::string::String;
9use core::fmt::{self, Formatter};
10#[cfg(feature = "std")]
11use std::io;
12
13/// Write a byte.
14///
15/// This uses `$f` to write bytes not corresponding to normal ASCII characters.
16///
17/// This is especially useful to pretty-print control characters, such as
18/// `'\n'` (U+000A), but also all other control characters.
19#[macro_export]
20macro_rules! write_byte {
21    ($w:ident, $c:expr, $f:expr) => {{
22        match $c {
23            // Rust does not recognise the following two character escapes
24            0x08 => write!($w, "\\b"),
25            0x0c => write!($w, "\\f"),
26            c @ (b'\t' | b'\n' | b'\r' | b'\\' | b'"') => {
27                write!($w, "{}", char::from(c).escape_default())
28            }
29            0x00..=0x1F | 0x7F..=0xFF => $f,
30            c => write!($w, "{}", char::from(c)),
31        }
32    }};
33}
34
35/// Write a UTF-8 string as JSON string, including leading and trailing quotes.
36///
37/// This uses `$f` to format byte slices that do not need to be escaped.
38#[macro_export]
39macro_rules! write_utf8 {
40    ($w:ident, $s:ident, $f:expr) => {{
41        write!($w, "\"")?;
42        let is_special = |c| matches!(c, 0x00..=0x1F | b'\\' | b'"' | 0x7F);
43        for s in $s.split_inclusive(|c| is_special(*c)) {
44            match s.split_last() {
45                Some((last, init)) if is_special(*last) => {
46                    $f(init)?;
47                    $crate::write_byte!($w, *last, write!($w, "\\u{last:04x}"))?
48                }
49                _ => $f(s)?,
50            }
51        }
52        write!($w, "\"")
53    }};
54}
55
56/// Write a byte string, including leading and trailing quotes.
57///
58/// This maps all non-ASCII `u8`s to `\xXX`.
59#[macro_export]
60macro_rules! write_bytes {
61    ($w:ident, $s:ident) => {{
62        write!($w, "b\"")?;
63        $s.iter()
64            .try_for_each(|c| $crate::write_byte!($w, *c, write!($w, "\\x{c:02x}")))?;
65        write!($w, "\"")
66    }};
67}
68
69/// Write a comma-separated sequence of values `$xs` with `$f`.
70#[macro_export]
71macro_rules! write_seq {
72    ($w:ident, $pp:expr, $level:expr, $xs:expr, $f:expr) => {{
73        let indent = &$pp.indent;
74        if indent.is_some() {
75            writeln!($w)?;
76        }
77        let mut iter = $xs.into_iter().peekable();
78        while let Some(x) = iter.next() {
79            if let Some(indent) = indent {
80                write!($w, "{}", indent.repeat($level + 1))?;
81            }
82            $f(x)?;
83            if iter.peek().is_some() {
84                write!($w, ",")?;
85                if $pp.sep_space && indent.is_none() {
86                    write!($w, " ")?
87                }
88            }
89            if indent.is_some() {
90                writeln!($w)?
91            }
92        }
93        if let Some(indent) = indent {
94            write!($w, "{}", indent.repeat($level))
95        } else {
96            Ok(())
97        }
98    }};
99}
100
101/// Styles used to pretty-print values.
102#[derive(Clone, Default)]
103pub struct Styles<S = String> {
104    /// null
105    pub null: S,
106    /// false
107    pub r#false: S,
108    /// true
109    pub r#true: S,
110    /// numbers
111    pub num: S,
112    /// strings
113    pub str: S,
114    /// arrays
115    pub arr: S,
116    /// objects
117    pub obj: S,
118    /// object keys
119    pub key: S,
120
121    /// byte strings
122    pub bstr: S,
123
124    /// reset pretty printer
125    pub reset: S,
126}
127
128impl Styles {
129    /// Default ANSI styles
130    pub fn ansi() -> Self {
131        let mut cols = Styles::default().parse("90:39:39:39:32:1;39:1;39:1;34");
132        cols.bstr = "\x1b[31m".into();
133        cols.reset = "\x1b[0m".into();
134        cols
135    }
136
137    /// Overwrite styles with those present in `JQ_COLORS` environment variable.
138    pub fn parse(mut self, s: &str) -> Self {
139        let fields = [
140            &mut self.null,
141            &mut self.r#false,
142            &mut self.r#true,
143            &mut self.num,
144            &mut self.str,
145            &mut self.arr,
146            &mut self.obj,
147            &mut self.key,
148        ];
149        for (style, field) in s.split(':').zip(fields) {
150            *field = if style.is_empty() {
151                "".into()
152            } else {
153                alloc::format!("\x1b[{style}m")
154            }
155        }
156        self
157    }
158}
159
160/// Pretty printer.
161#[derive(Clone, Default)]
162pub struct Pp<S = String> {
163    /// indent by repeating given string `n` times
164    pub indent: Option<S>,
165    /// sort objects by keys
166    pub sort_keys: bool,
167    /// styles for different types of values
168    pub styles: Styles<S>,
169    /// put a space after ':'
170    ///
171    /// This is necessary for YAML, which interprets
172    /// {1:2}  as {"1:2": null}, whereas it interprets
173    /// {1: 2} as {1: 2}.
174    pub sep_space: bool,
175}
176
177/// Apply a style `$style` to the output of `$f`.
178#[macro_export]
179macro_rules! style {
180    ($w:ident, $pp:ident, $style:ident, $f:expr) => {{
181        let style = &$pp.styles.$style;
182        let reset = if style.is_empty() {
183            ""
184        } else {
185            &*$pp.styles.reset
186        };
187        write!($w, "{style}")?;
188        $f?;
189        write!($w, "{reset}")
190    }};
191}
192
193/// Write a value as JSON superset, using a function `$f` to write sub-values.
194///
195/// This macro writes strings by replacing invalid UTF-8 characters with the
196/// Unicode replacement character.
197/// That way, this macro can be used not only for writers, but also for
198/// formatters, which require all output to be valid UTF-8.
199/// However, the JSON/YAML writers usually override this behaviour,
200/// yielding invalid UTF-8 characters as-is.
201#[macro_export]
202macro_rules! format_val {
203    ($w:ident, $pp:ident, $level:expr, $v:ident, $f:expr) => {{
204        macro_rules! color {
205            ($style:ident, $g:expr) => {{
206                $crate::style!($w, $pp, $style, $g)
207            }};
208        }
209        match $v {
210            Val::Null => color!(null, write!($w, "null")),
211            Val::Bool(true) => color!(r#true, write!($w, "true")),
212            Val::Bool(false) => color!(r#false, write!($w, "false")),
213            Val::Num(n) => color!(num, write!($w, "{n}")),
214            Val::BStr(b) => color!(bstr, $crate::write_bytes!($w, b)),
215            Val::TStr(s) => color!(
216                str,
217                $crate::write_utf8!($w, s, |part| write!($w, "{}", $crate::bstr(part)))
218            ),
219            Val::Arr(a) => {
220                color!(arr, write!($w, "["))?;
221                if !a.is_empty() {
222                    $crate::write_seq!($w, $pp, $level, &**a, |x| $f($w, $pp, $level + 1, x))?;
223                }
224                color!(arr, write!($w, "]"))
225            }
226            Val::Obj(o) => {
227                color!(obj, write!($w, "{{"))?;
228                macro_rules! kv {
229                    ($kv:expr) => {{
230                        let (k, v) = $kv;
231                        if $pp.styles.key.is_empty() {
232                            $f($w, $pp, $level + 1, k)?
233                        } else {
234                            let unstyled = $crate::write::Pp {
235                                styles: Default::default(),
236                                ..$pp.clone()
237                            };
238                            color!(key, $f($w, &unstyled, $level + 1, k))?;
239                        }
240                        color!(obj, write!($w, ":"))?;
241                        if $pp.sep_space {
242                            write!($w, " ")?;
243                        }
244                        $f($w, $pp, $level + 1, v)
245                    }};
246                }
247                if !o.is_empty() {
248                    if $pp.sort_keys {
249                        let mut o: alloc::vec::Vec<_> = o.iter().collect();
250                        o.sort_by_key(|(k, _v)| *k);
251                        $crate::write_seq!($w, $pp, $level, o, |x| kv!(x))
252                    } else {
253                        $crate::write_seq!($w, $pp, $level, &**o, |x| kv!(x))
254                    }?
255                }
256                color!(obj, write!($w, "}}"))
257            }
258        }
259    }};
260}
261
262/// Write a value as JSON, using a custom function for child values.
263#[macro_export]
264macro_rules! write_val {
265    ($w:ident, $pp:ident, $level:expr, $v:ident, $f:expr) => {{
266        use $crate::Val::TStr;
267        match $v {
268            TStr(s) => style!($w, $pp, str, write_utf8!($w, s, |part| $w.write_all(part))),
269            _ => format_val!($w, $pp, $level, $v, $f),
270        }
271    }};
272}
273
274/// Write a value as JSON.
275///
276/// Note that unlike jq, this may actually produce invalid JSON.
277/// In particular, this may yield:
278///
279/// - literals for special floating-point values (NaN, Infinity, -Infinity)
280/// - invalid UTF-8 characters
281/// - byte strings with `\xXX` sequences
282/// - objects with non-string keys
283///
284/// The key principles behind this behaviour are:
285///
286/// 1. Printing a value should always succeed.
287///    (Otherwise, there would exist values that we could not even inspect.)
288/// 2. Printing a value should yield valid JSON if and only if
289///    the value can be represented by an equivalent JSON value.
290///    (To give users a chance to find non-JSON values and to take appropriate action.)
291///
292/// jq and jaq agree on principle 1, but disagree on principle 2.
293/// In particular, this shows by the fact that `jq -n 'nan'` yields `null`.
294/// That means that jq maps values that cannot be represented by JSON
295/// to different values that can be represented by JSON.
296///
297/// In summary,
298/// jq may cause silent information loss, whereas
299/// jaq may yield invalid JSON values.
300/// Choose your poison.
301#[cfg(feature = "std")]
302pub fn write(w: &mut dyn io::Write, pp: &Pp, level: usize, v: &Val) -> io::Result<()> {
303    write_val!(w, pp, level, v, write)
304}
305
306pub(crate) struct Buf(pub(crate) alloc::vec::Vec<u8>);
307
308impl Buf {
309    fn write_all(&mut self, bytes: &[u8]) -> fmt::Result {
310        self.0.extend_from_slice(bytes);
311        Ok(())
312    }
313}
314
315impl fmt::Write for Buf {
316    fn write_str(&mut self, s: &str) -> fmt::Result {
317        self.write_all(s.as_bytes())
318    }
319}
320
321pub(crate) fn write_buf(w: &mut Buf, pp: &Pp, level: usize, v: &Val) -> fmt::Result {
322    use core::fmt::Write;
323    write_val!(w, pp, level, v, write_buf)
324}
325
326pub(crate) fn format(w: &mut Formatter, pp: &Pp, level: usize, v: &Val) -> fmt::Result {
327    format_val!(w, pp, level, v, format)
328}