use std::{pin::Pin, sync::Arc};
use thiserror::Error;
use crate::{io::ReadableFile, schema::{EntitySchema, EntityStream}, storage::Pool};
mod column_parser;
mod flat_file;
pub use flat_file::FlatFileImporter;
mod json_virtual;
pub use json_virtual::VirtualImporter;
#[cfg(feature = "csv")]
mod csv;
#[cfg(feature = "csv")]
pub use csv::CsvImporter;
#[cfg(feature = "izs")]
mod izs;
#[cfg(feature = "izs")]
pub use izs::IzsImporter;
#[derive(Error, Debug)]
pub enum ImportError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Schema mismatch: {0}")]
SchemaMismatch(String),
#[error("File is malformed: {0}")]
InvalidFile(String),
}
pub struct OptionDescription {
pub name: &'static str,
pub description: &'static str,
}
pub trait Importer: Send {
fn options(&self) -> &'static [ OptionDescription ] {
&[]
}
fn set(&mut self, option: &str, value: &str) -> Result<(), String> {
let _ = (option, value);
Err(format!("Unknown option"))
}
fn get(&self, option: &str) -> Option<String> {
let _ = option;
None
}
fn load_schema(&self, file: Arc<dyn ReadableFile>) -> Pin<Box<dyn Future<Output = Result<EntitySchema, ImportError>> + Send + '_>>;
fn import(&self, file: Arc<dyn ReadableFile>, schema: Option<EntitySchema>, pool: Arc<Pool>) -> Pin<Box<dyn Future<Output = Result<(EntityStream, Pin<Box<dyn Future<Output = Result<(), ImportError>> + Send>>), ImportError>> + Send + '_>>;
}
pub struct ImportFormat {
pub name: &'static str,
pub description: &'static str,
pub extensions: &'static [&'static str],
pub importer: fn () -> Box<dyn Importer>,
}
impl ImportFormat {
pub fn matches_filename(&self, name: &str) -> bool {
self.extensions.iter().any(|ext| name.ends_with(ext))
}
pub fn importer(&self) -> Box<dyn Importer> {
(self.importer)()
}
}
pub struct ImportFormats<'a>(&'a [ImportFormat]);
impl<'a> ImportFormats<'a> {
pub fn iter(&self) -> std::slice::Iter<'a, ImportFormat> {
self.0.as_ref().iter()
}
pub fn by_name(&self, name: &str) -> Option<&'a ImportFormat> {
self.iter().find(|imp| imp.name.eq_ignore_ascii_case(name))
}
pub fn first_for_filename(&self, fname: &str) -> Option<&'a ImportFormat> {
self.iter().find(|imp| imp.matches_filename(fname))
}
}
impl<'a> IntoIterator for &'a ImportFormats<'a> {
type Item = &'a ImportFormat;
type IntoIter = std::slice::Iter<'a, ImportFormat>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub const VIRTUAL: ImportFormat = ImportFormat {
name: "virtual",
description: "Iguazu Virtual JSON",
extensions: &[".iguazu.json"],
importer: || Box::new(json_virtual::VirtualImporter::new()),
};
#[cfg(feature = "izs")]
pub const IZS: ImportFormat = ImportFormat {
name: "izs",
description: "Iguazu Pack",
extensions: &[".izs"],
importer: || Box::new(izs::IzsImporter::new()),
};
pub const BIN: ImportFormat = ImportFormat {
name: "bin",
description: "Raw binary",
extensions: &[".bin"],
importer: || Box::new(flat_file::FlatFileImporter::binary()),
};
pub const LOGIC8: ImportFormat = ImportFormat {
name: "logic8",
description: "Raw binary (8 bit logic trace)",
extensions: &[".logic8"],
importer: || Box::new(flat_file::FlatFileImporter::logic8())
};
#[cfg(feature = "csv")]
pub const CSV: ImportFormat = ImportFormat {
name: "csv",
description: "Comma-separated values",
extensions: &[".csv"],
importer: || Box::new(csv::CsvImporter::csv()),
};
#[cfg(feature = "csv")]
pub const TSV: ImportFormat = ImportFormat {
name: "tsv",
description: "Tab-separated values",
extensions: &[".tsv"],
importer: || Box::new(csv::CsvImporter::tsv())
};
pub const IMPORTERS: ImportFormats<'static> = ImportFormats(&[
VIRTUAL,
#[cfg(feature = "izs")] IZS,
BIN, LOGIC8,
#[cfg(feature = "csv")] CSV,
#[cfg(feature = "csv")] TSV,
]);