work rust
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 1 Jan 2026 01:04:10 +0000 (17:04 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 1 Jan 2026 01:04:10 +0000 (17:04 -0800)
rust/pspp/src/cli/show_spv.rs
rust/pspp/src/output.rs
rust/pspp/src/spv.rs
rust/pspp/src/spv/read.rs
rust/pspp/src/spv/read/structure.rs

index d60219997fd7729b356bb575ab3d94df938682cd..eeeeb927610cb529a4a5dcd5f1ec6584750df948 100644 (file)
@@ -22,7 +22,11 @@ use pspp::{
         Criteria, Item, Itemlike, SpvMembers,
         pivot::{Axis3, Dimension, Group, Leaf, PivotTable, value::Value},
     },
-    spv::{SpvArchive, legacy_bin::LegacyBin},
+    spv::{
+        SpvArchive,
+        legacy_bin::LegacyBin,
+        read::{ReadSeek, structure::OutlineItem},
+    },
 };
 use std::{
     collections::HashMap,
@@ -122,9 +126,15 @@ impl ShowSpv {
         ))
     }
 
+    fn read_outline(&self) -> Result<(SpvArchive<Box<dyn ReadSeek>>, Vec<Arc<OutlineItem>>)> {
+        let mut archive = SpvArchive::open_file(&self.input, self.password.as_deref())?;
+        let outline = archive.read_outline(|w| eprintln!("{w}"))?;
+        Ok((archive, self.criteria.apply(outline.items)))
+    }
+
     fn directory(self) -> Result<()> {
-        for child in self.read()? {
-            print_item_directory(&child, 0, self.show_member_names);
+        for child in self.read_outline()?.1 {
+            print_item_directory(&*child, 0, self.show_member_names);
         }
         Ok(())
     }
@@ -137,9 +147,7 @@ impl ShowSpv {
     }
 
     fn legacy_data(self) -> Result<()> {
-        let mut archive = SpvArchive::open_file(&self.input, self.password.as_deref())?;
-        let outline = archive.read_outline(|w| eprintln!("{w}"))?;
-        let items = self.criteria.apply(outline.items);
+        let (mut archive, items) = self.read_outline()?;
         for item in items {
             for item in item.iter_in_order() {
                 if let Some(spv_info) = item.spv_info()
@@ -199,39 +207,43 @@ impl ShowSpv {
     }
 }
 
-fn print_item_directory(item: &Item, level: usize, show_member_names: bool) {
+fn print_item_directory<T>(item: &T, level: usize, show_member_names: bool)
+where
+    T: Itemlike,
+{
     for _ in 0..level {
         print!("    ");
     }
-    print!("- {} {:?}", item.details.kind(), item.label());
+    print!("- {} {:?}", item.kind(), item.label());
+    /*
     if let Some(table) = item.details.as_table() {
         let title = table.title().display(table).to_string();
         if item.label.as_ref().is_none_or(|label| label != &title) {
             print!(" title {title:?}");
         }
-    }
-    if let Some(command_name) = &item.command_name {
+    }*/
+    if let Some(command_name) = item.command_name() {
         print!(" command {command_name:?}");
     }
     if let Some(subtype) = item.subtype()
-        && item.label.as_ref().is_none_or(|label| label != &subtype)
+        && let label = item.label().as_ref()
+        && label != &subtype
     {
         print!(" subtype {subtype:?}");
     }
-    if !item.show {
-        if item.details.is_heading() {
-            print!(" (collapsed)");
-        } else {
-            print!(" (hidden)");
-        }
+    if item.is_expanded() == Some(false) {
+        print!(" (collapsed");
+    }
+    if item.is_shown() == Some(false) {
+        print!(" (hidden)");
     }
-    if show_member_names && let Some(spv_info) = &item.spv_info {
+    if show_member_names && let Some(spv_info) = item.spv_info() {
         for (index, name) in spv_info.member_names().into_iter().enumerate() {
             print!(" {} {name:?}", if index == 0 { "in" } else { "and" });
         }
     }
     println!();
-    for child in item.details.children() {
-        print_item_directory(&child, level + 1, show_member_names);
+    for child in item.children() {
+        print_item_directory(&**child, level + 1, show_member_names);
     }
 }
index a2f1d6953203451c1f2c8d768acfcf51e4b2c0a8..83da04e0001270c2eeda4b5cd970d098449fb498 100644 (file)
@@ -87,7 +87,7 @@ pub trait Itemlike {
     fn label(&self) -> Cow<'_, str>;
     fn command_name(&self) -> Option<&str>;
     fn subtype(&self) -> Option<String>;
-    fn is_shown(&self) -> bool;
+    fn is_shown(&self) -> Option<bool>;
     fn page_break_before(&self) -> bool;
     fn spv_info(&self) -> Option<Cow<'_, SpvInfo>>;
     fn iter_in_order(&self) -> ItemRefIterator<'_, Self>
@@ -99,6 +99,7 @@ pub trait Itemlike {
     fn is_heading(&self) -> bool {
         self.kind() == ItemKind::Heading
     }
+    fn is_expanded(&self) -> Option<bool>;
     fn is_table(&self) -> bool {
         self.kind() == ItemKind::Table
     }
@@ -148,10 +149,22 @@ impl Itemlike for Item {
 
     /// Should the item be shown?
     ///
-    /// This always returns true for headings because their contents are always
-    /// shown (although headings can be collapsed in an outline view).
-    fn is_shown(&self) -> bool {
-        self.details.is_heading() || self.show
+    /// This returns None for headings because their contents are always shown
+    /// (although headings can be collapsed in an outline view).
+    fn is_shown(&self) -> Option<bool> {
+        if !self.details.is_heading() {
+            Some(self.show)
+        } else {
+            None
+        }
+    }
+
+    fn is_expanded(&self) -> Option<bool> {
+        if self.details.is_heading() {
+            Some(self.show)
+        } else {
+            None
+        }
     }
 
     fn page_break_before(&self) -> bool {
@@ -591,7 +604,7 @@ impl<'a, T> ItemRefIterator<'a, T> {
     where
         T: Itemlike,
     {
-        self.filter(|item| item.is_shown())
+        self.filter(|item| item.is_shown().unwrap_or(true))
     }
 
     pub fn new(start: &'a T) -> Self {
@@ -638,7 +651,7 @@ where
 {
     pub fn new(start: Arc<T>) -> Self {
         Self {
-            cur: start.as_ref().is_shown().then_some(start),
+            cur: start.as_ref().is_shown().unwrap_or(true).then_some(start),
             stack: Vec::new(),
             include_hidden: false,
         }
@@ -680,7 +693,7 @@ where
 
         inner(self);
         while let Some(cur) = &self.cur
-            && !cur.as_ref().is_shown()
+            && !cur.as_ref().is_shown().unwrap_or(true)
         {
             inner(self);
         }
@@ -1044,8 +1057,8 @@ impl Criteria {
                     continue;
                 }
                 if let Some(visible) = selection.visible
-                    && !item.is_heading()
-                    && visible != item.is_shown()
+                    && let Some(shown) = item.is_shown()
+                    && visible != shown
                 {
                     continue;
                 }
index eb26aaf501d0af82faa685410cc864c6428acc84..17990f94fc9368c2bafd5c5d92a2c6ff904d69e7 100644 (file)
@@ -27,7 +27,7 @@
 // Warn about missing docs, but not for items declared with `#[cfg(test)]`.
 #![cfg_attr(not(test), warn(missing_docs))]
 
-mod read;
+pub mod read;
 mod write;
 
 pub use read::{Error, SpvArchive, SpvFile, SpvOutline, html, legacy_bin};
index 2c0fd944325f12cc7702f4ccf70e7b45ed4c38f5..fb5daf70cfc2436b7c903134e1ee26670405a862 100644 (file)
@@ -39,7 +39,7 @@ pub mod html;
 pub mod legacy_bin;
 mod legacy_xml;
 mod light;
-mod structure;
+pub mod structure;
 #[cfg(test)]
 mod tests;
 
index 231c1e906fc8f891bec9eebc239c6f7a1900f2bd..a4b18eb0372d50e191d229ec4f5ad5ce436bf3d9 100644 (file)
@@ -155,10 +155,17 @@ impl Itemlike for OutlineItem {
         }
     }
 
-    fn is_shown(&self) -> bool {
+    fn is_shown(&self) -> Option<bool> {
         match self {
-            OutlineItem::Heading(_) => true,
-            OutlineItem::Container(container) => container.show,
+            OutlineItem::Heading(_) => None,
+            OutlineItem::Container(container) => Some(container.show),
+        }
+    }
+
+    fn is_expanded(&self) -> Option<bool> {
+        match self {
+            OutlineItem::Heading(outline_heading) => Some(outline_heading.expand),
+            OutlineItem::Container(_) => None,
         }
     }