convert2json 2.2.1

CLI utilities to convert BSON, CBOR, CSV, INI, MessagePack, Plist, RSV, TOML, XML & YAML into JSON and for use with jaq or jq.
Documentation
#![cfg(any(
    feature = "bsonq",
    feature = "cborq",
    feature = "cq",
    feature = "iq",
    feature = "msgq",
    feature = "plistq",
    feature = "tq",
    feature = "xq",
    feature = "yq"
))]
use super::{exit, stdin_reader, Error, HELP_ARGS};
use serde::Serialize;
use std::env::args;
use std::fs::File;
use std::io::{BufRead, BufReader, ErrorKind::NotFound};
use std::path::Path;
use std::process::{Child, Command, Stdio};

pub struct Jq {
    child: Child,
    program: String,
    files: Vec<String>,
    help_requested: bool,
}

impl Jq {
    fn is_jq(&mut self) -> bool {
        self.program == "jq"
    }

    pub fn write<T>(&mut self, input: T)
    where
        T: Sized + Serialize,
    {
        let stdin = self.child.stdin.as_mut();
        if stdin.is_none() {
            eprintln!("Error opening {}'s STDIN for writing", self.program);
            self.wait();
            exit(match self.is_jq() {
                true => Error::JqPiping,
                false => Error::JaqPiping,
            } as i32);
        }
        if let Err(e) = &serde_json::to_writer(stdin.unwrap(), &input) {
            eprintln!("Error serializing output: {e}");
            self.wait();
            exit(Error::OutputSerialization as i32);
        };
    }

    fn wait(&mut self) {
        if let Err(e) = self.child.wait() {
            eprintln!("Error waiting on {}: {e}", self.program);
            exit(match self.is_jq() {
                true => Error::JqWaiting,
                false => Error::JaqWaiting,
            } as i32);
        }
    }

    fn parse_args() -> (Vec<String>, Vec<String>, bool) {
        #[derive(PartialEq)]
        enum ArgType {
            Csv,
            Jq,
        }
        let mut arguments: Vec<String> = vec![];
        let mut files: Vec<String> = vec![];
        let mut args_done = false;
        let mut help_requested = false;
        let mut skip: u8 = 0;
        let skip_args = [
            ("--no-trim", 0, ArgType::Csv),
            ("-d", 1, ArgType::Csv),
            ("--delimiter", 1, ArgType::Csv),
            ("-q", 1, ArgType::Csv),
            ("--quote", 1, ArgType::Csv),
            ("-E", 1, ArgType::Csv),
            ("--escape", 1, ArgType::Csv),
            ("-f", 1, ArgType::Jq),
            ("--from-file", 1, ArgType::Jq),
            ("--run-tests", 1, ArgType::Jq),
            ("--slurpfile", 2, ArgType::Jq),
            ("--rawfile", 2, ArgType::Jq),
        ];
        let mut skip_and_push = false;
        for arg in args().skip(1) {
            if skip > 0 {
                skip -= 1;
                if !skip_and_push {
                    continue;
                }
            } else if let Some((_, args_to_skip, arg_type)) =
                skip_args.iter().find(|&item| item.0 == arg.as_str())
            {
                skip = *args_to_skip;
                skip_and_push = *arg_type == ArgType::Jq;
                if !skip_and_push {
                    continue;
                }
            } else if args_done || Path::new(&arg).is_file() {
                files.push(arg);
                continue;
            } else if arg == "--" {
                args_done = true;
                continue;
            } else if HELP_ARGS.contains(&arg.as_str()) {
                help_requested = true;
            }
            arguments.push(arg);
            if skip_and_push && skip == 0 {
                skip_and_push = false;
            }
        }
        (arguments, files, help_requested)
    }

    pub fn readers(&self) -> impl Iterator<Item = Box<dyn BufRead>> {
        let mut file_readers: Vec<Box<dyn BufRead>> = vec![];
        if self.help_requested {
            return file_readers.into_iter();
        }
        if self.files.is_empty() {
            file_readers.push(Box::new(stdin_reader()));
        } else {
            for file_name in &self.files {
                let file = match File::open(file_name) {
                    Ok(file) => file,
                    Err(e) => {
                        eprintln!("Error opening file {file_name}: {e}");
                        exit(Error::FileOpening as i32);
                    }
                };
                file_readers.push(Box::new(BufReader::new(file)))
            }
        }
        file_readers.into_iter()
    }
}

impl Default for Jq {
    fn default() -> Self {
        let (arguments, files, help_requested) = Self::parse_args();
        let mut program = "jaq".to_string();
        let child = match Command::new(&program)
            .args(&arguments)
            .stdin(Stdio::piped())
            .spawn()
        {
            Ok(child) => child,
            Err(e) => {
                if NotFound == e.kind() {
                    program = "jq".to_string();
                    match Command::new(&program)
                        .args(&arguments)
                        .stdin(Stdio::piped())
                        .spawn()
                    {
                        Ok(child) => child,
                        Err(e) => {
                            eprintln!("Error calling {program}: {e}");
                            exit(Error::JqCalling as i32);
                        }
                    }
                } else {
                    eprintln!("Error calling {program}: {e}");
                    exit(Error::JaqCalling as i32);
                }
            }
        };
        Self {
            child,
            program,
            files,
            help_requested,
        }
    }
}

impl Drop for Jq {
    fn drop(&mut self) {
        self.wait();
    }
}