use vantage_core::{Result, error};
use vantage_table::column::core::Column as TableColumn;
use vantage_table::table::{BuildEnv, Table, TableInjector, VistaMetadataOptions};
use vantage_types::{EmptyEntity, Entity};
use vantage_vista::{Capability, NoExtras, Vista, VistaCapabilities, VistaFactory};
use crate::csv::Csv;
use crate::type_system::AnyCsvType;
use crate::vista::source::CsvTableShell;
use crate::vista::spec::{CsvColumnExtras, CsvTableExtras, CsvVistaSpec};
pub struct CsvVistaFactory {
csv: Csv,
injector: Option<TableInjector<Csv>>,
}
impl CsvVistaFactory {
pub fn new(csv: Csv) -> Self {
Self {
csv,
injector: None,
}
}
pub fn with_injector(mut self, injector: TableInjector<Csv>) -> Self {
self.injector = Some(injector);
self
}
pub fn from_table<E>(&self, table: Table<Csv, E>) -> Result<Vista>
where
E: Entity<AnyCsvType> + 'static,
{
let metadata = table.vista_metadata(VistaMetadataOptions {
references: true,
..VistaMetadataOptions::default()
});
let name = table.table_name().to_string();
let any_table = table.into_entity::<EmptyEntity>();
let shell = CsvTableShell::new(any_table, csv_capabilities(), metadata);
Ok(Vista::new(name, shell))
}
pub fn table_from_spec(&self, spec: &CsvVistaSpec) -> Result<Table<Csv, EmptyEntity>> {
let env = BuildEnv::root(&spec.name, None, self.injector.clone());
build_csv_table(spec, self.csv.clone(), &env)
}
}
fn build_csv_table(
spec: &CsvVistaSpec,
csv: Csv,
env: &BuildEnv<CsvVistaSpec, Csv>,
) -> Result<Table<Csv, EmptyEntity>> {
let csv_path = if spec.driver.csv.path.is_absolute() {
spec.driver.csv.path.clone()
} else {
csv.base_dir().join(&spec.driver.csv.path)
};
let stem = csv_path
.file_stem()
.ok_or_else(|| error!("csv.path must point to a file", path = csv_path.display()))?
.to_string_lossy()
.to_string();
let parent = csv_path
.parent()
.ok_or_else(|| error!("csv.path must have a parent", path = csv_path.display()))?
.to_path_buf();
let id_column = spec.resolve_id_column("id");
let csv = Csv::new(parent).with_id_column(&id_column);
let mut table = Table::<Csv, EmptyEntity>::new(stem, csv);
table.add_spec_columns(&spec.columns, build_column)?;
table.set_spec_id_field(&id_column)?;
env.inject(&spec.name, &mut table, build_csv_table)?;
Ok(table)
}
impl VistaFactory for CsvVistaFactory {
type TableExtras = CsvTableExtras;
type ColumnExtras = CsvColumnExtras;
type ReferenceExtras = NoExtras;
fn build_from_spec(&self, spec: CsvVistaSpec) -> Result<Vista> {
let vista_name = spec.name.clone();
let table = self.table_from_spec(&spec)?;
Ok(self.from_table(table)?.with_name(vista_name))
}
}
fn csv_capabilities() -> VistaCapabilities {
[Capability::Window, Capability::Traverse]
.into_iter()
.collect()
}
pub(crate) fn build_column(
name: &str,
col_spec: &vantage_vista::ColumnSpec<CsvColumnExtras>,
) -> Result<TableColumn<AnyCsvType>> {
let header = col_spec.driver.csv.as_ref().and_then(|b| b.source.clone());
TableColumn::from_spec(name, col_spec, header, column_for_type)
}
pub(crate) fn column_for_type(name: &str, ty: &str) -> Result<TableColumn<AnyCsvType>> {
let col: TableColumn<AnyCsvType> = match ty {
"int" | "integer" | "i64" | "i32" => {
TableColumn::from_column(TableColumn::<i64>::new(name))
}
"float" | "double" | "f64" | "f32" => {
TableColumn::from_column(TableColumn::<f64>::new(name))
}
"bool" | "boolean" => TableColumn::from_column(TableColumn::<bool>::new(name)),
"string" | "text" | "str" => TableColumn::from_column(TableColumn::<String>::new(name)),
"json" => TableColumn::from_column(TableColumn::<serde_json::Value>::new(name)),
other => {
return Err(error!(
"Unknown YAML column type",
column = name,
ty = other.to_string()
));
}
};
Ok(col)
}