sheeter-engine 1.5.0

Convert, inspect, and transform data files across eleven formats with streaming where it matters and auto-detection where it can.
Documentation
use assert_cmd::Command;
use std::fs;
use tempfile::tempdir;

fn run(dir: &std::path::Path, args: &[&str]) -> Result<String, String> {
    let output = Command::cargo_bin("sheeter")
        .unwrap()
        .current_dir(dir)
        .args(args)
        .output()
        .unwrap();
    if !output.status.success() {
        return Err(String::from_utf8_lossy(&output.stderr).to_string());
    }
    Ok(String::from_utf8(output.stdout).unwrap())
}

fn read_json(path: &std::path::Path) -> serde_json::Value {
    serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap()
}

#[test]
fn concat_two_objects() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("a.json"), r#"{"x":1}"#).unwrap();
    fs::write(dir.path().join("b.json"), r#"{"y":2}"#).unwrap();
    run(
        dir.path(),
        &["json:concat", "a.json", "b.json", "-o", "out.json"],
    )
    .unwrap();
    let parsed = read_json(&dir.path().join("out.json"));
    assert_eq!(parsed["a"]["x"], 1);
    assert_eq!(parsed["b"]["y"], 2);
}

#[test]
fn concat_preserves_arrays_and_scalars() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("arr.json"), r#"[1,2,3]"#).unwrap();
    fs::write(dir.path().join("scalar.json"), r#"42"#).unwrap();
    run(
        dir.path(),
        &["json:concat", "arr.json", "scalar.json", "-o", "out.json"],
    )
    .unwrap();
    let parsed = read_json(&dir.path().join("out.json"));
    assert_eq!(parsed["arr"], serde_json::json!([1, 2, 3]));
    assert_eq!(parsed["scalar"], 42);
}

#[test]
fn concat_collision_appends_suffix() {
    let dir = tempdir().unwrap();
    fs::create_dir_all(dir.path().join("sub")).unwrap();
    fs::write(dir.path().join("a.json"), r#"{"from":"root"}"#).unwrap();
    fs::write(dir.path().join("sub/a.json"), r#"{"from":"sub"}"#).unwrap();
    run(
        dir.path(),
        &["json:concat", "a.json", "sub/a.json", "-o", "out.json"],
    )
    .unwrap();
    let parsed = read_json(&dir.path().join("out.json"));
    assert_eq!(parsed["a"]["from"], "root");
    assert_eq!(parsed["a_2"]["from"], "sub");
}

#[test]
fn append_to_existing_object() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("base.json"), r#"{"keep":true}"#).unwrap();
    fs::write(dir.path().join("a.json"), r#"{"x":1}"#).unwrap();
    fs::write(dir.path().join("b.json"), r#"{"y":2}"#).unwrap();
    run(
        dir.path(),
        &["json:append", "base.json", "a.json", "b.json"],
    )
    .unwrap();
    let parsed = read_json(&dir.path().join("base.json"));
    assert_eq!(parsed["keep"], true);
    assert_eq!(parsed["a"]["x"], 1);
    assert_eq!(parsed["b"]["y"], 2);
}

#[test]
fn append_creates_missing_target() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("a.json"), r#"{"x":1}"#).unwrap();
    run(dir.path(), &["json:append", "new.json", "a.json"]).unwrap();
    let parsed = read_json(&dir.path().join("new.json"));
    assert_eq!(parsed["a"]["x"], 1);
}

#[test]
fn append_overwrites_existing_key() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("base.json"), r#"{"a":{"old":true}}"#).unwrap();
    fs::write(dir.path().join("a.json"), r#"{"new":1}"#).unwrap();
    run(dir.path(), &["json:append", "base.json", "a.json"]).unwrap();
    let parsed = read_json(&dir.path().join("base.json"));
    assert_eq!(parsed["a"]["new"], 1);
    assert!(parsed["a"].get("old").is_none());
}

#[test]
fn append_with_output_flag_does_not_touch_source() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("base.json"), r#"{"keep":true}"#).unwrap();
    fs::write(dir.path().join("a.json"), r#"{"x":1}"#).unwrap();
    run(
        dir.path(),
        &["json:append", "base.json", "a.json", "-o", "out.json"],
    )
    .unwrap();
    let base = read_json(&dir.path().join("base.json"));
    assert!(base.get("a").is_none());
    let out = read_json(&dir.path().join("out.json"));
    assert_eq!(out["a"]["x"], 1);
}

#[test]
fn append_rejects_non_object_target() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("base.json"), r#"[1,2,3]"#).unwrap();
    fs::write(dir.path().join("a.json"), r#"{"x":1}"#).unwrap();
    let err = run(dir.path(), &["json:append", "base.json", "a.json"]).unwrap_err();
    assert!(err.contains("not a JSON object") || err.contains("object"));
}

#[test]
fn frag_level_zero_writes_root_children() {
    let dir = tempdir().unwrap();
    fs::write(
        dir.path().join("in.json"),
        r#"{"ObjetoA":{"id":1},"ObjetoB":{"id":2}}"#,
    )
    .unwrap();
    run(dir.path(), &["json:frag", "in.json", "--level", "0"]).unwrap();
    assert!(dir.path().join("objetoa.json").exists());
    assert!(dir.path().join("objetob.json").exists());
    let a = read_json(&dir.path().join("objetoa.json"));
    assert_eq!(a["id"], 1);
}

#[test]
fn frag_level_one_descends_one_into_root() {
    let dir = tempdir().unwrap();
    fs::write(
        dir.path().join("in.json"),
        r#"{"nivel_1":{"ObjetoA":{"id":1},"ObjetoB":{"id":2}}}"#,
    )
    .unwrap();
    run(dir.path(), &["json:frag", "in.json", "--level", "1"]).unwrap();
    assert!(dir.path().join("objetoa.json").exists());
    assert!(dir.path().join("objetob.json").exists());
    let a = read_json(&dir.path().join("objetoa.json"));
    assert_eq!(a["id"], 1);
}

#[test]
fn frag_level_two_descends_two_into_root() {
    let dir = tempdir().unwrap();
    fs::write(
        dir.path().join("in.json"),
        r#"{"l1":{"l2":{"a":{"v":1},"b":{"v":2}}}}"#,
    )
    .unwrap();
    run(dir.path(), &["json:frag", "in.json", "--level", "2"]).unwrap();
    assert!(dir.path().join("a.json").exists());
    assert!(dir.path().join("b.json").exists());
}

#[test]
fn frag_sanitizes_names() {
    let dir = tempdir().unwrap();
    fs::write(
        dir.path().join("in.json"),
        r#"{"Relatório (2024)":{"x":1}}"#,
    )
    .unwrap();
    run(dir.path(), &["json:frag", "in.json"]).unwrap();
    assert!(dir.path().join("relatorio_2024.json").exists());
}

#[test]
fn frag_out_dir() {
    let dir = tempdir().unwrap();
    fs::write(dir.path().join("in.json"), r#"{"a":{"x":1}}"#).unwrap();
    let out = dir.path().join("sub");
    run(
        dir.path(),
        &["json:frag", "in.json", "--out", out.to_str().unwrap()],
    )
    .unwrap();
    assert!(out.join("a.json").exists());
}

#[test]
fn frag_handles_array_root() {
    let dir = tempdir().unwrap();
    fs::write(
        dir.path().join("in.json"),
        r#"[{"id":1},{"id":2},{"id":3}]"#,
    )
    .unwrap();
    run(dir.path(), &["json:frag", "in.json", "--level", "0"]).unwrap();
    assert!(dir.path().join("0.json").exists());
    assert!(dir.path().join("1.json").exists());
    assert!(dir.path().join("2.json").exists());
}