convert2json 1.1.3

CLI utilities to convert CSV, INI, RSV, TOML, XML & YAML into JSON and for use with jaq or jq.
Documentation
#![cfg(any(
    feature = "cq",
    feature = "iq",
    feature = "tq",
    feature = "xq",
    feature = "yq"
))]
use super::{exit, stdin_reader, Error};
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,
}

impl Jq {
    pub fn new(arguments: &Vec<String>) -> Self {
        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 }
    }

    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);
        }
    }
}

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

pub fn parse_args() -> (Vec<String>, Vec<String>) {
    #[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 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;
        }
        arguments.push(arg);
        if skip_and_push && skip == 0 {
            skip_and_push = false;
        }
    }
    (arguments, files)
}

pub fn readers(files: &Vec<String>) -> Vec<Box<dyn BufRead>> {
    if !files.is_empty() {
        let mut file_readers: Vec<Box<dyn BufRead>> = vec![];
        for file_name in 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
    } else {
        vec![Box::new(stdin_reader())]
    }
}