From 42af3aee8347b2d8ef56ef158965e7cda7891fc5 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 10 Oct 2025 22:12:25 -0700 Subject: [PATCH] work --- rust/pspp/src/message.rs | 6 +- rust/pspp/src/output.rs | 128 ++++++++++++++++++++++++++++++++------ rust/pspp/src/show_spv.rs | 5 +- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/rust/pspp/src/message.rs b/rust/pspp/src/message.rs index 28d0a9911a..125de74687 100644 --- a/rust/pspp/src/message.rs +++ b/rust/pspp/src/message.rs @@ -178,13 +178,13 @@ pub enum Category { Data, } -#[derive(Serialize)] +#[derive(Clone, Serialize)] pub struct Stack { location: Location, description: String, } -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct Diagnostics(pub Vec); impl From for Diagnostics { @@ -193,7 +193,7 @@ impl From for Diagnostics { } } -#[derive(Serialize)] +#[derive(Clone, Serialize)] pub struct Diagnostic { pub severity: Severity, pub category: Category, diff --git a/rust/pspp/src/output.rs b/rust/pspp/src/output.rs index 1ea6c5c561..563d5ef5fc 100644 --- a/rust/pspp/src/output.rs +++ b/rust/pspp/src/output.rs @@ -18,6 +18,7 @@ use std::{ borrow::Cow, collections::BTreeMap, + mem::take, str::FromStr, sync::{Arc, OnceLock}, }; @@ -45,7 +46,7 @@ mod spv; pub mod table; /// A single output item. -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct Item { /// The localized label for the item that appears in the outline pane in the /// output viewer and in PDF outlines. This is `None` if no label has been @@ -121,7 +122,7 @@ where } } -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub enum Details { Chart, Image, @@ -343,7 +344,7 @@ impl ItemCursor { /// Information for output items that were read from an SPV file. /// /// This is for debugging and troubleshooting purposes. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct SpvInfo { /// True if there was an error reading the output item (e.g. because of /// corruption or because PSPP doesn't understand the format). @@ -374,7 +375,7 @@ impl SpvInfo { } /// Identifies ZIP file members for one kind of output item in an SPV file. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum SpvMembers { /// Light detail members. Light( @@ -464,7 +465,7 @@ impl Item { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Selection { /// - `None`: Include all objects. /// - `Some(true)`: Include only visible objects. @@ -515,10 +516,16 @@ impl Selection { pub fn parse_instances(s: &str) -> Result, anyhow::Error> { s.split(',') - .map(|s| match s.parse::() { - Ok(0) => Err(anyhow!("--instances values must be nonzero")), - Ok(n) => Ok(n), - Err(error) => Err(error.into()), + .map(|s| { + if s == "last" { + Ok(-1) + } else { + match s.parse::() { + Ok(0) => Err(anyhow!("--instances values must be nonzero")), + Ok(n) => Ok(n), + Err(error) => Err(error.into()), + } + } }) .collect() } @@ -566,9 +573,11 @@ impl Default for Selection { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum StringMatch { + /// Include any string that matches any of the patterns. Include(Vec), + /// Include only the strings that do not match any of the patterns. Exclude(Vec), } @@ -611,10 +620,44 @@ impl FromStr for StringMatch { } } -#[derive(Clone, Debug, Default)] -pub struct Selections(pub Vec); +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Criteria(pub Vec); + +impl Criteria { + /// Returns a new output item whose children are all the (direct and + /// indirect) children of `item` that meet the criteria. + fn apply(&self, mut item: Item) -> Item { + fn take_children(item: &mut Item) -> Vec> { + match &mut item.details { + Details::Group(items) => take(items), + _ => Vec::new(), + } + } + fn flatten_children( + children: Vec>, + depth: usize, + items: &mut Vec, + depths: &mut Vec, + ) { + for child in children { + flatten(Arc::unwrap_or_clone(child), depth, items, depths); + } + } + fn flatten(mut item: Item, depth: usize, items: &mut Vec, depths: &mut Vec) { + let children = take_children(&mut item); + items.push(item); + depths.push(depth); + flatten_children(children, depth + 1, items, depths); + } + + let mut items = Vec::new(); + let mut depths = Vec::new(); + flatten_children(take_children(&mut item), 0, &mut items, &mut depths); + todo!() + } +} -impl FromArgMatches for Selections { +impl FromArgMatches for Criteria { fn from_arg_matches(matches: &ArgMatches) -> Result { let mut this = Self::default(); this.update_from_arg_matches(matches)?; @@ -653,7 +696,6 @@ impl FromArgMatches for Selections { } } - println!("{:#?}", matches.ids()); let mut values = BTreeMap::new(); for id in matches.ids() { if matches.try_get_many::(id.as_str()).is_ok() { @@ -668,7 +710,7 @@ impl FromArgMatches for Selections { continue; } match id.as_str() { - "or" => extract(matches, id, &mut values, |_: bool| Value::Or), + "_or" => extract(matches, id, &mut values, |_: bool| Value::Or), "select" => extract(matches, id, &mut values, Value::Classes), "commands" => extract(matches, id, &mut values, Value::Commands), "subtypes" => extract(matches, id, &mut values, Value::Subtypes), @@ -677,7 +719,7 @@ impl FromArgMatches for Selections { "instances" => extract(matches, id, &mut values, Value::Instances), "show-hidden" => extract(matches, id, &mut values, Value::ShowHidden), "errors" => extract(matches, id, &mut values, Value::Errors), - _ => unreachable!(), + _ => unreachable!("{id}"), } } @@ -688,7 +730,7 @@ impl FromArgMatches for Selections { let mut selection = Selection::default(); for value in values.into_values() { match value { - Value::Or => self.0.push(std::mem::take(&mut selection)), + Value::Or => self.0.push(take(&mut selection)), Value::Classes(classes) => selection.classes = classes, Value::Commands(commands) => selection.commands = commands, Value::Subtypes(subtypes) => selection.subtypes = subtypes, @@ -704,7 +746,7 @@ impl FromArgMatches for Selections { } } -impl Args for Selections { +impl Args for Criteria { fn augment_args(cmd: clap::Command) -> clap::Command { SelectionArgs::augment_args(cmd.next_help_heading("Input selection options")) } @@ -750,6 +792,7 @@ struct SelectionArgs { /// Include only XML and binary member names that match. Without any member /// names, include all objects. + #[arg(long, required = false, action = ArgAction::Append)] pub members: Vec, /// Separate two groups of selection options. @@ -759,9 +802,10 @@ struct SelectionArgs { #[cfg(test)] mod tests { + use clap::Parser; use enumset::EnumSet; - use crate::output::{Class, Selection, StringMatch}; + use crate::output::{Class, Criteria, Selection, StringMatch}; #[test] fn parse_classes() { @@ -790,6 +834,17 @@ mod tests { assert!(Selection::parse_nth_commands("0").is_err()); } + #[test] + fn parse_instances() { + assert_eq!(Selection::parse_instances("1").unwrap(), vec![1]); + assert_eq!( + Selection::parse_instances("2,3,-2,-3").unwrap(), + vec![2, 3, -2, -3] + ); + assert_eq!(Selection::parse_instances("last,1").unwrap(), vec![-1, 1]); + assert!(Selection::parse_instances("0").is_err()); + } + #[test] fn string_matches() { assert!(StringMatch::default().matches("xyzzy")); @@ -810,4 +865,39 @@ mod tests { assert!(!m.matches("abc")); assert!(!m.matches("abcd")); } + + #[test] + fn selections() { + #[derive(Parser, Debug, PartialEq, Eq)] + struct Command { + #[command(flatten)] + selections: Criteria, + } + + let args = vec![ + "myprog", + "--select=tables", + "--or", + "--commands=Frequencies,Descriptives", + ]; + let matches = Command::parse_from(args); + assert_eq!( + matches, + Command { + selections: Criteria(vec![ + Selection { + classes: EnumSet::only(Class::Tables), + ..Selection::default() + }, + Selection { + commands: StringMatch::Include(vec![ + String::from("Frequencies"), + String::from("Descriptives") + ]), + ..Selection::default() + } + ]) + } + ); + } } diff --git a/rust/pspp/src/show_spv.rs b/rust/pspp/src/show_spv.rs index 35ef9786ae..3a680c2dd2 100644 --- a/rust/pspp/src/show_spv.rs +++ b/rust/pspp/src/show_spv.rs @@ -16,7 +16,7 @@ use anyhow::Result; use clap::{Args, ValueEnum}; -use pspp::output::Selections; +use pspp::output::Criteria; use std::{fmt::Display, path::PathBuf}; /// Show information about SPSS viewer files (SPV files). @@ -33,8 +33,9 @@ pub struct ShowSpv { #[arg(required = true)] input: PathBuf, + /// Input selection options. #[command(flatten)] - selection: Selections, + selection: Criteria, } /// What to show in a system file. -- 2.30.2