1use crate::{Error, Result};
4use serde_json::Value;
5use std::io::{self, BufRead, Write};
6use std::path::{Path, PathBuf};
7use std::process::{Child, Command, Stdio};
8use std::thread;
9
10#[derive(Debug, Clone)]
32pub struct Jq {
33 executable: PathBuf,
34}
35
36impl Jq {
37 pub fn new() -> Result<Jq> {
43 Jq::with_executable("jq")
44 }
45
46 pub fn with_executable<P>(executable: P) -> Result<Jq>
53 where
54 P: AsRef<Path>,
55 {
56 let executable = executable.as_ref();
57
58 let output = Command::new(executable)
59 .arg("--version")
60 .output()
61 .map_err(|err| {
62 if let io::ErrorKind::NotFound = err.kind() {
63 Error::new(format!("executable `{}` not found", executable.display()))
64 } else {
65 Error::Io(err)
66 }
67 })?;
68
69 let executable = executable.to_path_buf();
70 let version = String::from_utf8_lossy(&output.stdout);
71
72 if version.starts_with("jq-") {
73 Ok(Jq { executable })
74 } else {
75 Err(Error::new(format!(
76 "executable `{}` exists but does appear to be `jq`",
77 executable.display()
78 )))
79 }
80 }
81
82 pub fn process(&self, expr: &str, value: &Value) -> Result<Value> {
90 let mut cmd = self.spawn_cmd(expr)?;
91 let mut stdin = cmd.stdin.take().unwrap();
92
93 let buf = serde_json::to_vec(value)?;
94
95 thread::spawn(move || stdin.write_all(&buf));
96
97 let output = cmd.wait_with_output()?;
98
99 if output.status.success() {
100 process_output(&output.stdout)
101 } else {
102 Err(Error::new(String::from_utf8_lossy(&output.stderr)))
103 }
104 }
105
106 fn spawn_cmd(&self, expr: &str) -> io::Result<Child> {
107 Command::new(&self.executable)
108 .arg("--compact-output")
109 .arg("--monochrome-output")
110 .arg(expr)
111 .stdin(Stdio::piped())
112 .stdout(Stdio::piped())
113 .stderr(Stdio::piped())
114 .spawn()
115 }
116}
117
118fn process_output(buf: &[u8]) -> Result<Value, Error> {
119 let mut values = buf
120 .lines()
121 .map(|line| serde_json::from_str(&line.unwrap()))
122 .collect::<Result<Vec<Value>, _>>()?;
123
124 if values.len() == 1 {
125 Ok(values.remove(0))
126 } else {
127 Ok(Value::Array(values))
128 }
129}