sheeter-engine 0.9.0

A CLI tool for converting data files between formats. v0.1.0 supports CSV <-> JSON.
Documentation
use crate::filter::Filter;
use crate::CanonicalValue;

/// Row-level transformations applied between reading and writing.
///
/// Transforms are applied in order: filter → rename → select → limit.
/// All transforms are optional; an empty `Transforms` is a no-op.
#[derive(Debug, Clone, Default)]
pub struct Transforms {
    pub filter: Option<Filter>,
    pub select: Option<Vec<String>>,
    pub rename: Vec<(String, String)>,
    pub limit: Option<usize>,
}

impl Transforms {
    pub fn is_empty(&self) -> bool {
        self.filter.is_none()
            && self.select.is_none()
            && self.rename.is_empty()
            && self.limit.is_none()
    }

    /// Applies transforms to a single row. Returns `None` if the row was
    /// filtered out.
    pub fn apply_to_row(
        &self,
        mut row: Vec<(String, CanonicalValue)>,
    ) -> Option<Vec<(String, CanonicalValue)>> {
        if let Some(f) = &self.filter {
            let wrapped = CanonicalValue::Object(row.clone());
            if !f.matches(&wrapped) {
                return None;
            }
        }
        for (from, to) in &self.rename {
            for (k, _) in row.iter_mut() {
                if k == from {
                    *k = to.clone();
                    break;
                }
            }
        }
        if let Some(sel) = &self.select {
            let mut new_row = Vec::with_capacity(sel.len());
            for name in sel {
                match row.iter().find(|(k, _)| k == name) {
                    Some((k, v)) => new_row.push((k.clone(), v.clone())),
                    None => new_row.push((name.clone(), CanonicalValue::Null)),
                }
            }
            row = new_row;
        }
        Some(row)
    }

    /// Applies transforms to a `CanonicalValue::Array` of objects.
    pub fn apply_to_array(&self, value: &CanonicalValue) -> CanonicalValue {
        let rows = match value {
            CanonicalValue::Array(a) => a,
            _ => return value.clone(),
        };
        let limit = self.limit.unwrap_or(usize::MAX);
        let mut out = Vec::new();
        for row in rows {
            if out.len() >= limit {
                break;
            }
            match row {
                CanonicalValue::Object(o) => {
                    if let Some(new_row) = self.apply_to_row(o.clone()) {
                        out.push(CanonicalValue::Object(new_row));
                    }
                }
                other => out.push(other.clone()),
            }
        }
        CanonicalValue::Array(out)
    }
}