termcolor_json/
lib.rs

1#![deny(missing_debug_implementations)]
2#![deny(missing_docs)]
3#![doc(html_root_url = "https://docs.rs/termcolor-json/1.0.0")]
4
5//! A library for writing colored [JSON](https://crates.io/crates/serde_json) output to a [termcolor](https://crates.io/crates/termcolor) terminal.
6//!
7//! ```rust
8//! # use termcolor::{ColorChoice, StandardStream};
9//! # fn run() -> serde_json::Result<()> {
10//! let stdout = StandardStream::stdout(ColorChoice::Auto);
11//!
12//! termcolor_json::to_writer(
13//!     &mut stdout.lock(),
14//!     &serde_json::json!({
15//!         "string": "value",
16//!         "number": 123,
17//!         "bool": true,
18//!         "null": null,
19//!     }),
20//! )?;
21//! # Ok(())
22//! # }
23//! # fn main() { run().unwrap() }
24//! ```
25
26use std::{
27    cell::RefCell,
28    io::{self, Write},
29    mem,
30};
31
32use serde::Serialize;
33use serde_json::ser::{CharEscape, CompactFormatter, Formatter, PrettyFormatter, Serializer};
34use termcolor::{Color, ColorSpec, WriteColor};
35
36/// Controls the console formatter used for different JSON tokens.
37///
38/// A reasonable default theme is provided by [`Theme::default`].
39#[derive(Clone, Debug)]
40pub struct Theme {
41    reset: ColorSpec,
42    null: ColorSpec,
43    bool: ColorSpec,
44    number: ColorSpec,
45    string: ColorSpec,
46    object_key: ColorSpec,
47}
48
49/// Serialize the given data structure as colored, pretty-printed JSON into the IO stream, using the default theme.
50pub fn to_writer<W, T>(writer: W, value: &T) -> serde_json::Result<()>
51where
52    W: WriteColor,
53    T: ?Sized + Serialize,
54{
55    to_writer_with_theme_and_formatter(writer, value, &Theme::default(), PrettyFormatter::new())
56}
57
58/// Serialize the given data structure as colored, compact JSON into the IO stream, using the default theme.
59pub fn to_writer_compact<W, T>(writer: W, value: &T) -> serde_json::Result<()>
60where
61    W: WriteColor,
62    T: ?Sized + Serialize,
63{
64    to_writer_with_theme_and_formatter(writer, value, &Theme::default(), CompactFormatter)
65}
66
67/// Serialize the given data structure as colored, pretty-printed JSON into the IO stream, using the given theme.
68pub fn to_writer_with_theme<W, T>(writer: W, value: &T, theme: &Theme) -> serde_json::Result<()>
69where
70    W: WriteColor,
71    T: ?Sized + Serialize,
72{
73    to_writer_with_theme_and_formatter(writer, value, theme, PrettyFormatter::new())
74}
75
76/// Serialize the given data structure as colored JSON into the IO stream, using the given theme and formatter.
77///
78/// The `formatter` argument is used to write text to the stream. For example, to customize the identation of pretty-printed JSON, you could
79/// pass `PrettyFormatter::with_indent("\t")`.
80pub fn to_writer_with_theme_and_formatter<W, T, F>(
81    writer: W,
82    value: &T,
83    theme: &Theme,
84    formatter: F,
85) -> serde_json::Result<()>
86where
87    W: WriteColor,
88    T: ?Sized + Serialize,
89    F: Formatter,
90{
91    if !writer.supports_color() {
92        let mut ser = Serializer::with_formatter(writer, formatter);
93        value.serialize(&mut ser)
94    } else {
95        let writer = SharedWriter::new(writer);
96        let formatter = ColorFormatter::new(&writer, theme, formatter);
97        let mut ser = Serializer::with_formatter(&writer, formatter);
98        value.serialize(&mut ser)
99    }
100}
101
102struct ColorFormatter<'a, W, F> {
103    formatter: F,
104    writer: W,
105    theme: &'a Theme,
106    writing_key: bool,
107    need_reset: bool,
108}
109
110impl<'a, W, F> ColorFormatter<'a, W, F> {
111    fn new(writer: W, theme: &'a Theme, formatter: F) -> Self {
112        ColorFormatter {
113            formatter,
114            writer,
115            theme,
116            writing_key: false,
117            need_reset: false,
118        }
119    }
120}
121
122impl<'a, W, F> Formatter for ColorFormatter<'a, W, F>
123where
124    W: WriteColor,
125    F: Formatter,
126{
127    fn write_null<U>(&mut self, _: &mut U) -> io::Result<()>
128    where
129        U: ?Sized + Write,
130    {
131        let f = &mut self.formatter;
132        with_color(&mut self.writer, &self.theme.null, self.theme, |w| {
133            f.write_null(w)
134        })
135    }
136
137    fn write_bool<U>(&mut self, _: &mut U, value: bool) -> io::Result<()>
138    where
139        U: ?Sized + Write,
140    {
141        let f = &mut self.formatter;
142        with_color(&mut self.writer, &self.theme.bool, self.theme, |w| {
143            f.write_bool(w, value)
144        })
145    }
146
147    fn write_i8<U>(&mut self, _: &mut U, value: i8) -> io::Result<()>
148    where
149        U: ?Sized + Write,
150    {
151        let f = &mut self.formatter;
152        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
153            f.write_i8(w, value)
154        })
155    }
156
157    fn write_i16<U>(&mut self, _: &mut U, value: i16) -> io::Result<()>
158    where
159        U: ?Sized + Write,
160    {
161        let f = &mut self.formatter;
162        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
163            f.write_i16(w, value)
164        })
165    }
166
167    fn write_i32<U>(&mut self, _: &mut U, value: i32) -> io::Result<()>
168    where
169        U: ?Sized + Write,
170    {
171        let f = &mut self.formatter;
172        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
173            f.write_i32(w, value)
174        })
175    }
176
177    fn write_i64<U>(&mut self, _: &mut U, value: i64) -> io::Result<()>
178    where
179        U: ?Sized + Write,
180    {
181        let f = &mut self.formatter;
182        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
183            f.write_i64(w, value)
184        })
185    }
186
187    fn write_u8<U>(&mut self, _: &mut U, value: u8) -> io::Result<()>
188    where
189        U: ?Sized + Write,
190    {
191        let f = &mut self.formatter;
192        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
193            f.write_u8(w, value)
194        })
195    }
196
197    fn write_u16<U>(&mut self, _: &mut U, value: u16) -> io::Result<()>
198    where
199        U: ?Sized + Write,
200    {
201        let f = &mut self.formatter;
202        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
203            f.write_u16(w, value)
204        })
205    }
206
207    fn write_u32<U>(&mut self, _: &mut U, value: u32) -> io::Result<()>
208    where
209        U: ?Sized + Write,
210    {
211        let f = &mut self.formatter;
212        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
213            f.write_u32(w, value)
214        })
215    }
216
217    fn write_u64<U>(&mut self, _: &mut U, value: u64) -> io::Result<()>
218    where
219        U: ?Sized + Write,
220    {
221        let f = &mut self.formatter;
222        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
223            f.write_u64(w, value)
224        })
225    }
226
227    fn write_f32<U>(&mut self, _: &mut U, value: f32) -> io::Result<()>
228    where
229        U: ?Sized + Write,
230    {
231        let f = &mut self.formatter;
232        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
233            f.write_f32(w, value)
234        })
235    }
236
237    fn write_f64<U>(&mut self, _: &mut U, value: f64) -> io::Result<()>
238    where
239        U: ?Sized + Write,
240    {
241        let f = &mut self.formatter;
242        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
243            f.write_f64(w, value)
244        })
245    }
246
247    fn write_number_str<U>(&mut self, _: &mut U, value: &str) -> io::Result<()>
248    where
249        U: ?Sized + Write,
250    {
251        let f = &mut self.formatter;
252        with_color(&mut self.writer, &self.theme.number, self.theme, |w| {
253            f.write_number_str(w, value)
254        })
255    }
256
257    fn begin_string<U>(&mut self, _: &mut U) -> io::Result<()>
258    where
259        U: ?Sized + Write,
260    {
261        if !self.writing_key {
262            self.need_reset = set_color(&mut self.writer, &self.theme.string)?;
263        }
264        self.formatter.begin_string(&mut self.writer)
265    }
266
267    fn end_string<U>(&mut self, _: &mut U) -> io::Result<()>
268    where
269        U: ?Sized + Write,
270    {
271        self.formatter.end_string(&mut self.writer)?;
272        if !self.writing_key && mem::take(&mut self.need_reset) {
273            reset(&mut self.writer, self.theme)?;
274        }
275        Ok(())
276    }
277
278    fn write_string_fragment<U>(&mut self, _: &mut U, fragment: &str) -> io::Result<()>
279    where
280        U: ?Sized + Write,
281    {
282        self.formatter
283            .write_string_fragment(&mut self.writer, fragment)
284    }
285
286    fn write_char_escape<U>(&mut self, _: &mut U, char_escape: CharEscape) -> io::Result<()>
287    where
288        U: ?Sized + Write,
289    {
290        self.formatter
291            .write_char_escape(&mut self.writer, char_escape)
292    }
293
294    fn begin_array<U>(&mut self, _: &mut U) -> io::Result<()>
295    where
296        U: ?Sized + Write,
297    {
298        self.formatter.begin_array(&mut self.writer)
299    }
300
301    fn end_array<U>(&mut self, _: &mut U) -> io::Result<()>
302    where
303        U: ?Sized + Write,
304    {
305        self.formatter.end_array(&mut self.writer)
306    }
307
308    fn begin_array_value<U>(&mut self, _: &mut U, first: bool) -> io::Result<()>
309    where
310        U: ?Sized + Write,
311    {
312        self.formatter.begin_array_value(&mut self.writer, first)
313    }
314
315    fn end_array_value<U>(&mut self, _: &mut U) -> io::Result<()>
316    where
317        U: ?Sized + Write,
318    {
319        self.formatter.end_array_value(&mut self.writer)
320    }
321
322    fn begin_object<U>(&mut self, _: &mut U) -> io::Result<()>
323    where
324        U: ?Sized + Write,
325    {
326        self.formatter.begin_object(&mut self.writer)
327    }
328
329    fn end_object<U>(&mut self, _: &mut U) -> io::Result<()>
330    where
331        U: ?Sized + Write,
332    {
333        self.formatter.end_object(&mut self.writer)
334    }
335
336    fn begin_object_key<U>(&mut self, _: &mut U, first: bool) -> io::Result<()>
337    where
338        U: ?Sized + Write,
339    {
340        self.writing_key = true;
341        self.formatter.begin_object_key(&mut self.writer, first)?;
342        self.need_reset = set_color(&mut self.writer, &self.theme.object_key)?;
343        Ok(())
344    }
345
346    fn end_object_key<U>(&mut self, _: &mut U) -> io::Result<()>
347    where
348        U: ?Sized + Write,
349    {
350        if mem::take(&mut self.need_reset) {
351            reset(&mut self.writer, self.theme)?;
352        }
353        self.formatter.end_object_key(&mut self.writer)?;
354        self.writing_key = false;
355        Ok(())
356    }
357
358    fn begin_object_value<U>(&mut self, _: &mut U) -> io::Result<()>
359    where
360        U: ?Sized + Write,
361    {
362        self.formatter.begin_object_value(&mut self.writer)
363    }
364
365    fn end_object_value<U>(&mut self, _: &mut U) -> io::Result<()>
366    where
367        U: ?Sized + Write,
368    {
369        self.formatter.end_object_value(&mut self.writer)
370    }
371
372    fn write_raw_fragment<U>(&mut self, _: &mut U, fragment: &str) -> io::Result<()>
373    where
374        U: ?Sized + io::Write,
375    {
376        self.formatter
377            .write_raw_fragment(&mut self.writer, fragment)
378    }
379}
380
381fn set_color<W>(writer: &mut W, color: &ColorSpec) -> io::Result<bool>
382where
383    W: WriteColor,
384{
385    if color.is_none() {
386        Ok(false)
387    } else {
388        writer.set_color(color)?;
389        Ok(true)
390    }
391}
392
393fn reset<W>(writer: &mut W, theme: &Theme) -> io::Result<()>
394where
395    W: WriteColor,
396{
397    writer.set_color(&theme.reset)
398}
399
400fn with_color<W, F>(writer: &mut W, color: &ColorSpec, theme: &Theme, write: F) -> io::Result<()>
401where
402    W: WriteColor,
403    F: FnOnce(&mut W) -> io::Result<()>,
404{
405    if set_color(writer, color)? {
406        write(writer)?;
407        reset(writer, theme)?;
408        Ok(())
409    } else {
410        write(writer)
411    }
412}
413
414// serde_json's serializer expects to own its own `Write` implementation, but we need to keep our
415// own reference to it so we can set colors on the stream.
416//
417// We could pass serde_json a dummy implementation like io::Sink and do all the writing ourselves
418// in ColorFormatter, but this is a forwards-compability hazard if new methods are added to Formatter
419// in future which write to the dummy stream by default.
420//
421// Instead we share ownership of the stream between serde_json and this library using a RefCell.
422struct SharedWriter<W> {
423    inner: RefCell<W>,
424}
425
426impl<W> SharedWriter<W> {
427    fn new(writer: W) -> Self {
428        SharedWriter {
429            inner: RefCell::new(writer),
430        }
431    }
432}
433
434impl<W> Write for &'_ SharedWriter<W>
435where
436    W: Write,
437{
438    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
439        self.inner.borrow_mut().write(buf)
440    }
441
442    fn flush(&mut self) -> io::Result<()> {
443        self.inner.borrow_mut().flush()
444    }
445
446    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> io::Result<usize> {
447        self.inner.borrow_mut().write_vectored(bufs)
448    }
449
450    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
451        self.inner.borrow_mut().write_all(buf)
452    }
453}
454
455impl<W> WriteColor for &'_ SharedWriter<W>
456where
457    W: WriteColor,
458{
459    fn supports_color(&self) -> bool {
460        self.inner.borrow().supports_color()
461    }
462
463    fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
464        self.inner.borrow_mut().set_color(spec)
465    }
466
467    fn reset(&mut self) -> io::Result<()> {
468        self.inner.borrow_mut().reset()
469    }
470
471    fn is_synchronous(&self) -> bool {
472        self.inner.borrow().is_synchronous()
473    }
474}
475
476impl Theme {
477    /// Create a theme with no styling.
478    pub fn none() -> Self {
479        Theme::new(ColorSpec::new())
480    }
481
482    /// Create a theme where all text is printed using the given [`ColorSpec`].
483    pub fn new(default: ColorSpec) -> Self {
484        Theme {
485            reset: default.clone(),
486            null: default.clone(),
487            bool: default.clone(),
488            number: default.clone(),
489            string: default.clone(),
490            object_key: default,
491        }
492    }
493
494    /// Gets a reference to the color specification for the `null` token.
495    pub fn null(&self) -> &ColorSpec {
496        &self.null
497    }
498
499    /// Gets a mutable reference to the color specification for the `null` token.
500    pub fn null_mut(&mut self) -> &mut ColorSpec {
501        &mut self.null
502    }
503
504    /// Gets a reference to the color specification for `true` and `false` tokens.
505    pub fn bool(&self) -> &ColorSpec {
506        &self.bool
507    }
508
509    /// Gets a mutable reference to the color specification for `true` and `false` tokens.
510    pub fn bool_mut(&mut self) -> &mut ColorSpec {
511        &mut self.bool
512    }
513
514    /// Gets a reference to the color specification for number tokens.
515    pub fn number(&self) -> &ColorSpec {
516        &self.number
517    }
518
519    /// Gets a mutable reference to the color specification for number tokens.
520    pub fn number_mut(&mut self) -> &mut ColorSpec {
521        &mut self.number
522    }
523
524    /// Gets a mutable reference to the color specification for string tokens.
525    ///
526    /// Note this is not used for object keys, which are controlled by the [`object_key`][Theme::object_key] field.
527    pub fn string(&self) -> &ColorSpec {
528        &self.string
529    }
530
531    /// Gets a mutable reference to the color specification for string tokens.
532    ///
533    /// Note this is not used for object keys, which are controlled by the [`object_key_mut`][Theme::object_key_mut] field.
534    pub fn string_mut(&mut self) -> &mut ColorSpec {
535        &mut self.string
536    }
537
538    /// Gets a reference to the color specification for object key tokens.
539    pub fn object_key(&self) -> &ColorSpec {
540        &self.object_key
541    }
542
543    /// Gets a mutable reference to the color specification for object key tokens.
544    pub fn object_key_mut(&mut self) -> &mut ColorSpec {
545        &mut self.object_key
546    }
547}
548
549impl Default for Theme {
550    /// Get a reasonable default theme.
551    fn default() -> Self {
552        let mut theme = Theme::none();
553
554        theme.null_mut().set_fg(Some(Color::Cyan)).set_bold(true);
555        theme.bool_mut().set_fg(Some(Color::Cyan)).set_bold(true);
556        theme.number_mut().set_fg(Some(Color::Cyan));
557        theme.string_mut().set_fg(Some(Color::Green));
558        theme
559            .object_key_mut()
560            .set_fg(Some(Color::Blue))
561            .set_intense(true);
562
563        theme
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570
571    fn data() -> impl Serialize {
572        serde_json::json!({
573            "b": true,
574            "n": null,
575            "m": 1,
576            "s": "v",
577        })
578    }
579
580    fn to_readable_string(vec: Vec<u8>) -> String {
581        String::from_utf8_lossy(&vec).replace('\x1B', "^[")
582    }
583
584    #[test]
585    fn no_color_passthrough() {
586        use termcolor::NoColor;
587
588        let mut buf = Vec::new();
589        let writer = NoColor::new(io::Cursor::new(&mut buf));
590
591        to_writer(writer, &data()).unwrap();
592
593        assert_eq!(
594            to_readable_string(buf).as_str(),
595            "{\n  \"b\": true,\n  \"m\": 1,\n  \"n\": null,\n  \"s\": \"v\"\n}"
596        );
597    }
598
599    #[test]
600    fn no_color_passthrough_compact() {
601        use termcolor::NoColor;
602
603        let mut buf = Vec::new();
604        let writer = NoColor::new(io::Cursor::new(&mut buf));
605
606        to_writer_compact(writer, &data()).unwrap();
607
608        assert_eq!(
609            to_readable_string(buf).as_str(),
610            "{\"b\":true,\"m\":1,\"n\":null,\"s\":\"v\"}"
611        );
612    }
613
614    #[test]
615    fn ansi_empty_theme_passthrough() {
616        use termcolor::Ansi;
617
618        let mut buf = Vec::new();
619        let writer = Ansi::new(io::Cursor::new(&mut buf));
620
621        to_writer_with_theme(writer, &data(), &Theme::none()).unwrap();
622
623        assert_eq!(
624            to_readable_string(buf).as_str(),
625            "{\n  \"b\": true,\n  \"m\": 1,\n  \"n\": null,\n  \"s\": \"v\"\n}"
626        );
627    }
628
629    #[test]
630    fn ansi_empty_theme_passthrough_compact() {
631        use termcolor::Ansi;
632
633        let mut buf = Vec::new();
634        let writer = Ansi::new(io::Cursor::new(&mut buf));
635
636        to_writer_with_theme_and_formatter(writer, &data(), &Theme::none(), CompactFormatter)
637            .unwrap();
638
639        assert_eq!(
640            to_readable_string(buf).as_str(),
641            "{\"b\":true,\"m\":1,\"n\":null,\"s\":\"v\"}"
642        );
643    }
644
645    #[test]
646    fn ansi_default_theme() {
647        use termcolor::Ansi;
648
649        let mut buf = Vec::new();
650        let writer = Ansi::new(io::Cursor::new(&mut buf));
651
652        to_writer(writer, &data()).unwrap();
653
654        assert_eq!(to_readable_string(buf).as_str(), "{\n  ^[[0m^[[38;5;12m\"b\"^[[0m: ^[[0m^[[1m^[[36mtrue^[[0m,\n  ^[[0m^[[38;5;12m\"m\"^[[0m: ^[[0m^[[36m1^[[0m,\n  ^[[0m^[[38;5;12m\"n\"^[[0m: ^[[0m^[[1m^[[36mnull^[[0m,\n  ^[[0m^[[38;5;12m\"s\"^[[0m: ^[[0m^[[32m\"v\"^[[0m\n}");
655    }
656
657    #[test]
658    fn ansi_default_theme_compact() {
659        use termcolor::Ansi;
660
661        let mut buf = Vec::new();
662        let writer = Ansi::new(io::Cursor::new(&mut buf));
663
664        to_writer_compact(writer, &data()).unwrap();
665
666        assert_eq!(to_readable_string(buf).as_str(), "{^[[0m^[[38;5;12m\"b\"^[[0m:^[[0m^[[1m^[[36mtrue^[[0m,^[[0m^[[38;5;12m\"m\"^[[0m:^[[0m^[[36m1^[[0m,^[[0m^[[38;5;12m\"n\"^[[0m:^[[0m^[[1m^[[36mnull^[[0m,^[[0m^[[38;5;12m\"s\"^[[0m:^[[0m^[[32m\"v\"^[[0m}");
667    }
668}