csv_managed/
derive.rs

1use anyhow::{Context, Result, anyhow};
2use evalexpr::{Value as EvalValue, eval_with_context};
3
4use crate::{data::Value, expr};
5
6#[derive(Debug, Clone)]
7pub struct DerivedColumn {
8    pub name: String,
9    pub expression: String,
10}
11
12impl DerivedColumn {
13    pub fn parse(spec: &str) -> Result<Self> {
14        let mut parts = spec.splitn(2, '=');
15        let name = parts
16            .next()
17            .map(|s| s.trim())
18            .filter(|s| !s.is_empty())
19            .ok_or_else(|| anyhow!("Derived column is missing a name"))?;
20        let expression = parts
21            .next()
22            .map(|s| s.trim())
23            .filter(|s| !s.is_empty())
24            .ok_or_else(|| anyhow!("Derived column '{name}' is missing an expression"))?;
25        Ok(DerivedColumn {
26            name: name.to_string(),
27            expression: expression.to_string(),
28        })
29    }
30
31    pub fn evaluate(
32        &self,
33        headers: &[String],
34        raw_row: &[String],
35        typed_row: &[Option<Value>],
36        row_number: Option<usize>,
37    ) -> Result<String> {
38        let context = expr::build_context(headers, raw_row, typed_row, row_number)?;
39
40        let result = eval_with_context(&self.expression, &context)
41            .with_context(|| format!("Evaluating expression for column '{}'", self.name))?;
42        Ok(match result {
43            EvalValue::String(s) => s,
44            EvalValue::Int(i) => i.to_string(),
45            EvalValue::Float(f) => f.to_string(),
46            EvalValue::Boolean(b) => b.to_string(),
47            EvalValue::Tuple(values) => values
48                .into_iter()
49                .map(|v| v.to_string())
50                .collect::<Vec<_>>()
51                .join("|"),
52            EvalValue::Empty => String::new(),
53        })
54    }
55}
56
57pub fn parse_derived_columns(specs: &[String]) -> Result<Vec<DerivedColumn>> {
58    specs
59        .iter()
60        .map(|spec| DerivedColumn::parse(spec))
61        .collect()
62}