From d9d9340c240da3b4015e52c0333e294a25b66487 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 12 Oct 2025 10:48:46 -0700 Subject: [PATCH] Introduce Heading type and into_item(). --- rust/pspp/src/output.rs | 83 ++++++++++++++-------------- rust/pspp/src/output/drivers/spv.rs | 4 +- rust/pspp/src/output/drivers/text.rs | 2 +- rust/pspp/src/output/pivot/tests.rs | 6 +- rust/pspp/src/output/spv.rs | 39 ++++++------- rust/pspp/src/pc/tests.rs | 4 +- rust/pspp/src/por/read.rs | 4 +- rust/pspp/src/show.rs | 34 +++--------- rust/pspp/src/show_pc.rs | 18 ++---- rust/pspp/src/show_por.rs | 18 ++---- rust/pspp/src/sys/tests.rs | 5 +- 11 files changed, 91 insertions(+), 126 deletions(-) diff --git a/rust/pspp/src/output.rs b/rust/pspp/src/output.rs index 8c307a6c48..16cf4c5cca 100644 --- a/rust/pspp/src/output.rs +++ b/rust/pspp/src/output.rs @@ -97,7 +97,7 @@ impl Item { /// output document, but we'd need more special cases instead of just using /// the existing support for group items. pub fn new_root() -> Self { - Self::new(Details::Group(Vec::new())).with_label(Some(String::from("Output"))) + Self::new(Details::Group(Heading(Vec::new()))).with_label(Some(String::from("Output"))) } pub fn label(&self) -> Cow<'static, str> { @@ -139,11 +139,14 @@ where } } +#[derive(Clone, Debug, Serialize)] +pub struct Heading(pub Vec>); + #[derive(Clone, Debug, Serialize)] pub enum Details { Chart, Image, - Group(Vec>), + Group(Heading), Message(Box), PageBreak, Table(Box), @@ -151,23 +154,27 @@ pub enum Details { } impl Details { + pub fn into_item(self) -> Item { + Item::new(self) + } + pub fn as_group(&self) -> Option<&[Arc]> { match self { - Self::Group(children) => Some(children.as_slice()), + Self::Group(heading) => Some(heading.0.as_slice()), _ => None, } } pub fn as_mut_group(&mut self) -> Option<&mut Vec>> { match self { - Self::Group(children) => Some(children), + Self::Group(heading) => Some(&mut heading.0), _ => None, } } pub fn children(&self) -> impl Iterator> { match self { - Self::Group(children) => Some(children.iter()), + Self::Group(children) => Some(children.0.iter()), _ => None, } .into_iter() @@ -248,7 +255,9 @@ where where T: IntoIterator, { - Self::Group(iter.into_iter().map(|value| value.into()).collect()) + Self::Group(Heading( + iter.into_iter().map(|value| value.into()).collect(), + )) } } @@ -392,19 +401,18 @@ impl ItemCursor { 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; - } + if let Some(children) = cur.details.as_group() + && let Some(first_child) = children.first() + { + self.cur = Some(first_child.clone()); + self.stack.push((cur, 1)); + } else { + 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; } } } @@ -732,37 +740,25 @@ impl Criteria { fn flatten_children<'a>( children: Vec<&'a Item>, depth: usize, - items: &mut Vec<&'a Item>, - depths: &mut Vec, + items: &mut Vec<(&'a Item, usize)>, ) { for child in children { - flatten(child, depth, items, depths); + flatten(child, depth, items); } } - fn flatten<'a>( - item: &'a Item, - depth: usize, - items: &mut Vec<&'a Item>, - depths: &mut Vec, - ) { + fn flatten<'a>(item: &'a Item, depth: usize, items: &mut Vec<(&'a Item, usize)>) { let children = take_children(item); - items.push(item); - depths.push(depth); - flatten_children(children, depth + 1, items, depths); + items.push((item, depth)); + flatten_children(children, depth + 1, items); } - fn select_matches( - items: &[&Item], - depths: &[usize], - selection: &Selection, - include: &mut BitVec, - ) { + fn select_matches(items: &[(&Item, usize)], selection: &Selection, include: &mut BitVec) { let mut instance_within_command = 0; let mut last_instance = None; let mut command_item = None; let mut command_command_item = None; let mut nth_command = 0; - for (index, (item, depth)) in std::iter::zip(items, depths).enumerate() { + for (index, (item, depth)) in items.into_iter().enumerate() { if *depth == 0 { command_item = Some(index); if let Some(last_instance) = last_instance.take() { @@ -868,8 +864,7 @@ impl Criteria { } let mut items = Vec::new(); - let mut depths = Vec::new(); - flatten_children(take_children(&item), 0, &mut items, &mut depths); + flatten_children(take_children(&item), 0, &mut items); let mut include = BitVec::from_elem(items.len(), false); let selections = if self.0.is_empty() { @@ -878,7 +873,7 @@ impl Criteria { self.0.as_slice() }; for selection in selections { - select_matches(&items, &depths, selection, &mut include); + select_matches(&items, selection, &mut include); } let mut output = Item::new_root(); @@ -1138,4 +1133,10 @@ mod tests { } ); } + + #[test] + fn apply_criteria() { + //let item = Details::Group(); + todo!() + } } diff --git a/rust/pspp/src/output/drivers/spv.rs b/rust/pspp/src/output/drivers/spv.rs index 907f96cd64..107fed9c6c 100644 --- a/rust/pspp/src/output/drivers/spv.rs +++ b/rust/pspp/src/output/drivers/spv.rs @@ -206,8 +206,8 @@ where .write_inner_content(|w| { w.create_element("label") .write_text_content(BytesText::new(&item.label()))?; - for child in children { - self.write_item(child, w); + for child in &children.0 { + self.write_item(&child, w); } Ok(()) }) diff --git a/rust/pspp/src/output/drivers/text.rs b/rust/pspp/src/output/drivers/text.rs index b9d4e7ee76..e544a37826 100644 --- a/rust/pspp/src/output/drivers/text.rs +++ b/rust/pspp/src/output/drivers/text.rs @@ -385,7 +385,7 @@ impl TextRenderer { Details::Chart => todo!(), Details::Image => todo!(), Details::Group(children) => { - for (index, child) in children.iter().enumerate() { + for (index, child) in children.0.iter().enumerate() { if index > 0 { writeln!(writer)?; } diff --git a/rust/pspp/src/output/pivot/tests.rs b/rust/pspp/src/output/pivot/tests.rs index 1e7ee45da3..e6c2245753 100644 --- a/rust/pspp/src/output/pivot/tests.rs +++ b/rust/pspp/src/output/pivot/tests.rs @@ -19,7 +19,7 @@ use std::{fmt::Display, fs::File, path::Path, sync::Arc}; use enum_map::EnumMap; use crate::output::{ - Details, Item, + Details, drivers::{ Driver, cairo::{CairoConfig, CairoDriver}, @@ -174,13 +174,13 @@ pub fn assert_rendering(name: &str, pivot_table: &PivotTable, expected: &str) { format!("{name} actual"), ); - let item = Arc::new(Item::new(Details::Table(Box::new(pivot_table.clone())))); + let item = Arc::new(Details::Table(Box::new(pivot_table.clone())).into_item()); if let Some(dir) = std::env::var_os("PSPP_TEST_HTML_DIR") { let writer = File::create(Path::new(&dir).join(name).with_extension("html")).unwrap(); HtmlDriver::for_writer(writer).write(&item); } - let item = Arc::new(Item::new(Details::Table(Box::new(pivot_table.clone())))); + let item = Arc::new(Details::Table(Box::new(pivot_table.clone())).into_item()); if let Some(dir) = std::env::var_os("PSPP_TEST_PDF_DIR") { let config = CairoConfig::new(Path::new(&dir).join(name).with_extension("pdf")); CairoDriver::new(&config).unwrap().write(&item); diff --git a/rust/pspp/src/output/spv.rs b/rust/pspp/src/output/spv.rs index b24f506da7..6dc1bdb3a7 100644 --- a/rust/pspp/src/output/spv.rs +++ b/rust/pspp/src/output/spv.rs @@ -22,7 +22,6 @@ use std::{ use binrw::BinRead; use displaydoc::Display; -use itertools::Itertools; use serde::Deserialize; use zip::{ZipArchive, result::ZipError}; @@ -92,7 +91,7 @@ impl Item { } Ok(( - Item::new(Details::Group(items.into_iter().map_into().collect())), + items.into_iter().collect::
().into_item(), page_setup, )) } @@ -159,7 +158,8 @@ impl Heading { HeadingContent::Container(container) => { if container.page_break_before { items.push( - Item::new(Details::PageBreak) + Details::PageBreak + .into_item() .with_spv_info(SpvInfo::new(structure_member)), ); } @@ -171,11 +171,10 @@ impl Heading { } ContainerContent::Text(container_text) => { items.push( - Item::new(Details::Text(Box::new(Text::new_log( - container_text.decode(), - )))) - .with_command_name(container_text.command_name) - .with_spv_info(SpvInfo::new(structure_member)), + Details::Text(Box::new(Text::new_log(container_text.decode()))) + .into_item() + .with_command_name(container_text.command_name) + .with_spv_info(SpvInfo::new(structure_member)), ); } } @@ -183,15 +182,13 @@ impl Heading { HeadingContent::Heading(heading) => { let show = !heading.visibility.is_some(); items.push( - Item::new(Details::Group( - heading - .decode(archive, structure_member)? - .into_iter() - .map_into() - .collect(), - )) - .with_show(show) - .with_spv_info(SpvInfo::new(structure_member)), + heading + .decode(archive, structure_member)? + .into_iter() + .collect::
() + .into_item() + .with_show(show) + .with_spv_info(SpvInfo::new(structure_member)), ); } } @@ -287,12 +284,12 @@ impl Table { let table = LightTable::read(&mut Cursor::new(data))?; let pivot_table = table.decode()?; println!("{}", &pivot_table); - Ok( - Item::new(Details::Table(Box::new(pivot_table))).with_spv_info( + Ok(Details::Table(Box::new(pivot_table)) + .into_item() + .with_spv_info( SpvInfo::new(structure_member) .with_members(SpvMembers::Light(self.table_structure.data_path.clone())), - ), - ) + )) } else { todo!() } diff --git a/rust/pspp/src/pc/tests.rs b/rust/pspp/src/pc/tests.rs index 25dab4f513..b629bf1799 100644 --- a/rust/pspp/src/pc/tests.rs +++ b/rust/pspp/src/pc/tests.rs @@ -30,9 +30,9 @@ fn test_pcfile(name: &str) { output.push(PivotTable::from(&metadata).into()); output.extend(dictionary.all_pivot_tables().into_iter().map_into()); output.extend(cases_to_output(&dictionary, cases)); - Item::new(Details::Group(output.into_iter().map_into().collect())) + output.into_iter().collect::
().into_item() } - Err(error) => Item::new(Details::Text(Box::new(Text::new_log(error.to_string())))), + Err(error) => Details::Text(Box::new(Text::new_log(error.to_string()))).into_item(), }; let actual = output.to_string(); diff --git a/rust/pspp/src/por/read.rs b/rust/pspp/src/por/read.rs index 51ccfbef8e..65184ca162 100644 --- a/rust/pspp/src/por/read.rs +++ b/rust/pspp/src/por/read.rs @@ -1196,9 +1196,9 @@ mod tests { output.push(PivotTable::from(&metadata).into()); output.extend(dictionary.all_pivot_tables().into_iter().map_into()); output.extend(cases_to_output(&dictionary, cases)); - Item::new(Details::Group(output.into_iter().map_into().collect())) + output.into_iter().collect::
().into_item() } - Err(error) => Item::new(Details::Text(Box::new(Text::new_log(error.to_string())))), + Err(error) => Details::Text(Box::new(Text::new_log(error.to_string()))).into_item(), }; let actual = output.to_string(); diff --git a/rust/pspp/src/show.rs b/rust/pspp/src/show.rs index 494d322e27..f8d10d7829 100644 --- a/rust/pspp/src/show.rs +++ b/rust/pspp/src/show.rs @@ -18,6 +18,7 @@ use crate::parse_encoding; use anyhow::{Result, anyhow}; use clap::{Args, ValueEnum}; use encoding_rs::Encoding; +use itertools::Itertools; use pspp::{ data::cases_to_output, output::{ @@ -94,20 +95,6 @@ enum Output { } impl Output { - /* - fn show_metadata(&self, metadata: MetadataEntry) -> Result<()> { - match self { - Self::Driver { driver, .. } => { - driver - .borrow_mut() - .write(&Arc::new(Item::new(metadata.into_pivot_table()))); - Ok(()) - } - Self::Json { .. } => self.show_json(&metadata), - Self::Discard => Ok(()), - } - }*/ - fn show(&self, value: &T) -> Result<()> where T: Serialize, @@ -117,7 +104,7 @@ impl Output { Self::Driver { driver, .. } => { driver .borrow_mut() - .write(&Arc::new(Item::new(value.into()))); + .write(&Arc::new(value.into().into_item())); Ok(()) } Self::Json { .. } => self.show_json(value), @@ -295,19 +282,12 @@ impl Show { match &output { Output::Driver { driver, mode: _ } => { let mut output = Vec::new(); - output.push(Item::new(PivotTable::from(&metadata))); - output.extend( - dictionary - .all_pivot_tables() - .into_iter() - .map(|pivot_table| Item::new(pivot_table)), - ); + output.push(PivotTable::from(&metadata).into()); + output.extend(dictionary.all_pivot_tables().into_iter().map_into()); output.extend(cases_to_output(&dictionary, cases)); - driver - .borrow_mut() - .write(&Arc::new(Item::new(Details::Group( - output.into_iter().map(Arc::new).collect(), - )))); + driver.borrow_mut().write(&Arc::new( + output.into_iter().collect::
().into_item(), + )); } Output::Json { .. } => { output.show_json(&dictionary)?; diff --git a/rust/pspp/src/show_pc.rs b/rust/pspp/src/show_pc.rs index a6dea1286a..baa5cc730b 100644 --- a/rust/pspp/src/show_pc.rs +++ b/rust/pspp/src/show_pc.rs @@ -16,6 +16,7 @@ use anyhow::{Result, anyhow}; use clap::{Args, ValueEnum}; +use itertools::Itertools; use pspp::{ data::cases_to_output, output::{ @@ -216,18 +217,11 @@ impl ShowPc { match &output { Output::Driver { driver, mode: _ } => { let mut output = Vec::new(); - output.extend( - dictionary - .all_pivot_tables() - .into_iter() - .map(|pivot_table| Item::new(pivot_table)), - ); + output.extend(dictionary.all_pivot_tables().into_iter().map_into()); output.extend(cases_to_output(&dictionary, cases)); - driver - .borrow_mut() - .write(&Arc::new(Item::new(Details::Group( - output.into_iter().map(Arc::new).collect(), - )))); + driver.borrow_mut().write(&Arc::new( + output.into_iter().collect::
().into_item(), + )); } Output::Json { .. } => { output.show_json(&dictionary)?; @@ -245,7 +239,7 @@ impl ShowPc { Output::Driver { driver, mode: _ } => { driver .borrow_mut() - .write(&Arc::new(Item::new(PivotTable::from(&metadata)))); + .write(&Arc::new(PivotTable::from(&metadata).into())); } Output::Json { .. } => { output.show_json(&metadata)?; diff --git a/rust/pspp/src/show_por.rs b/rust/pspp/src/show_por.rs index f3e6ca6369..9b474b639a 100644 --- a/rust/pspp/src/show_por.rs +++ b/rust/pspp/src/show_por.rs @@ -16,6 +16,7 @@ use anyhow::{Result, anyhow}; use clap::{Args, ValueEnum}; +use itertools::Itertools; use pspp::{ data::cases_to_output, output::{ @@ -216,18 +217,11 @@ impl ShowPor { match &output { Output::Driver { driver, mode: _ } => { let mut output = Vec::new(); - output.extend( - dictionary - .all_pivot_tables() - .into_iter() - .map(|pivot_table| Item::new(pivot_table)), - ); + output.extend(dictionary.all_pivot_tables().into_iter().map_into()); output.extend(cases_to_output(&dictionary, cases)); - driver - .borrow_mut() - .write(&Arc::new(Item::new(Details::Group( - output.into_iter().map(Arc::new).collect(), - )))); + driver.borrow_mut().write(&Arc::new( + output.into_iter().collect::
().into_item(), + )); } Output::Json { .. } => { output.show_json(&dictionary)?; @@ -246,7 +240,7 @@ impl ShowPor { Output::Driver { driver, mode: _ } => { driver .borrow_mut() - .write(&Arc::new(Item::new(PivotTable::from(&metadata)))); + .write(&Arc::new(PivotTable::from(&metadata).into())); } Output::Json { .. } => { output.show_json(&metadata)?; diff --git a/rust/pspp/src/sys/tests.rs b/rust/pspp/src/sys/tests.rs index 857e12ce8e..292dfe73db 100644 --- a/rust/pspp/src/sys/tests.rs +++ b/rust/pspp/src/sys/tests.rs @@ -18,7 +18,6 @@ use std::{ fs::File, io::{BufRead, BufReader, Cursor, Seek}, path::{Path, PathBuf}, - sync::Arc, }; use binrw::Endian; @@ -809,9 +808,9 @@ where } output.push(pt.into()); } - Item::new(Details::Group(output.into_iter().map(Arc::new).collect())) + output.into_iter().collect::
().into_item() } - Err(error) => Item::new(Details::Text(Box::new(Text::new_log(error.to_string())))), + Err(error) => Details::Text(Box::new(Text::new_log(error.to_string()))).into_item(), }; let actual = output.to_string(); -- 2.30.2