taplo_cli/commands/
queries.rs

1use 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        // `--separator` should only be handled for a text output format
21        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}