use laterite_ags4_emit::GroupInput;
use super::{Document, WriteMode, Written, emit_groups};
use crate::{Error, ErrorKind};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum Cell {
Null,
Text(String),
Int(i64),
Float(f64),
Bool(bool),
}
impl From<&str> for Cell {
fn from(s: &str) -> Cell {
Cell::Text(s.to_string())
}
}
impl From<String> for Cell {
fn from(s: String) -> Cell {
Cell::Text(s)
}
}
impl From<&String> for Cell {
fn from(s: &String) -> Cell {
Cell::Text(s.clone())
}
}
impl From<i64> for Cell {
fn from(v: i64) -> Cell {
Cell::Int(v)
}
}
impl From<i32> for Cell {
fn from(v: i32) -> Cell {
Cell::Int(i64::from(v))
}
}
impl From<f64> for Cell {
fn from(v: f64) -> Cell {
Cell::Float(v)
}
}
impl From<bool> for Cell {
fn from(v: bool) -> Cell {
Cell::Bool(v)
}
}
impl<T: Into<Cell>> From<Option<T>> for Cell {
fn from(v: Option<T>) -> Cell {
v.map_or(Cell::Null, Into::into)
}
}
impl Cell {
fn to_engine(&self) -> serde_json::Value {
match self {
Cell::Null => serde_json::Value::Null,
Cell::Text(s) => serde_json::Value::from(s.as_str()),
Cell::Int(v) => serde_json::Value::from(*v),
Cell::Float(v) => serde_json::Value::from(*v),
Cell::Bool(v) => serde_json::Value::from(*v),
}
}
}
#[derive(Debug, Clone)]
pub struct GroupData {
code: String,
headings: Vec<String>,
units: Option<Vec<String>>,
types: Option<Vec<String>>,
rows: Vec<Vec<Cell>>,
}
impl GroupData {
pub fn new<I, S>(code: impl Into<String>, headings: I) -> GroupData
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
GroupData {
code: code.into(),
headings: headings.into_iter().map(Into::into).collect(),
units: None,
types: None,
rows: Vec::new(),
}
}
#[must_use]
pub fn units<I, S>(mut self, units: I) -> GroupData
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.units = Some(units.into_iter().map(Into::into).collect());
self
}
#[must_use]
pub fn types<I, S>(mut self, types: I) -> GroupData
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.types = Some(types.into_iter().map(Into::into).collect());
self
}
#[must_use]
pub fn row<I, C>(mut self, cells: I) -> GroupData
where
I: IntoIterator<Item = C>,
C: Into<Cell>,
{
self.rows.push(cells.into_iter().map(Into::into).collect());
self
}
#[must_use]
pub fn code(&self) -> &str {
&self.code
}
#[must_use]
pub fn len(&self) -> usize {
self.rows.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
fn to_engine(&self) -> Result<GroupInput, Error> {
let width = self.headings.len();
for (i, row) in self.rows.iter().enumerate() {
if row.len() != width {
return Err(Error::new(
ErrorKind::InvalidArgument,
format!(
"{}: row {i} has {} cells but the group declares {width} headings",
self.code,
row.len()
),
));
}
}
for (what, meta) in [
("units", self.units.as_ref()),
("types", self.types.as_ref()),
] {
if let Some(m) = meta {
if m.len() != width {
return Err(Error::new(
ErrorKind::InvalidArgument,
format!(
"{}: {what} has {} entries but the group declares {width} headings",
self.code,
m.len()
),
));
}
}
}
Ok(GroupInput {
code: self.code.clone(),
headings: self.headings.clone(),
units: self.units.clone(),
types: self.types.clone(),
rows: self
.rows
.iter()
.map(|r| r.iter().map(Cell::to_engine).collect())
.collect(),
})
}
}
pub struct Build {
groups: Vec<GroupData>,
mode: WriteMode,
edition: Option<String>,
synthesise_metadata: bool,
tran: Option<laterite_ags4_emit::TranStamp>,
}
#[must_use]
pub fn build(groups: Vec<GroupData>) -> Build {
Build {
groups,
mode: WriteMode::default(),
edition: None,
synthesise_metadata: true,
tran: None,
}
}
#[must_use]
pub fn build_document(doc: &Document) -> Build {
let groups = doc
.groups()
.iter()
.map(|g| {
let headings = g.headings();
let mut data = GroupData::new(g.code(), headings.iter().copied())
.units(g.units().iter().copied())
.types(g.types().iter().copied());
for row in g.rows() {
data = data.row(
headings
.iter()
.map(|h| Cell::Text(row.cell(h).unwrap_or("").to_string())),
);
}
data
})
.collect();
build(groups)
}
impl Build {
#[must_use]
pub fn mode(mut self, mode: WriteMode) -> Build {
self.mode = mode;
self
}
#[must_use]
pub fn edition(mut self, edition: impl Into<String>) -> Build {
self.edition = Some(edition.into());
self
}
#[must_use]
pub fn synthesise_metadata(mut self, yes: bool) -> Build {
self.synthesise_metadata = yes;
self
}
#[must_use]
pub fn transmission(
mut self,
issue_number: impl Into<String>,
date: impl Into<String>,
producer: impl Into<String>,
recipient: impl Into<String>,
status: impl Into<String>,
) -> Build {
self.tran = Some(laterite_ags4_emit::TranStamp::new(
issue_number,
date,
producer,
recipient,
status,
));
self
}
pub fn run(self) -> Result<Written, Error> {
let groups = self
.groups
.iter()
.map(GroupData::to_engine)
.collect::<Result<Vec<_>, _>>()?;
emit_groups(
&groups,
self.mode,
self.edition.as_deref(),
self.synthesise_metadata,
self.tran,
)
}
}
impl std::fmt::Debug for Build {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Build")
.field(
"groups",
&self
.groups
.iter()
.map(|g| format!("{} x{}", g.code, g.rows.len()))
.collect::<Vec<_>>(),
)
.field("mode", &self.mode)
.field("edition", &self.edition)
.field("synthesise_metadata", &self.synthesise_metadata)
.field("transmission", &self.tran.is_some())
.finish()
}
}