use std::{
borrow::Cow,
collections::BTreeMap,
+ mem::take,
str::FromStr,
sync::{Arc, OnceLock},
};
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
}
}
-#[derive(Debug, Serialize)]
+#[derive(Clone, Debug, Serialize)]
pub enum Details {
Chart,
Image,
/// 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).
}
/// 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(
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Selection {
/// - `None`: Include all objects.
/// - `Some(true)`: Include only visible objects.
pub fn parse_instances(s: &str) -> Result<Vec<isize>, anyhow::Error> {
s.split(',')
- .map(|s| match s.parse::<isize>() {
- 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::<isize>() {
+ Ok(0) => Err(anyhow!("--instances values must be nonzero")),
+ Ok(n) => Ok(n),
+ Err(error) => Err(error.into()),
+ }
+ }
})
.collect()
}
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StringMatch {
+ /// Include any string that matches any of the patterns.
Include(Vec<String>),
+ /// Include only the strings that do not match any of the patterns.
Exclude(Vec<String>),
}
}
}
-#[derive(Clone, Debug, Default)]
-pub struct Selections(pub Vec<Selection>);
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct Criteria(pub Vec<Selection>);
+
+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<Arc<Item>> {
+ match &mut item.details {
+ Details::Group(items) => take(items),
+ _ => Vec::new(),
+ }
+ }
+ fn flatten_children(
+ children: Vec<Arc<Item>>,
+ depth: usize,
+ items: &mut Vec<Item>,
+ depths: &mut Vec<usize>,
+ ) {
+ for child in children {
+ flatten(Arc::unwrap_or_clone(child), depth, items, depths);
+ }
+ }
+ fn flatten(mut item: Item, depth: usize, items: &mut Vec<Item>, depths: &mut Vec<usize>) {
+ 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<Self, clap::Error> {
let mut this = Self::default();
this.update_from_arg_matches(matches)?;
}
}
- println!("{:#?}", matches.ids());
let mut values = BTreeMap::new();
for id in matches.ids() {
if matches.try_get_many::<clap::Id>(id.as_str()).is_ok() {
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),
"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}"),
}
}
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,
}
}
-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"))
}
/// 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<String>,
/// Separate two groups of selection options.
#[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() {
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"));
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()
+ }
+ ])
+ }
+ );
+ }
}