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}