#![allow(dead_code)]
use std::{
borrow::Cow,
sync::{Arc, OnceLock},
};
use enum_map::EnumMap;
use pivot::PivotTable;
use serde::Serialize;
use crate::{
message::Diagnostic,
output::pivot::{Axis3, BorderStyle, Dimension, Group, Look},
};
use self::pivot::Value;
pub mod cairo;
pub mod csv;
pub mod driver;
pub mod html;
pub mod json;
pub mod page;
pub mod pivot;
pub mod render;
pub mod spv;
pub mod table;
pub mod text;
pub mod text_line;
#[derive(Serialize)]
pub struct Item {
label: Option<String>,
command_name: Option<String>,
show: bool,
details: Details,
}
impl Item {
pub fn new(details: impl Into<Details>) -> Self {
let details = details.into();
Self {
label: None,
command_name: details.command_name().cloned(),
show: true,
details,
}
}
pub fn label(&self) -> Cow<'static, str> {
match &self.label {
Some(label) => Cow::from(label.clone()),
None => self.details.label(),
}
}
}
impl<T> From<T> for Item
where
T: Into<Details>,
{
fn from(value: T) -> Self {
Self::new(value)
}
}
#[derive(Serialize)]
pub enum Details {
Chart,
Image,
Group(Vec<Arc<Item>>),
Message(Box<Diagnostic>),
PageBreak,
Table(Box<PivotTable>),
Text(Box<Text>),
}
impl Details {
pub fn as_group(&self) -> Option<&[Arc<Item>]> {
match self {
Self::Group(children) => Some(children.as_slice()),
_ => None,
}
}
pub fn command_name(&self) -> Option<&String> {
match self {
Details::Chart
| Details::Image
| Details::Group(_)
| Details::Message(_)
| Details::PageBreak
| Details::Text(_) => None,
Details::Table(pivot_table) => pivot_table.command_c.as_ref(),
}
}
pub fn label(&self) -> Cow<'static, str> {
match self {
Details::Chart => todo!(),
Details::Image => todo!(),
Details::Group(_) => Cow::from("Group"),
Details::Message(diagnostic) => Cow::from(diagnostic.severity.as_title_str()),
Details::PageBreak => Cow::from("Page Break"),
Details::Table(pivot_table) => Cow::from(pivot_table.label()),
Details::Text(text) => Cow::from(text.type_.as_str()),
}
}
pub fn is_page_break(&self) -> bool {
matches!(self, Self::PageBreak)
}
}
impl<A> FromIterator<A> for Details
where
A: Into<Arc<Item>>,
{
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = A>,
{
Self::Group(iter.into_iter().map(|value| value.into()).collect())
}
}
impl From<Diagnostic> for Details {
fn from(value: Diagnostic) -> Self {
Self::Message(Box::new(value))
}
}
impl From<Box<Diagnostic>> for Details {
fn from(value: Box<Diagnostic>) -> Self {
Self::Message(value)
}
}
impl From<PivotTable> for Details {
fn from(value: PivotTable) -> Self {
Self::Table(Box::new(value))
}
}
impl From<Box<PivotTable>> for Details {
fn from(value: Box<PivotTable>) -> Self {
Self::Table(value)
}
}
impl From<Text> for Details {
fn from(value: Text) -> Self {
Self::Text(Box::new(value))
}
}
impl From<Box<Text>> for Details {
fn from(value: Box<Text>) -> Self {
Self::Text(value)
}
}
#[derive(Clone, Debug, Serialize)]
pub struct Text {
type_: TextType,
content: Value,
}
impl Text {
pub fn new_log(value: impl Into<Value>) -> Self {
Self {
type_: TextType::Log,
content: value.into(),
}
}
}
fn text_item_table_look() -> Arc<Look> {
static LOOK: OnceLock<Arc<Look>> = OnceLock::new();
LOOK.get_or_init(|| {
Arc::new({
let mut look = Look::default().with_borders(EnumMap::from_fn(|_| BorderStyle::none()));
for style in look.areas.values_mut() {
style.cell_style.margins = EnumMap::from_fn(|_| [0, 0]);
}
look
})
})
.clone()
}
impl From<Text> for PivotTable {
fn from(value: Text) -> Self {
let dimension =
Dimension::new(Group::new(Value::new_text("Text")).with(Value::new_user_text("null")))
.with_all_labels_hidden();
PivotTable::new([(Axis3::Y, dimension)])
.with_look(text_item_table_look())
.with_data([(&[0], value.content)])
.with_subtype(Value::new_user_text("Text"))
}
}
impl From<&Diagnostic> for Text {
fn from(value: &Diagnostic) -> Self {
Self::new_log(value.to_string())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TextType {
PageTitle,
Title,
Syntax,
Log,
}
impl TextType {
pub fn as_str(&self) -> &'static str {
match self {
TextType::PageTitle => "Page Title",
TextType::Title => "Title",
TextType::Syntax => "Log",
TextType::Log => "Log",
}
}
pub fn as_xml_str(&self) -> &'static str {
match self {
TextType::PageTitle => "page-title",
TextType::Title => "title",
TextType::Syntax | TextType::Log => "log",
}
}
}
pub struct ItemCursor {
cur: Option<Arc<Item>>,
stack: Vec<(Arc<Item>, usize)>,
}
impl ItemCursor {
pub fn new(start: Arc<Item>) -> Self {
Self {
cur: Some(start),
stack: Vec::new(),
}
}
pub fn cur(&self) -> Option<&Arc<Item>> {
self.cur.as_ref()
}
pub fn next(&mut self) {
let Some(cur) = self.cur.take() else {
return;
};
match cur.details {
Details::Group(ref children) if !children.is_empty() => {
self.cur = Some(children[0].clone());
self.stack.push((cur, 1));
}
_ => {
while let Some((item, index)) = self.stack.pop() {
let children = item.details.as_group().unwrap();
if index < children.len() {
self.cur = Some(children[index].clone());
self.stack.push((item, index + 1));
return;
}
}
}
}
}
}