sheeter-engine 1.2.0

Convert, inspect, and transform data files across ten formats with streaming where it matters and auto-detection where it can.
Documentation
use std::io::Write;
use std::process::{Command, Stdio};

fn pipe(args: &[&str], stdin: &str) -> (String, String, bool) {
    let mut child = Command::new(env!("CARGO_BIN_EXE_sheeter"))
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn");
    child
        .stdin
        .as_mut()
        .unwrap()
        .write_all(stdin.as_bytes())
        .unwrap();
    let out = child.wait_with_output().unwrap();
    (
        String::from_utf8_lossy(&out.stdout).to_string(),
        String::from_utf8_lossy(&out.stderr).to_string(),
        out.status.success(),
    )
}

#[test]
fn csv_stdin_to_json_stdout() {
    let (stdout, _, ok) = pipe(
        &["convert", "--to", "json", "-", "-"],
        "name,age\nAlice,30\n",
    );
    assert!(ok);
    assert_eq!(stdout, r#"[{"name":"Alice","age":"30"}]"#);
}

#[test]
fn csv_stdin_autodetect_to_ndjson() {
    let (stdout, _, ok) = pipe(
        &["convert", "--to", "ndjson", "-", "-"],
        "name,age\nAlice,30\n",
    );
    assert!(ok);
    assert_eq!(stdout, "{\"name\":\"Alice\",\"age\":\"30\"}\n");
}

#[test]
fn ndjson_stdin_to_csv_stdout() {
    let (stdout, _, ok) = pipe(
        &["convert", "--from", "ndjson", "--to", "csv", "-", "-"],
        "{\"a\":1}\n{\"a\":2}\n",
    );
    assert!(ok);
    assert_eq!(stdout, "a\n1\n2\n");
}

#[test]
fn ndjson_filter_via_stdin() {
    let (stdout, _, ok) = pipe(
        &[
            "convert", "--from", "ndjson", "--to", "csv", "--filter", "age > 30", "-", "-",
        ],
        "{\"name\":\"A\",\"age\":25}\n{\"name\":\"B\",\"age\":40}\n",
    );
    assert!(ok);
    assert_eq!(stdout, "name,age\nB,40\n");
}

#[test]
fn ndjson_to_ndjson_passthrough() {
    let (stdout, _, ok) = pipe(
        &["convert", "--from", "ndjson", "--to", "ndjson", "-", "-"],
        "{\"a\":1}\n{\"a\":2}\n",
    );
    assert!(ok);
    assert_eq!(stdout, "{\"a\":1}\n{\"a\":2}\n");
}

#[test]
fn csv_to_ndjson_via_files() {
    let dir = tempfile::tempdir().unwrap();
    let inp = dir.path().join("in.csv");
    let out = dir.path().join("out.ndjson");
    std::fs::write(&inp, "a\n1\n2\n").unwrap();
    let status = Command::new(env!("CARGO_BIN_EXE_sheeter"))
        .args([
            "convert",
            "--from",
            "csv",
            "--to",
            "ndjson",
            inp.to_str().unwrap(),
            out.to_str().unwrap(),
        ])
        .status()
        .unwrap();
    assert!(status.success());
    assert_eq!(
        std::fs::read_to_string(out).unwrap(),
        "{\"a\":\"1\"}\n{\"a\":\"2\"}\n"
    );
}

#[test]
fn detect_subcommand_prints_format() {
    let (stdout, _, ok) = pipe(&["detect", "-"], "[1,2,3]");
    assert!(ok);
    assert_eq!(stdout.trim(), "Json");
}

#[test]
fn detect_ndjson() {
    let (stdout, _, ok) = pipe(&["detect", "-"], "{\"a\":1}\n{\"a\":2}\n");
    assert!(ok);
    assert_eq!(stdout.trim(), "Ndjson");
}