Introduce Heading type and into_item().
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 12 Oct 2025 17:48:46 +0000 (10:48 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 12 Oct 2025 17:48:46 +0000 (10:48 -0700)
rust/pspp/src/output.rs
rust/pspp/src/output/drivers/spv.rs
rust/pspp/src/output/drivers/text.rs
rust/pspp/src/output/pivot/tests.rs
rust/pspp/src/output/spv.rs
rust/pspp/src/pc/tests.rs
rust/pspp/src/por/read.rs
rust/pspp/src/show.rs
rust/pspp/src/show_pc.rs
rust/pspp/src/show_por.rs
rust/pspp/src/sys/tests.rs

index 8c307a6c4811264aadd9218b3984aace921c7f2f..16cf4c5cca9cf6d7697be207848c1aa7a266ddac 100644 (file)
@@ -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<Arc<Item>>);
+
 #[derive(Clone, Debug, Serialize)]
 pub enum Details {
     Chart,
     Image,
-    Group(Vec<Arc<Item>>),
+    Group(Heading),
     Message(Box<Diagnostic>),
     PageBreak,
     Table(Box<PivotTable>),
@@ -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<Item>]> {
         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<Arc<Item>>> {
         match self {
-            Self::Group(children) => Some(children),
+            Self::Group(heading) => Some(&mut heading.0),
             _ => None,
         }
     }
 
     pub fn children(&self) -> impl Iterator<Item = &Arc<Item>> {
         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<Item = A>,
     {
-        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<usize>,
+            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<usize>,
-        ) {
+        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!()
+    }
 }
index 907f96cd64f4880a63dd0f3614be0c4bf4a61cb7..107fed9c6c2e3a6398e7d72cf6fb5fc8a88ca1c4 100644 (file)
@@ -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(())
                     })
index b9d4e7ee765e1a790286628e75f598040b7dcf87..e544a3782614fa70961f6adc341fb5a7ce5ccadb 100644 (file)
@@ -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)?;
                     }
index 1e7ee45da34fc6c00be4bd324e290738162247c1..e6c22457538511dcc3ed32a963431b93caef2969 100644 (file)
@@ -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);
index b24f506da7b65ff121ebd1d11e5a4dc617272e12..6dc1bdb3a732cb5d9481a2efa92e3ec630e9f23c 100644 (file)
@@ -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::<Details>().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::<Details>()
+                            .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!()
         }
index 25dab4f51318731e7fa9eba4da7e3cbcf5b3af39..b629bf179976d302c062bc936945691f1e247947 100644 (file)
@@ -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::<Details>().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();
index 51ccfbef8e38f165e49babb1a9cacbc3d6f75011..65184ca16222db523471830df358ab2ba79097b8 100644 (file)
@@ -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::<Details>().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();
index 494d322e27de93c6b86e3550ed5e976b63ab6971..f8d10d7829e1932f3fe51cdd73aa6cca8d0dbd67 100644 (file)
@@ -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<T>(&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::<Details>().into_item(),
+                        ));
                     }
                     Output::Json { .. } => {
                         output.show_json(&dictionary)?;
index a6dea1286a07d99ce5e65519fcd4de3812f3c5f9..baa5cc730b89ac8dd5cf1233bd6a0eb133ae75e2 100644 (file)
@@ -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::<Details>().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)?;
index f3e6ca636947401f12e5f7b282237e2414096cc4..9b474b639ac8b9af3f537addd60d74ce566cc922 100644 (file)
@@ -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::<Details>().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)?;
index 857e12ce8ea10bcda795b8388c06f7a83f21b1bd..292dfe73dbe6d2bc5d31eec46dd1e8701a5daa4f 100644 (file)
@@ -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::<Details>().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();