sheeter-engine 1.0.0

Convert, inspect, and transform data files across ten formats with streaming where it matters and auto-detection where it can.
Documentation
use sheeter_engine::Transforms;

fn stream_csv(input: &str, transforms: &Transforms) -> String {
    let mut out = Vec::new();
    sheeter_engine::stream_delimited(input.as_bytes(), b',', &mut out, b',', transforms).unwrap();
    String::from_utf8(out).unwrap()
}

#[test]
fn filter_numeric() {
    let t = Transforms {
        filter: Some(sheeter_engine::Filter::parse("age > 30").unwrap()),
        ..Default::default()
    };
    let out = stream_csv("name,age\nAlice,25\nBob,40\nCarol,30\n", &t);
    assert_eq!(out, "name,age\nBob,40\n");
}

#[test]
fn filter_string() {
    let t = Transforms {
        filter: Some(sheeter_engine::Filter::parse("city == 'SP'").unwrap()),
        ..Default::default()
    };
    let out = stream_csv("name,city\nAlice,SP\nBob,RJ\n", &t);
    assert_eq!(out, "name,city\nAlice,SP\n");
}

#[test]
fn filter_and_conjunction() {
    let t = Transforms {
        filter: Some(sheeter_engine::Filter::parse("age > 20 and age < 50").unwrap()),
        ..Default::default()
    };
    let out = stream_csv("name,age\nA,15\nB,30\nC,60\n", &t);
    assert_eq!(out, "name,age\nB,30\n");
}

#[test]
fn select_columns() {
    let t = Transforms {
        select: Some(vec!["name".to_string()]),
        ..Default::default()
    };
    let out = stream_csv("name,age,city\nAlice,30,SP\n", &t);
    assert_eq!(out, "name\nAlice\n");
}

#[test]
fn rename_columns() {
    let t = Transforms {
        rename: vec![("name".to_string(), "who".to_string())],
        ..Default::default()
    };
    let out = stream_csv("name,age\nAlice,30\n", &t);
    assert_eq!(out, "who,age\nAlice,30\n");
}

#[test]
fn limit_rows() {
    let t = Transforms {
        limit: Some(2),
        ..Default::default()
    };
    let out = stream_csv("a\n1\n2\n3\n4\n", &t);
    assert_eq!(out, "a\n1\n2\n");
}

#[test]
fn combined_transforms() {
    let t = Transforms {
        filter: Some(sheeter_engine::Filter::parse("age >= 30").unwrap()),
        select: Some(vec!["name".to_string()]),
        limit: Some(2),
        ..Default::default()
    };
    let out = stream_csv("name,age\nA,20\nB,35\nC,40\nD,50\n", &t);
    assert_eq!(out, "name\nB\nC\n");
}

#[test]
fn no_op_returns_input() {
    let t = Transforms::default();
    let out = stream_csv("a,b\n1,2\n", &t);
    assert_eq!(out, "a,b\n1,2\n");
}

#[test]
fn buffer_path_with_transforms() {
    // JSON → JSON via buffer, apply filter
    let json = r#"[{"a":1},{"a":2},{"a":3}]"#;
    let t = Transforms {
        filter: Some(sheeter_engine::Filter::parse("a >= 2").unwrap()),
        ..Default::default()
    };
    let hub = sheeter_engine::CanonicalValue::from_json(json).unwrap();
    let filtered = t.apply_to_array(&hub);
    assert_eq!(filtered.to_json().unwrap(), r#"[{"a":2},{"a":3}]"#);
}

#[test]
fn dedup_whole_row_streaming() {
    let t = Transforms {
        dedup: Some(Vec::new()),
        ..Default::default()
    };
    let mut out = Vec::new();
    sheeter_engine::stream_delimited(
        b"a,b\n1,x\n1,x\n2,y\n1,x\n".as_slice(),
        b',',
        &mut out,
        b',',
        &t,
    )
    .unwrap();
    assert_eq!(String::from_utf8(out).unwrap(), "a,b\n1,x\n2,y\n");
}

#[test]
fn dedup_by_column_streaming() {
    let t = Transforms {
        dedup: Some(vec!["a".to_string()]),
        ..Default::default()
    };
    let mut out = Vec::new();
    sheeter_engine::stream_delimited(b"a,b\n1,x\n1,y\n2,z\n".as_slice(), b',', &mut out, b',', &t)
        .unwrap();
    assert_eq!(String::from_utf8(out).unwrap(), "a,b\n1,x\n2,z\n");
}