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}