use crate::ast::{Node, VariableValue, Variables};
use crate::{Result, Row, Runtime};
use serde::{Deserialize, Serialize};
use std::collections;
mod display;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Spreadsheet {
pub rows: Vec<Row>,
}
impl Spreadsheet {
pub fn parse(runtime: &Runtime) -> Result<Spreadsheet> {
let mut csv_reader = Self::csv_reader(runtime);
let mut rows: Vec<Row> = vec![];
for (row_index, result) in csv_reader.records().enumerate() {
let row = Row::parse(result, row_index, runtime)?;
rows.push(row);
}
Ok(Spreadsheet { rows })
}
pub fn variables(&self) -> Variables {
let mut vars = collections::HashMap::new();
for row in &self.rows {
if let Some(var_id) = &row.modifier.var {
let reference = if let Some(scope) = row.modifier.expand {
Node::Variable {
name: var_id.clone(),
value: VariableValue::RowRelative {
scope,
row: row.row,
},
}
} else {
Node::Variable {
name: var_id.clone(),
value: VariableValue::Row(row.row),
}
};
vars.insert(var_id.to_owned(), Box::new(reference));
};
row.cells.iter().for_each(|c| {
if let Some(var_id) = &c.modifier.var {
let reference = if let Some(scope) = row.modifier.expand {
Node::Variable {
name: var_id.clone(),
value: VariableValue::ColumnRelative {
scope,
column: c.position.column,
},
}
} else {
Node::Variable {
name: var_id.clone(),
value: VariableValue::Absolute(c.position),
}
};
vars.insert(var_id.to_owned(), Box::new(reference));
}
});
}
vars
}
fn csv_reader(runtime: &Runtime) -> csv::Reader<&[u8]> {
csv::ReaderBuilder::new()
.has_headers(false)
.flexible(true)
.trim(csv::Trim::All)
.from_reader(runtime.source_code.csv_section.as_bytes())
}
pub fn widest_row(&self) -> usize {
self.rows
.iter()
.map(|row| row.cells.len())
.max()
.unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::modifier::TextFormat;
use crate::test_utils::*;
use crate::*;
use a1_notation::Address;
fn build_runtime(input: &str) -> Runtime {
TestFile::new("csv", input).into()
}
#[test]
fn parse_simple() {
let runtime = build_runtime("foo,bar,baz\n1,2,3\n");
let spreadsheet = Spreadsheet::parse(&runtime).unwrap();
assert_eq!(spreadsheet.rows.len(), 2);
assert_eq!(spreadsheet.rows[0].cells.len(), 3);
assert_eq!(spreadsheet.rows[1].cells.len(), 3);
assert_eq!(spreadsheet.rows[0].cells[0].position.to_string(), "A1");
assert_eq!(spreadsheet.rows[0].cells[1].position.to_string(), "B1");
assert_eq!(spreadsheet.rows[0].cells[2].position.to_string(), "C1");
assert_eq!(spreadsheet.rows[1].cells[0].position.to_string(), "A2");
assert_eq!(spreadsheet.rows[1].cells[1].position.to_string(), "B2");
assert_eq!(spreadsheet.rows[1].cells[2].position.to_string(), "C2");
assert_eq!(spreadsheet.rows[0].cells[0].value, "foo");
assert_eq!(spreadsheet.rows[0].cells[1].value, "bar");
assert_eq!(spreadsheet.rows[0].cells[2].value, "baz");
assert_eq!(spreadsheet.rows[1].cells[0].value, "1");
assert_eq!(spreadsheet.rows[1].cells[1].value, "2");
assert_eq!(spreadsheet.rows[1].cells[2].value, "3");
assert!(spreadsheet.rows[0].cells[0].ast.is_none());
assert!(spreadsheet.rows[0].cells[1].ast.is_none());
assert!(spreadsheet.rows[0].cells[2].ast.is_none());
assert!(spreadsheet.rows[1].cells[0].ast.is_none());
assert!(spreadsheet.rows[1].cells[1].ast.is_none());
assert!(spreadsheet.rows[1].cells[2].ast.is_none());
}
#[test]
fn parse_with_asts() {
let runtime = build_runtime("=1,=2 * 3,=foo\n");
let spreadsheet = Spreadsheet::parse(&runtime).unwrap();
assert!(spreadsheet.rows[0].cells[0].ast.is_some());
assert!(spreadsheet.rows[0].cells[1].ast.is_some());
assert!(spreadsheet.rows[0].cells[2].ast.is_some());
}
#[test]
fn parse_trim_spaces() {
let runtime = build_runtime(" foo , bar\n");
let spreadsheet = Spreadsheet::parse(&runtime).unwrap();
assert_eq!(spreadsheet.rows[0].cells[0].value, "foo");
assert_eq!(spreadsheet.rows[0].cells[1].value, "bar");
}
#[test]
fn parse_with_modifiers() {
let runtime = build_runtime("[[f=b / fs=20]]foo");
let spreadsheet = Spreadsheet::parse(&runtime).unwrap();
assert!(spreadsheet.rows[0].cells[0]
.modifier
.formats
.contains(&TextFormat::Bold));
assert_eq!(spreadsheet.rows[0].cells[0].modifier.font_size, Some(20))
}
#[test]
fn parse_with_row_modifier() {
let runtime = build_runtime("![[f=b]]foo,bar,baz");
let spreadsheet = Spreadsheet::parse(&runtime).unwrap();
assert!(spreadsheet.rows[0].cells[0]
.modifier
.formats
.contains(&TextFormat::Bold));
assert!(spreadsheet.rows[0].cells[1]
.modifier
.formats
.contains(&TextFormat::Bold));
assert!(spreadsheet.rows[0].cells[2]
.modifier
.formats
.contains(&TextFormat::Bold));
}
#[test]
fn variables_unscoped() {
let spreadsheet = Spreadsheet {
rows: vec![Row {
row: 0.into(),
modifier: RowModifier::default(),
cells: vec![
Cell {
ast: None,
position: Address::new(0, 0),
modifier: Modifier {
var: Some("foo".to_string()),
..Default::default()
},
value: "".to_string(),
},
Cell {
ast: None,
position: Address::new(1, 1),
modifier: Modifier {
var: Some("bar".to_string()),
..Default::default()
},
value: "".to_string(),
},
],
}],
};
let variables = spreadsheet.variables();
assert_eq!(
**variables.get("foo").unwrap(),
Node::var("foo", VariableValue::Absolute(Address::new(0, 0)))
);
assert_eq!(
**variables.get("bar").unwrap(),
Node::var("bar", VariableValue::Absolute(Address::new(1, 1)))
);
}
#[test]
fn variables_with_scope() {
let spreadsheet = Spreadsheet {
rows: vec![
Row {
row: 0.into(),
modifier: RowModifier {
expand: Some(Expand::new(0, Some(10))),
..Default::default()
},
cells: vec![Cell {
ast: None,
position: (0, 0).into(),
modifier: Modifier {
var: Some("foo".to_string()),
..Default::default()
},
value: "".to_string(),
}],
},
Row {
row: 1.into(),
modifier: RowModifier {
expand: Some(Expand::new(10, Some(100))),
..Default::default()
},
cells: vec![Cell {
ast: None,
position: (1, 1).into(),
modifier: Modifier {
var: Some("bar".to_string()),
..Default::default()
},
value: "".to_string(),
}],
},
],
};
let variables = spreadsheet.variables();
assert_eq!(
**variables.get("foo").unwrap(),
Node::var(
"foo",
VariableValue::ColumnRelative {
scope: Expand {
amount: Some(10),
start_row: 0.into()
},
column: 0.into(),
}
)
);
assert_eq!(
**variables.get("bar").unwrap(),
Node::var(
"bar",
VariableValue::ColumnRelative {
scope: Expand {
amount: Some(100),
start_row: 10.into()
},
column: 1.into(),
}
)
);
}
#[test]
fn widest_row() {
let cell = Cell {
ast: None,
position: Address::new(0, 0),
modifier: Modifier::default(),
value: "foo".to_string(),
};
let spreadsheet = Spreadsheet {
rows: vec![
Row {
cells: vec![cell.clone()],
row: 0.into(),
modifier: RowModifier::default(),
},
Row {
cells: vec![cell.clone(), cell.clone()],
row: 1.into(),
modifier: RowModifier::default(),
},
Row {
cells: vec![cell.clone(), cell.clone(), cell.clone()],
row: 2.into(),
modifier: RowModifier::default(),
},
],
};
assert_eq!(spreadsheet.widest_row(), 3);
}
}