jsona_cli/commands/
queries.rs

1use crate::App;
2
3use anyhow::{anyhow, bail};
4use clap::Args;
5use codespan_reporting::files::SimpleFile;
6use jsona::{
7    dom::{DomNode, Node, QueryKeys},
8    parser,
9};
10use jsona_util::environment::Environment;
11use serde_json::{json, Value};
12use tokio::io::{AsyncReadExt, AsyncWriteExt};
13
14impl<E: Environment> App<E> {
15    pub async fn execute_get(&self, cmd: GetCommand) -> Result<(), anyhow::Error> {
16        let mut stdout = self.env.stdout();
17
18        let source = match &cmd.file_path {
19            Some(p) => {
20                let (_, source) = self.load_file(p).await?;
21                source
22            }
23            None => {
24                let mut stdin = self.env.stdin();
25                let mut s = String::new();
26                stdin.read_to_string(&mut s).await?;
27                s
28            }
29        };
30
31        let parse = parser::parse(&source);
32
33        let file_path = cmd.file_path.as_deref().unwrap_or("-");
34
35        self.print_parse_errors(&SimpleFile::new(file_path, &source), &parse.errors)
36            .await?;
37
38        if !parse.errors.is_empty() {
39            return Err(anyhow!("syntax errors found"));
40        }
41
42        let node = parse.into_dom();
43
44        if let Err(errors) = node.validate() {
45            self.print_semantic_errors(&SimpleFile::new(file_path, &source), errors)
46                .await?;
47
48            return Err(anyhow!("semantic errors found"));
49        }
50
51        let nodes = match cmd.pattern {
52            Some(p) => {
53                let p = p.trim_start_matches('.');
54
55                let keys = p.parse::<QueryKeys>().map_err(|errors| {
56                    anyhow!(
57                        "invalid pattern: {}",
58                        errors
59                            .into_iter()
60                            .map(|v| v.to_string())
61                            .collect::<Vec<String>>()
62                            .join(",")
63                    )
64                })?;
65
66                node.matches_all(keys, false)
67                    .map_err(|err| anyhow!("invalid pattern: {err}"))?
68                    .map(|(_, v)| v)
69                    .collect()
70            }
71            None => vec![node],
72        };
73        let buf = {
74            let items: Vec<Value> = if cmd.annotation {
75                nodes.iter().map(to_json).collect()
76            } else {
77                nodes.iter().map(|v| v.to_plain_json()).collect()
78            };
79            let value = match items.len() {
80                0 => {
81                    bail!("no found");
82                }
83                1 => items[0].clone(),
84                _ => Value::Array(items),
85            };
86            if let Some(value) = value.as_str() {
87                value.as_bytes().to_vec()
88            } else {
89                serde_json::to_vec_pretty(&value).unwrap()
90            }
91        };
92        stdout.write_all(&buf).await?;
93        stdout.flush().await?;
94        Ok(())
95    }
96}
97
98#[derive(Debug, Clone, Args)]
99pub struct GetCommand {
100    /// Whether output includes annotation
101    #[clap(short = 'A', long)]
102    pub annotation: bool,
103
104    /// Path to the JSONA document, if omitted the standard input will be used.
105    #[clap(short, long)]
106    pub file_path: Option<String>,
107
108    /// A dotted key pattern to the value within the JSONA document.
109    ///
110    /// If omitted, the entire document will be printed.
111    ///
112    /// If the pattern yielded no values, the operation will fail.
113    ///
114    /// The pattern supports `jq`-like syntax and glob patterns as well:
115    ///
116    /// Examples:
117    ///
118    /// - table.array[1].foo
119    /// - table.array.1.foo
120    /// - table.array[*].foo
121    /// - table.array.*.foo
122    /// - dependencies.tokio-*.version
123    ///
124    pub pattern: Option<String>,
125}
126
127pub fn to_json(node: &Node) -> Value {
128    let annotations = node.annotations().map(|a| {
129        Value::Object(
130            a.value()
131                .read()
132                .iter()
133                .map(|(k, v)| (k.to_string(), v.to_plain_json()))
134                .collect(),
135        )
136    });
137    match node {
138        Node::Null(_) => match annotations {
139            Some(annotations) => {
140                json!({
141                    "value": null,
142                    "annotations": annotations
143                })
144            }
145            None => {
146                json!({
147                    "value": null,
148                })
149            }
150        },
151        Node::Bool(v) => match annotations {
152            Some(annotations) => {
153                json!({
154                    "value": v.value(),
155                    "annotations": annotations
156                })
157            }
158            None => {
159                json!({
160                    "value": v.value(),
161                })
162            }
163        },
164        Node::Number(v) => match annotations {
165            Some(annotations) => {
166                json!({
167                    "value": v.value(),
168                    "annotations": annotations
169                })
170            }
171            None => {
172                json!({
173                    "value": v.value(),
174                })
175            }
176        },
177        Node::String(v) => match annotations {
178            Some(annotations) => {
179                json!({
180                    "value": v.value(),
181                    "annotations": annotations
182                })
183            }
184            None => {
185                json!({
186                    "value": v.value(),
187                })
188            }
189        },
190        Node::Array(v) => {
191            let value = Value::Array(v.value().read().iter().map(to_json).collect());
192            match annotations {
193                Some(annotations) => {
194                    json!({
195                        "value": value,
196                        "annotations": annotations
197                    })
198                }
199                None => {
200                    json!({
201                        "value": value,
202                    })
203                }
204            }
205        }
206        Node::Object(v) => {
207            let value = Value::Object(
208                v.value()
209                    .read()
210                    .iter()
211                    .map(|(k, v)| (k.to_string(), to_json(v)))
212                    .collect(),
213            );
214            match annotations {
215                Some(annotations) => {
216                    json!({
217                        "value": value,
218                        "annotations": annotations
219                    })
220                }
221                None => {
222                    json!({
223                        "value": value,
224                    })
225                }
226            }
227        }
228    }
229}