use crate::{Error, Result};
use serde_json::Value;
use std::io::{self, BufRead, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::thread;
#[derive(Debug, Clone)]
pub struct Jq {
executable: PathBuf,
}
impl Jq {
pub fn new() -> Result<Jq> {
Jq::with_executable("jq")
}
pub fn with_executable<P>(executable: P) -> Result<Jq>
where
P: AsRef<Path>,
{
let executable = executable.as_ref();
let output = Command::new(executable)
.arg("--version")
.output()
.map_err(|err| {
if let io::ErrorKind::NotFound = err.kind() {
Error::new(format!("executable `{}` not found", executable.display()))
} else {
Error::Io(err)
}
})?;
let executable = executable.to_path_buf();
let version = String::from_utf8_lossy(&output.stdout);
if version.starts_with("jq-") {
Ok(Jq { executable })
} else {
Err(Error::new(format!(
"executable `{}` exists but does appear to be `jq`",
executable.display()
)))
}
}
pub fn process(&self, expr: &str, value: &Value) -> Result<Value> {
let mut cmd = self.spawn_cmd(expr)?;
let mut stdin = cmd.stdin.take().unwrap();
let buf = serde_json::to_vec(value)?;
thread::spawn(move || stdin.write_all(&buf));
let output = cmd.wait_with_output()?;
if output.status.success() {
process_output(&output.stdout)
} else {
Err(Error::new(String::from_utf8_lossy(&output.stderr)))
}
}
fn spawn_cmd(&self, expr: &str) -> io::Result<Child> {
Command::new(&self.executable)
.arg("--compact-output")
.arg("--monochrome-output")
.arg(expr)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
}
}
fn process_output(buf: &[u8]) -> Result<Value, Error> {
let mut values = buf
.lines()
.map(|line| serde_json::from_str(&line.unwrap()))
.collect::<Result<Vec<Value>, _>>()?;
if values.len() == 1 {
Ok(values.remove(0))
} else {
Ok(Value::Array(values))
}
}