Skip to main content

convert2json/
jq.rs

1#![cfg(any(
2    feature = "bsonq",
3    feature = "cborq",
4    feature = "cq",
5    feature = "iq",
6    feature = "msgq",
7    feature = "plistq",
8    feature = "tq",
9    feature = "xq",
10    feature = "yq"
11))]
12use super::{exit, stdin_reader, Error, HELP_ARGS};
13use serde::Serialize;
14use std::env::args;
15use std::fs::File;
16use std::io::{BufRead, BufReader, ErrorKind::NotFound};
17use std::path::Path;
18use std::process::{Child, Command, Stdio};
19
20pub struct Jq {
21    child: Child,
22    program: String,
23    files: Vec<String>,
24    help_requested: bool,
25}
26
27impl Jq {
28    fn is_jq(&mut self) -> bool {
29        self.program == "jq"
30    }
31
32    pub fn write<T>(&mut self, input: T)
33    where
34        T: Sized + Serialize,
35    {
36        if let Some(stdin) = self.child.stdin.as_mut() {
37            if let Err(e) = &serde_json::to_writer(stdin, &input) {
38                eprintln!("Error serializing output: {e}");
39                self.wait();
40                exit(Error::OutputSerialization as i32);
41            }
42        } else {
43            eprintln!("Error opening {}'s STDIN for writing", self.program);
44            self.wait();
45            exit(if self.is_jq() {
46                Error::JqPiping
47            } else {
48                Error::JaqPiping
49            } as i32);
50        }
51    }
52
53    fn wait(&mut self) {
54        if let Err(e) = self.child.wait() {
55            eprintln!("Error waiting on {}: {e}", self.program);
56            exit(if self.is_jq() {
57                Error::JqWaiting
58            } else {
59                Error::JaqWaiting
60            } as i32);
61        }
62    }
63
64    fn parse_args() -> (Vec<String>, Vec<String>, bool) {
65        #[derive(PartialEq)]
66        enum ArgType {
67            Csv,
68            Jq,
69        }
70        let mut arguments: Vec<String> = vec![];
71        let mut files: Vec<String> = vec![];
72        let mut args_done = false;
73        let mut help_requested = false;
74        let mut skip: u8 = 0;
75        let skip_args = [
76            ("--no-trim", 0, ArgType::Csv),
77            ("-d", 1, ArgType::Csv),
78            ("--delimiter", 1, ArgType::Csv),
79            ("-q", 1, ArgType::Csv),
80            ("--quote", 1, ArgType::Csv),
81            ("-E", 1, ArgType::Csv),
82            ("--escape", 1, ArgType::Csv),
83            ("-f", 1, ArgType::Jq),
84            ("--from-file", 1, ArgType::Jq),
85            ("--run-tests", 1, ArgType::Jq),
86            ("--slurpfile", 2, ArgType::Jq),
87            ("--rawfile", 2, ArgType::Jq),
88        ];
89        let mut skip_and_push = false;
90        for arg in args().skip(1) {
91            if skip > 0 {
92                skip -= 1;
93                if !skip_and_push {
94                    continue;
95                }
96            } else if let Some((_, args_to_skip, arg_type)) =
97                skip_args.iter().find(|&item| item.0 == arg.as_str())
98            {
99                skip = *args_to_skip;
100                skip_and_push = *arg_type == ArgType::Jq;
101                if !skip_and_push {
102                    continue;
103                }
104            } else if args_done || Path::new(&arg).is_file() {
105                files.push(arg);
106                continue;
107            } else if arg == "--" {
108                args_done = true;
109                continue;
110            } else if HELP_ARGS.contains(&arg.as_str()) {
111                help_requested = true;
112            }
113            arguments.push(arg);
114            if skip_and_push && skip == 0 {
115                skip_and_push = false;
116            }
117        }
118        (arguments, files, help_requested)
119    }
120
121    pub fn readers(&self) -> impl Iterator<Item = Box<dyn BufRead>> {
122        let mut file_readers: Vec<Box<dyn BufRead>> = vec![];
123        if self.help_requested {
124            return file_readers.into_iter();
125        }
126        if self.files.is_empty() {
127            file_readers.push(Box::new(stdin_reader()));
128        } else {
129            for file_name in &self.files {
130                let file = match File::open(file_name) {
131                    Ok(file) => file,
132                    Err(e) => {
133                        eprintln!("Error opening file {file_name}: {e}");
134                        exit(Error::FileOpening as i32);
135                    }
136                };
137                file_readers.push(Box::new(BufReader::new(file)));
138            }
139        }
140        file_readers.into_iter()
141    }
142}
143
144impl Default for Jq {
145    fn default() -> Self {
146        let (arguments, files, help_requested) = Self::parse_args();
147        let mut program = "jaq".to_string();
148        let child = match Command::new(&program)
149            .args(&arguments)
150            .stdin(Stdio::piped())
151            .spawn()
152        {
153            Ok(child) => child,
154            Err(e) => {
155                if NotFound == e.kind() {
156                    program = "jq".to_string();
157                    match Command::new(&program)
158                        .args(&arguments)
159                        .stdin(Stdio::piped())
160                        .spawn()
161                    {
162                        Ok(child) => child,
163                        Err(e) => {
164                            eprintln!("Error calling {program}: {e}");
165                            exit(Error::JqCalling as i32);
166                        }
167                    }
168                } else {
169                    eprintln!("Error calling {program}: {e}");
170                    exit(Error::JaqCalling as i32);
171                }
172            }
173        };
174        Self {
175            child,
176            program,
177            files,
178            help_requested,
179        }
180    }
181}
182
183impl Drop for Jq {
184    fn drop(&mut self) {
185        self.wait();
186    }
187}