jsona_cli/commands/
queries.rs1use 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 #[clap(short = 'A', long)]
102 pub annotation: bool,
103
104 #[clap(short, long)]
106 pub file_path: Option<String>,
107
108 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}