taplo_cli/commands/
queries.rs1use std::borrow::Cow;
2
3use crate::{
4 args::{GetCommand, OutputFormat},
5 Taplo,
6};
7use anyhow::anyhow;
8use codespan_reporting::files::SimpleFile;
9use taplo::{
10 dom::{Keys, Node},
11 parser,
12};
13use taplo_common::environment::Environment;
14use tokio::io::{AsyncReadExt, AsyncWriteExt};
15
16impl<E: Environment> Taplo<E> {
17 pub async fn execute_get(&self, cmd: GetCommand) -> Result<(), anyhow::Error> {
18 let mut stdout = self.env.stdout();
19
20 if cmd.separator.is_some() && !matches!(cmd.output_format, OutputFormat::Value) {
22 return Err(anyhow!(
23 "`--separator` is only valid for `--output-format value`"
24 ));
25 }
26
27 let source = match &cmd.file_path {
28 Some(p) => String::from_utf8(self.env.read_file(p).await?)?,
29 None => {
30 let mut stdin = self.env.stdin();
31 let mut s = String::new();
32 stdin.read_to_string(&mut s).await?;
33 s
34 }
35 };
36
37 let parse = parser::parse(&source);
38
39 let file_path = cmd
40 .file_path
41 .as_ref()
42 .map(|p| p.to_string_lossy())
43 .unwrap_or(Cow::Borrowed("-"));
44
45 self.print_parse_errors(&SimpleFile::new(&file_path, &source), &parse.errors)
46 .await?;
47
48 if !parse.errors.is_empty() {
49 return Err(anyhow!("syntax errors found"));
50 }
51
52 let node = parse.into_dom();
53
54 if let Err(errors) = node.validate() {
55 self.print_semantic_errors(&SimpleFile::new(&file_path, &source), errors)
56 .await?;
57
58 return Err(anyhow!("semantic errors found"));
59 }
60
61 match cmd.output_format {
62 crate::args::OutputFormat::Json => {
63 if let Some(p) = cmd.pattern {
64 let p = p.trim_start_matches('.');
65
66 let keys = p
67 .parse::<Keys>()
68 .map_err(|err| anyhow!("invalid pattern: {err}"))?;
69
70 let mut nodes = node
71 .find_all_matches(keys, false)
72 .map_err(|err| anyhow!("invalid pattern: {err}"))?;
73
74 if nodes.len() == 0 {
75 return Err(anyhow!("no values matched the pattern"));
76 }
77
78 if nodes.len() == 1 {
79 stdout
80 .write_all(&serde_json::to_vec_pretty(&nodes.next().unwrap().1)?)
81 .await?;
82 if !cmd.strip_newline {
83 stdout.write_all(b"\n").await?;
84 }
85 stdout.flush().await?;
86 } else {
87 stdout
88 .write_all(&serde_json::to_vec_pretty(
89 &nodes.map(|n| n.1).collect::<Vec<_>>(),
90 )?)
91 .await?;
92 if !cmd.strip_newline {
93 stdout.write_all(b"\n").await?;
94 }
95 stdout.flush().await?;
96 }
97 } else {
98 stdout.write_all(&serde_json::to_vec_pretty(&node)?).await?;
99 if !cmd.strip_newline {
100 stdout.write_all(b"\n").await?;
101 }
102 stdout.flush().await?;
103 }
104 }
105 crate::args::OutputFormat::Value => {
106 let separator = cmd.separator.as_deref().unwrap_or("\n");
107 let mut buf = if let Some(p) = cmd.pattern {
108 let p = p.trim_start_matches('.');
109
110 let nodes = p
111 .parse::<Keys>()
112 .and_then(|keys| node.find_all_matches(keys, false))
113 .map_err(|err| anyhow!("invalid pattern: {err}"))?;
114
115 if nodes.len() == 0 {
116 return Err(anyhow!("no values matched the pattern"));
117 }
118
119 let values = nodes
120 .map(|(_, node)| extract_value(&node, separator))
121 .collect::<Result<Vec<String>, _>>()?;
122
123 values.join(separator)
124 } else {
125 extract_value(&node, separator)?
126 };
127
128 if !cmd.strip_newline {
129 buf += "\n";
130 }
131
132 stdout.write_all(buf.as_bytes()).await?;
133 stdout.flush().await?;
134 }
135 crate::args::OutputFormat::Toml => {
136 if let Some(p) = cmd.pattern {
137 let p = p.trim_start_matches('.');
138
139 let keys = p
140 .parse::<Keys>()
141 .map_err(|err| anyhow!("invalid pattern: {err}"))?;
142
143 let mut nodes = node
144 .find_all_matches(keys, false)
145 .map_err(|err| anyhow!("invalid pattern: {err}"))?;
146
147 if nodes.len() == 0 {
148 return Err(anyhow!("no values matched the pattern"));
149 }
150
151 if nodes.len() == 1 {
152 let mut buf = nodes.next().unwrap().1.to_toml(false, false);
153
154 if cmd.strip_newline {
155 if buf.ends_with('\n') {
156 let new_len = buf.trim_end().len();
157 buf.truncate(new_len);
158 }
159 } else if !buf.ends_with('\n') {
160 buf += "\n";
161 }
162
163 stdout.write_all(buf.as_bytes()).await?;
164 stdout.flush().await?;
165 } else {
166 let mut buf = String::from("[\n");
167
168 for (_, node) in nodes {
169 buf += " ";
170 buf += &node.to_toml(true, false);
171 buf += ",\n";
172 }
173
174 buf += "]\n";
175
176 if cmd.strip_newline {
177 if buf.ends_with('\n') {
178 let new_len = buf.trim_end().len();
179 buf.truncate(new_len);
180 }
181 } else if !buf.ends_with('\n') {
182 buf += "\n";
183 }
184
185 stdout.write_all(buf.as_bytes()).await?;
186 stdout.flush().await?;
187 }
188 stdout.flush().await?;
189 } else {
190 let mut buf = node.to_toml(false, false);
191
192 if cmd.strip_newline {
193 if buf.ends_with('\n') {
194 let new_len = buf.trim_end().len();
195 buf.truncate(new_len);
196 }
197 } else if !buf.ends_with('\n') {
198 buf += "\n";
199 }
200
201 stdout.write_all(buf.as_bytes()).await?;
202 stdout.flush().await?;
203 }
204 }
205 }
206
207 Ok(())
208 }
209}
210
211fn extract_value(node: &Node, separator: &str) -> Result<String, anyhow::Error> {
212 Ok(match node {
213 Node::Table(_) => {
214 return Err(anyhow!(
215 r#"cannot print tables with the given output format, specify a different output format (e.g. with `-o json`) "#
216 ))
217 }
218 Node::Array(arr) => {
219 let mut values = Vec::new();
220
221 for node in arr.items().read().iter() {
222 values.push(extract_value(node, separator)?);
223 }
224
225 values.join(separator)
226 }
227 Node::Bool(b) => b.value().to_string(),
228 Node::Str(s) => s.value().to_string(),
229 Node::Integer(i) => i.value().to_string(),
230 Node::Float(f) => f.value().to_string(),
231 Node::Date(d) => d.value().to_string(),
232 Node::Invalid(_) => "".into(),
233 })
234}