ctoolbox/utilities/
json.rs

1use crate::log;
2use core::fmt::{Display, Formatter};
3use hifijson::token::Lex;
4use hifijson::{SliceLexer, str};
5use jaq_core::{Ctx, RcIter, load};
6use jaq_json::Val;
7use load::{Arena, File, Loader};
8use serde::Deserialize;
9use serde::Serialize;
10pub use serde_json as utilities_serde_json;
11pub use serde_json::json as utilities_serde_json_json;
12use std::default::Default;
13use std::fmt;
14use thiserror::Error;
15
16// Equivalent to json_encode
17#[macro_export]
18macro_rules! utilities_json_json {
19    ($($json:tt)+) => {
20        // The $crate prefix is used to refer to the current crate, so that the macro can be used in other crates.
21        $crate::utilities::json::utilities_serde_json::to_string(&$crate::utilities::json::utilities_serde_json_json!($($json)+)).unwrap()
22    };
23}
24
25/* Remaining code is based on https://github.com/01mf02/jaq
26    License:
27
28    Permission is hereby granted, free of charge, to any
29    person obtaining a copy of this software and associated
30    documentation files (the "Software"), to deal in the
31    Software without restriction, including without
32    limitation the rights to use, copy, modify, merge,
33    publish, distribute, sublicense, and/or sell copies of
34    the Software, and to permit persons to whom the Software
35    is furnished to do so, subject to the following
36    conditions:
37
38    The above copyright notice and this permission notice
39    shall be included in all copies or substantial portions
40    of the Software.
41
42    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
43    ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
44    TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
45    PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
46    SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
47    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
48    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
49    IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
50    DEALINGS IN THE SOFTWARE.
51*/
52
53#[derive(Error, Debug)]
54#[error("Jaq JSON error")] // Formats the error message - but I haven't implemented it properly
55enum JaqError {
56    Parse(String),
57}
58
59pub fn jq_formatted(query: &str, input: &str) -> anyhow::Result<String> {
60    let cli = Cli {
61        ..Default::default()
62    };
63
64    jq_implementation(query, input, cli)
65}
66
67pub fn jqq(query: &str, input: &str) -> anyhow::Result<String> {
68    // jqq = jq quiet
69    let cli = Cli {
70        compact_output: true,
71        raw_output: true,
72        log: false,
73        ..Default::default()
74    };
75
76    jq_implementation(query, input, cli)
77}
78
79trait ErrorSized: std::marker::Sized + std::error::Error {}
80pub fn jq(query: &str, input: &str) -> anyhow::Result<String> {
81    let cli = Cli {
82        compact_output: true,
83        raw_output: true,
84        ..Default::default()
85    };
86
87    jq_implementation(query, input, cli)
88}
89
90pub fn jq_implementation(
91    query: &str,
92    input: &str,
93    options: Cli,
94) -> anyhow::Result<String> {
95    let program = File {
96        code: query,
97        path: (),
98    };
99
100    let loader = Loader::new(jaq_std::defs().chain(jaq_json::defs()));
101    let arena = Arena::default();
102
103    let modules_wrapped = loader.load(&arena, program);
104
105    if let Err(e) = modules_wrapped {
106        return Err(anyhow::anyhow!(format!("Error: {e:?}")));
107    }
108    let modules = modules_wrapped.unwrap();
109
110    let filter_wrapped = jaq_core::Compiler::default()
111        .with_funs(jaq_std::funs().chain(jaq_json::funs()))
112        .compile(modules);
113
114    if let Err(e) = filter_wrapped {
115        return Err(anyhow::anyhow!(format!("Error: {e:?}")));
116    }
117    let filter = filter_wrapped.unwrap();
118
119    let inputs = RcIter::new(core::iter::empty());
120
121    let slice = input.as_bytes();
122    let mut lexer = SliceLexer::new(slice);
123    let err = |e| JaqError::Parse(format!("{e} parsing JSON"));
124    let parsed = lexer.exactly_one(Val::parse).map_err(err)?;
125
126    let mut out = filter.run((Ctx::new([], &inputs), parsed));
127
128    let cli = options;
129
130    if cli.color_output {
131        yansi::enable();
132    } else {
133        yansi::disable();
134    }
135
136    let mut result = String::new();
137
138    if let Some(val_result) = out.next() {
139        match val_result {
140            Ok(val) => {
141                let f = |f: &mut Formatter| {
142                    let opts = PpOpts {
143                        compact: cli.compact_output,
144                        indent: if cli.tab {
145                            String::from("\t")
146                        } else {
147                            " ".repeat(cli.indent)
148                        },
149                        sort_keys: cli.sort_keys,
150                    };
151                    fmt_val(f, &opts, 0, &val)
152                };
153                if let Val::Str(s) = &val {
154                    if cli.raw_output || cli.join_output {
155                        result = format!("{result}{s}");
156                    } else {
157                        result = format!("{}{}", result, FormatterFn(f));
158                    }
159                } else {
160                    result = format!("{}{}", result, FormatterFn(f));
161                }
162                return Ok(result);
163            }
164            Err(e) => {
165                if cli.log {
166                    log!(
167                        format!("Error querying {input} with {query}: {e:?}")
168                            .as_str()
169                    );
170                }
171                return Err(anyhow::anyhow!(format!(
172                    "Error querying {input} with {query}: {e:?}"
173                )));
174            }
175        }
176    }
177
178    Err(anyhow::anyhow!("Could not parse JSON"))
179}
180
181#[derive(Serialize, Deserialize)]
182pub struct Cli {
183    // see https://github.com/01mf02/jaq/blob/main/jaq/src/cli.rs
184    pub compact_output: bool,
185    pub raw_output: bool,
186    pub join_output: bool,
187    pub in_place: bool,
188    pub sort_keys: bool,
189    pub color_output: bool,
190    pub tab: bool,
191    pub indent: usize,
192    pub log: bool,
193}
194
195impl Default for Cli {
196    fn default() -> Self {
197        Self {
198            compact_output: false,
199            raw_output: false,
200            join_output: false,
201            in_place: false,
202            sort_keys: false,
203            color_output: false,
204            tab: false,
205            indent: 2,
206            log: true,
207        }
208    }
209}
210
211// see https://github.com/01mf02/jaq/blob/main/jaq/src/main.rs
212struct FormatterFn<F>(F);
213
214impl<F: Fn(&mut Formatter) -> fmt::Result> Display for FormatterFn<F> {
215    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
216        self.0(f)
217    }
218}
219
220struct PpOpts {
221    compact: bool,
222    indent: String,
223    sort_keys: bool,
224}
225
226impl PpOpts {
227    fn indent(&self, f: &mut Formatter, level: usize) -> fmt::Result {
228        if !self.compact {
229            write!(f, "{}", self.indent.repeat(level))?;
230        }
231        Ok(())
232    }
233
234    fn newline(&self, f: &mut Formatter) -> fmt::Result {
235        if !self.compact {
236            writeln!(f)?;
237        }
238        Ok(())
239    }
240}
241
242fn fmt_seq<T, I, F>(
243    fmt: &mut Formatter,
244    opts: &PpOpts,
245    level: usize,
246    xs: I,
247    f: F,
248) -> fmt::Result
249where
250    I: IntoIterator<Item = T>,
251    F: Fn(&mut Formatter, T) -> fmt::Result,
252{
253    opts.newline(fmt)?;
254    let mut iter = xs.into_iter().peekable();
255    while let Some(x) = iter.next() {
256        opts.indent(fmt, level + 1)?;
257        f(fmt, x)?;
258        if iter.peek().is_some() {
259            write!(fmt, ",")?;
260        }
261        opts.newline(fmt)?;
262    }
263    opts.indent(fmt, level)
264}
265
266fn fmt_val(
267    f: &mut Formatter,
268    opts: &PpOpts,
269    level: usize,
270    v: &Val,
271) -> fmt::Result {
272    use yansi::Paint;
273
274    match v {
275        Val::Null
276        | Val::Bool(_)
277        | Val::Int(_)
278        | Val::Float(_)
279        | Val::Num(_) => v.fmt(f),
280        Val::Str(_) => write!(f, "{}", v.green()),
281        Val::Arr(a) => {
282            '['.bold().fmt(f)?;
283            if !a.is_empty() {
284                fmt_seq(f, opts, level, &**a, |f, x| {
285                    fmt_val(f, opts, level + 1, x)
286                })?;
287            }
288            ']'.bold().fmt(f)
289        }
290        Val::Obj(o) => {
291            '{'.bold().fmt(f)?;
292            let kv =
293                |f: &mut Formatter, (k, val): (&std::rc::Rc<String>, &Val)| {
294                    write!(f, "{:?}:", k.bold())?;
295                    if !opts.compact {
296                        write!(f, " ")?;
297                    }
298                    fmt_val(f, opts, level + 1, val)
299                };
300            if !o.is_empty() {
301                if opts.sort_keys {
302                    let mut o: Vec<_> = o.iter().collect();
303                    o.sort_by_key(|(k, _v)| *k);
304                    fmt_seq(f, opts, level, o, kv)
305                } else {
306                    fmt_seq(f, opts, level, &**o, kv)
307                }?;
308            }
309            '}'.bold().fmt(f)
310        }
311    }
312}