use std::collections::HashMap;
use crate::error::{Error, Result};
use crate::types::LigoType;
use crate::value::Value;
#[derive(Debug, Clone)]
pub struct Document {
pub root: LigoLwElement,
pub tables: HashMap<String, Table>,
pub params: HashMap<String, Param>,
pub arrays: HashMap<String, Array>,
pub times: HashMap<String, Time>,
}
impl Document {
pub(crate) fn from_root(root: LigoLwElement) -> Self {
let mut tables = HashMap::new();
let mut params = HashMap::new();
let mut arrays = HashMap::new();
let mut times = HashMap::new();
index_into(&root, &mut tables, &mut params, &mut arrays, &mut times);
Self {
root,
tables,
params,
arrays,
times,
}
}
pub fn table(&self, name: &str) -> Option<&Table> {
self.tables.get(name)
}
pub fn require_table(&self, name: &str) -> Result<&Table> {
self.tables
.get(name)
.ok_or_else(|| Error::MissingTable(name.to_string()))
}
pub fn array(&self, name: &str) -> Option<&Array> {
self.arrays.get(name)
}
pub fn time(&self, name: &str) -> Option<&Time> {
self.times.get(name)
}
}
fn index_into(
elem: &LigoLwElement,
tables: &mut HashMap<String, Table>,
params: &mut HashMap<String, Param>,
arrays: &mut HashMap<String, Array>,
times: &mut HashMap<String, Time>,
) {
for child in &elem.children {
match child {
Child::LigoLw(nested) => index_into(nested, tables, params, arrays, times),
Child::Table(t) => {
tables.insert(t.name.clone(), t.clone());
}
Child::Param(p) => {
params.insert(p.name.clone(), p.clone());
}
Child::Array(a) => {
if let Some(n) = &a.name {
arrays.insert(n.clone(), a.clone());
}
}
Child::Time(t) => {
if let Some(n) = &t.name {
times.insert(n.clone(), t.clone());
}
}
Child::Comment(_) => {}
Child::Other(g) => {
index_generic(g, tables, params, arrays, times);
}
}
}
}
fn index_generic(
elem: &GenericElement,
tables: &mut HashMap<String, Table>,
params: &mut HashMap<String, Param>,
arrays: &mut HashMap<String, Array>,
times: &mut HashMap<String, Time>,
) {
for child in &elem.children {
match child {
Child::LigoLw(nested) => index_into(nested, tables, params, arrays, times),
Child::Other(g) => index_generic(g, tables, params, arrays, times),
Child::Table(t) => {
tables.insert(t.name.clone(), t.clone());
}
Child::Param(p) => {
params.insert(p.name.clone(), p.clone());
}
Child::Array(a) => {
if let Some(n) = &a.name {
arrays.insert(n.clone(), a.clone());
}
}
Child::Time(t) => {
if let Some(n) = &t.name {
times.insert(n.clone(), t.clone());
}
}
Child::Comment(_) => {}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LigoLwElement {
pub name: Option<String>,
pub element_type: Option<String>,
pub children: Vec<Child>,
}
impl LigoLwElement {
pub fn tables(&self) -> impl Iterator<Item = &Table> {
self.children.iter().filter_map(|c| match c {
Child::Table(t) => Some(t),
_ => None,
})
}
pub fn arrays(&self) -> impl Iterator<Item = &Array> {
self.children.iter().filter_map(|c| match c {
Child::Array(a) => Some(a),
_ => None,
})
}
pub fn params(&self) -> impl Iterator<Item = &Param> {
self.children.iter().filter_map(|c| match c {
Child::Param(p) => Some(p),
_ => None,
})
}
}
#[derive(Debug, Clone)]
pub enum Child {
LigoLw(LigoLwElement),
Table(Table),
Param(Param),
Array(Array),
Time(Time),
Comment(String),
Other(GenericElement),
}
#[derive(Debug, Clone, Default)]
pub struct GenericElement {
pub tag: String,
pub name: Option<String>,
pub element_type: Option<String>,
pub attributes: Vec<(String, String)>,
pub children: Vec<Child>,
pub text: String,
}
#[derive(Debug, Clone)]
pub struct Table {
pub name: String,
pub delimiter: char,
pub columns: Vec<Column>,
pub rows: Vec<Vec<Value>>,
}
impl Table {
pub fn column_index(&self, name: &str) -> Option<usize> {
self.columns.iter().position(|c| c.name == name)
}
pub fn cell(&self, row: usize, column: &str) -> Option<&Value> {
let idx = self.column_index(column)?;
self.rows.get(row)?.get(idx)
}
pub fn require_cell(&self, row: usize, column: &str) -> Result<&Value> {
let idx = self
.column_index(column)
.ok_or_else(|| Error::MissingColumn {
table: self.name.clone(),
column: column.to_string(),
})?;
Ok(&self.rows[row][idx])
}
}
#[derive(Debug, Clone)]
pub struct Column {
pub name: String,
pub ty: LigoType,
}
#[derive(Debug, Clone)]
pub struct Param {
pub name: String,
pub ty: LigoType,
pub unit: Option<String>,
pub raw: String,
}
#[derive(Debug, Clone)]
pub struct Time {
pub name: Option<String>,
pub time_type: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct Dim {
pub name: Option<String>,
pub size: usize,
pub scale: Option<f64>,
pub start: Option<f64>,
pub unit: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Array {
pub name: Option<String>,
pub ty: LigoType,
pub unit: Option<String>,
pub dims: Vec<Dim>,
pub encoding: ArrayEncoding,
pub delimiter: char,
pub values: Vec<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrayEncoding {
Text,
LittleEndianBase64,
BigEndianBase64,
}
impl ArrayEncoding {
pub fn parse(s: &str) -> Self {
let lower = s.trim().to_ascii_lowercase();
if lower.contains("base64") {
if lower.contains("bigendian") {
Self::BigEndianBase64
} else {
Self::LittleEndianBase64
}
} else {
Self::Text
}
}
pub fn as_attribute(self) -> Option<&'static str> {
match self {
Self::Text => None,
Self::LittleEndianBase64 => Some("LittleEndian,base64"),
Self::BigEndianBase64 => Some("BigEndian,base64"),
}
}
}
impl Array {
pub fn expected_len(&self) -> usize {
if self.dims.is_empty() {
self.values.len()
} else {
self.dims.iter().map(|d| d.size).product()
}
}
}