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 sheeter_engine::{external_sort_delimited, SortSpec, Transforms};

fn sort(input: &str, spec: &str, chunk: usize) -> String {
    let mut out = Vec::new();
    external_sort_delimited(
        input.as_bytes(),
        b',',
        &mut out,
        b',',
        &Transforms::default(),
        &SortSpec::parse(spec).unwrap(),
        chunk,
    )
    .unwrap();
    String::from_utf8(out).unwrap()
}

#[test]
fn sort_ascending() {
    let out = sort("n\n3\n1\n2\n", "n", 1000);
    assert_eq!(out, "n\n1\n2\n3\n");
}

#[test]
fn sort_descending() {
    let out = sort("n\n3\n1\n2\n", "n desc", 1000);
    assert_eq!(out, "n\n3\n2\n1\n");
}

#[test]
fn sort_numeric_not_lexical() {
    let out = sort("n\n10\n2\n33\n", "n", 1000);
    assert_eq!(out, "n\n2\n10\n33\n");
}

#[test]
fn sort_multi_key() {
    let input = "a,b\n2,x\n1,y\n2,w\n1,z\n";
    let out = sort(input, "a,b desc", 1000);
    assert_eq!(out, "a,b\n1,z\n1,y\n2,x\n2,w\n");
}

#[test]
fn sort_external_spill() {
    let mut input = String::from("n\n");
    for i in (0..20).rev() {
        input.push_str(&format!("{i}\n"));
    }
    let out = sort(&input, "n", 2);
    let mut expected = String::from("n\n");
    for i in 0..20 {
        expected.push_str(&format!("{i}\n"));
    }
    assert_eq!(out, expected);
}

#[test]
fn sort_with_filter() {
    let t = Transforms {
        filter: Some(sheeter_engine::Filter::parse("n >= 3").unwrap()),
        ..Default::default()
    };
    let mut out = Vec::new();
    external_sort_delimited(
        b"n\n5\n2\n4\n1\n3\n".as_slice(),
        b',',
        &mut out,
        b',',
        &t,
        &SortSpec::parse("n").unwrap(),
        1000,
    )
    .unwrap();
    assert_eq!(String::from_utf8(out).unwrap(), "n\n3\n4\n5\n");
}

#[test]
fn sort_with_dedup() {
    let t = Transforms {
        dedup: Some(Vec::new()),
        ..Default::default()
    };
    let mut out = Vec::new();
    external_sort_delimited(
        b"n\n2\n1\n2\n3\n1\n".as_slice(),
        b',',
        &mut out,
        b',',
        &t,
        &SortSpec::parse("n").unwrap(),
        1000,
    )
    .unwrap();
    assert_eq!(String::from_utf8(out).unwrap(), "n\n1\n2\n3\n");
}

#[test]
fn sort_with_dedup_by_column() {
    let t = Transforms {
        dedup: Some(vec!["email".to_string()]),
        ..Default::default()
    };
    let mut out = Vec::new();
    external_sort_delimited(
        b"email,name\nb@x.com,Bob\na@x.com,Alice\nb@x.com,Robert\n".as_slice(),
        b',',
        &mut out,
        b',',
        &t,
        &SortSpec::parse("email").unwrap(),
        1000,
    )
    .unwrap();
    let result = String::from_utf8(out).unwrap();
    assert_eq!(result.lines().count(), 3, "one duplicate removed: {result}");
    assert!(result.contains("Alice"));
}

#[test]
fn sort_limit_interacts_correctly() {
    let t = Transforms {
        limit: Some(2),
        ..Default::default()
    };
    let mut out = Vec::new();
    external_sort_delimited(
        b"n\n3\n1\n2\n4\n".as_slice(),
        b',',
        &mut out,
        b',',
        &t,
        &SortSpec::parse("n").unwrap(),
        1000,
    )
    .unwrap();
    assert_eq!(String::from_utf8(out).unwrap(), "n\n1\n3\n");
}