use laterite_ags4_core::ags4_codec::ParsedAgs4;
use crate::{Error, ErrorKind};
pub struct Document {
pub(crate) parsed: ParsedAgs4,
pub(crate) source_bytes: Vec<u8>,
pub(crate) encoding: Option<String>,
pub(crate) sliced: bool,
}
impl Document {
pub(crate) fn new(
parsed: ParsedAgs4,
source_bytes: Vec<u8>,
encoding: Option<String>,
) -> Document {
Document {
parsed,
source_bytes,
encoding,
sliced: false,
}
}
#[must_use]
pub fn sliced(&self) -> bool {
self.sliced
}
pub(crate) fn retain_only(&mut self, codes: &[String]) {
self.parsed.groups.retain(|code, _| codes.contains(code));
self.parsed.order.retain(|code| codes.contains(code));
}
#[must_use]
pub fn codes(&self) -> Vec<&str> {
self.parsed.order.iter().map(String::as_str).collect()
}
#[must_use]
pub fn groups(&self) -> Vec<Group<'_>> {
self.parsed
.order
.iter()
.filter_map(|code| self.group(code))
.collect()
}
#[must_use]
pub fn group(&self, code: &str) -> Option<Group<'_>> {
self.parsed.groups.get(code).map(|g| Group { inner: g })
}
#[must_use]
pub fn contains(&self, code: &str) -> bool {
self.parsed.groups.contains_key(code)
}
#[must_use]
pub fn len(&self) -> usize {
self.parsed.order.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.parsed.order.is_empty()
}
pub fn set_cell(
&mut self,
group: &str,
row: usize,
heading: &str,
value: impl Into<String>,
) -> Result<(), Error> {
let g = self.parsed.groups.get_mut(group).ok_or_else(|| {
Error::new(
ErrorKind::InvalidArgument,
format!("no group `{group}` in this document"),
)
})?;
if !g.headings.iter().any(|h| h == heading) {
return Err(Error::new(
ErrorKind::InvalidArgument,
format!("group `{group}` has no heading `{heading}`"),
));
}
let n = g.rows.len();
let r = g.rows.get_mut(row).ok_or_else(|| {
Error::new(
ErrorKind::InvalidArgument,
format!("group `{group}` has {n} row(s); no row {row}"),
)
})?;
let key = r
.keys()
.find(|k| &***k == heading)
.cloned()
.unwrap_or_else(|| heading.into());
r.insert(key, value.into());
Ok(())
}
pub fn push_row(&mut self, group: &str, cells: &[(&str, &str)]) -> Result<(), Error> {
let g = self.parsed.groups.get_mut(group).ok_or_else(|| {
Error::new(
ErrorKind::InvalidArgument,
format!("no group `{group}` in this document"),
)
})?;
if let Some((bad, _)) = cells
.iter()
.find(|(h, _)| !g.headings.iter().any(|x| x == h))
{
return Err(Error::new(
ErrorKind::InvalidArgument,
format!("group `{group}` has no heading `{bad}`"),
));
}
let mut row = std::collections::HashMap::with_capacity(g.headings.len());
for h in &g.headings {
let v = cells
.iter()
.find(|(name, _)| name == h)
.map_or("", |(_, v)| *v);
row.insert(std::sync::Arc::<str>::from(h.as_str()), v.to_string());
}
g.rows.push(row);
Ok(())
}
pub fn remove_group(&mut self, code: &str) -> bool {
let existed = self.parsed.groups.remove(code).is_some();
self.parsed.order.retain(|c| c != code);
existed
}
}
#[derive(Clone, Copy)]
pub struct Group<'a> {
inner: &'a laterite_ags4_core::ags4_codec::AgsGroup,
}
impl<'a> Group<'a> {
#[must_use]
pub fn code(&self) -> &'a str {
&self.inner.code
}
#[must_use]
pub fn headings(&self) -> Vec<&'a str> {
self.inner.headings.iter().map(String::as_str).collect()
}
#[must_use]
pub fn units(&self) -> Vec<&'a str> {
self.inner.units.iter().map(String::as_str).collect()
}
#[must_use]
pub fn types(&self) -> Vec<&'a str> {
self.inner.types.iter().map(String::as_str).collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.rows.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.rows.is_empty()
}
#[must_use]
pub fn row(&self, i: usize) -> Option<Row<'a>> {
self.inner.rows.get(i).map(|r| Row { cells: r })
}
#[must_use]
pub fn rows(&self) -> Rows<'a> {
Rows {
group: *self,
next: 0,
}
}
}
pub struct Rows<'a> {
group: Group<'a>,
next: usize,
}
impl<'a> Iterator for Rows<'a> {
type Item = Row<'a>;
fn next(&mut self) -> Option<Row<'a>> {
let row = self.group.row(self.next)?;
self.next += 1;
Some(row)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let left = self.group.len().saturating_sub(self.next);
(left, Some(left))
}
}
impl ExactSizeIterator for Rows<'_> {}
#[derive(Clone, Copy)]
pub struct Row<'a> {
cells: &'a std::collections::HashMap<std::sync::Arc<str>, String>,
}
impl<'a> Row<'a> {
#[must_use]
pub fn cell(&self, heading: &str) -> Option<&'a str> {
self.cells.get(heading).map(String::as_str)
}
#[must_use]
pub fn len(&self) -> usize {
self.cells.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cells.is_empty()
}
}
impl std::fmt::Debug for Document {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Document")
.field("groups", &self.parsed.order.len())
.field("codes", &self.codes())
.field("source_bytes", &self.source_bytes.len())
.field("encoding", &self.encoding)
.field("sliced", &self.sliced)
.finish()
}
}
impl std::fmt::Debug for Group<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Group")
.field("code", &self.code())
.field("headings", &self.headings())
.field("rows", &self.len())
.finish()
}
}
impl std::fmt::Debug for Row<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Row").field("cells", &self.len()).finish()
}
}
impl std::fmt::Debug for Rows<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Rows")
.field("group", &self.group.code())
.field("remaining", &self.len())
.finish()
}
}