From: Ben Pfaff Date: Mon, 13 Oct 2025 02:00:15 +0000 (-0700) Subject: work X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91e8803aabf7bc5096720f667ac2b901d407d87c;p=pspp work --- diff --git a/rust/doc/src/spv/structure.md b/rust/doc/src/spv/structure.md index 619fa62fde..c1299b6cef 100644 --- a/rust/doc/src/spv/structure.md +++ b/rust/doc/src/spv/structure.md @@ -29,6 +29,10 @@ or `container` elements (or a mix), forming a tree. In turn, `container` holds a `label` and one more child, usually `text` or `table`. + + +## Grammar + The following sections document the elements found in structure members in a context-free grammar-like fashion. Consider the following example, which specifies the attributes and content for the `container` @@ -174,8 +178,6 @@ information, and the CSS from the embedded HTML: ``` - - ## The `heading` Element ``` diff --git a/rust/pspp/src/output.rs b/rust/pspp/src/output.rs index 5ac0a34b35..913c8756e5 100644 --- a/rust/pspp/src/output.rs +++ b/rust/pspp/src/output.rs @@ -18,6 +18,7 @@ use std::{ borrow::Cow, collections::BTreeMap, + fmt::Display, iter::once, mem::take, str::FromStr, @@ -53,12 +54,12 @@ 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 /// explicitly set. - label: Option, + pub label: Option, /// A locale-invariant identifier for the command that produced the output, /// which may be `None` if unknown or if a command did not produce this /// output. - command_name: Option, + pub command_name: Option, /// For a heading item, this is true if the heading's subtree should /// be expanded in an outline view, false otherwise. @@ -66,15 +67,15 @@ pub struct Item { /// For other kinds of output items, this is true to show the item's /// content, false to hide it. The item's label is always shown in an /// outline view. - show: bool, + pub show: bool, /// Item details. - details: Details, + pub details: Details, /// If the item was read from an SPV file, this is additional information /// related to that file. #[serde(skip_serializing)] - spv_info: Option>, + pub spv_info: Option>, } impl Item { @@ -89,17 +90,6 @@ impl Item { } } - /// Returns a new heading item suitable as the root node of an output document. - /// - /// A root node is a heading whose own properties are mostly disregarded. - /// Instead of having root nodes, it would make just as much sense to just - /// keep around arrays of nodes that would serve as the top level of an - /// output document, but we'd need more special cases instead of just using - /// the existing support for heading items. - pub fn new_root() -> Self { - Self::new(Details::Heading(Heading(Vec::new()))).with_label(Some(String::from("Output"))) - } - pub fn label(&self) -> Cow<'static, str> { match &self.label { Some(label) => Cow::from(label.clone()), @@ -107,12 +97,21 @@ impl Item { } } + pub fn subtype(&self) -> Option { + self.details + .as_table() + .map(|table| table.subtype().display(table).to_string()) + } + pub fn with_show(self, show: bool) -> Self { Self { show, ..self } } - pub fn with_label(self, label: Option) -> Self { - Self { label, ..self } + pub fn with_label(self, label: impl Into) -> Self { + Self { + label: Some(label.into()), + ..self + } } pub fn with_command_name(self, command_name: Option) -> Self { @@ -122,6 +121,10 @@ impl Item { } } + pub fn with_some_command_name(self, command_name: impl Into) -> Self { + self.with_command_name(Some(command_name.into())) + } + pub fn with_spv_info(self, spv_info: SpvInfo) -> Self { Self { spv_info: Some(Box::new(spv_info)), @@ -175,6 +178,37 @@ impl Heading { } } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ItemKind { + Chart, + Image, + Heading, + Message, + PageBreak, + Table, + Text, +} + +impl ItemKind { + pub fn as_str(&self) -> &'static str { + match self { + ItemKind::Chart => "chart", + ItemKind::Image => "image", + ItemKind::Heading => "heading", + ItemKind::Message => "message", + ItemKind::PageBreak => "page break", + ItemKind::Table => "table", + ItemKind::Text => "text", + } + } +} + +impl Display for ItemKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} + #[derive(Clone, Debug, Serialize)] pub enum Details { Chart, @@ -269,6 +303,18 @@ impl Details { pub fn is_text(&self) -> bool { matches!(self, Self::Text(_)) } + + pub fn kind(&self) -> ItemKind { + match self { + Details::Chart => ItemKind::Chart, + Details::Image => ItemKind::Image, + Details::Heading(_) => ItemKind::Heading, + Details::Message(_) => ItemKind::Message, + Details::PageBreak => ItemKind::PageBreak, + Details::Table(_) => ItemKind::Table, + Details::Text(_) => ItemKind::Text, + } + } } impl FromIterator for Details @@ -329,12 +375,15 @@ pub struct Text { } impl Text { - pub fn new_log(value: impl Into) -> Self { + pub fn new(type_: TextType, content: impl Into) -> Self { Self { - type_: TextType::Log, - content: value.into(), + type_, + content: content.into(), } } + pub fn new_log(content: impl Into) -> Self { + Self::new(TextType::Log, content) + } pub fn into_item(self) -> Item { Details::Text(Box::new(self)).into_item() @@ -758,7 +807,7 @@ 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, item: Item) -> Item { + pub fn apply(&self, item: Item) -> Item { fn take_children(item: &Item) -> Vec<&Item> { item.details.children().iter().map(|item| &**item).collect() } @@ -875,7 +924,7 @@ impl Criteria { } } fn unflatten_item(mut item: Item, include: &mut bit_vec::Iter, out: &mut Vec>) { - let include_item = include.next().unwrap(); + let include_item = include.next().unwrap_or_default(); //XXX should just be unwrap if let Some(children) = item.details.mut_children() { if !include_item { unflatten_items(take(children), include, out); @@ -901,13 +950,9 @@ impl Criteria { select_matches(&items, selection, &mut include); } - let mut output = Item::new_root(); - unflatten_item( - item, - &mut include.iter(), - output.details.mut_children().unwrap(), - ); - output + let mut output = Vec::new(); + unflatten_item(item, &mut include.iter(), &mut output); + Heading(output).into_item().with_label("Output") } } @@ -934,50 +979,43 @@ impl FromArgMatches for Criteria { fn extract( matches: &ArgMatches, - id: &clap::Id, + id: &str, output: &mut BTreeMap, f: F, ) where F: Fn(T) -> Value, { + if !matches.contains_id(id) || matches.try_get_many::(id).is_ok() { + // ignore groups + return; + } + let value_source = matches.value_source(id).expect("id came from matches"); + if value_source != clap::parser::ValueSource::CommandLine { + // Any other source just gets tacked on at the end (like default values) + return; + } for (value, index) in matches - .try_get_many::(id.as_str()) + .try_get_many::(id) .unwrap() .unwrap() - .zip(matches.indices_of(id.as_str()).unwrap()) + .zip(matches.indices_of(id).unwrap()) { output.insert(index, f(value.clone())); } } let mut values = BTreeMap::new(); - for id in matches.ids() { - if matches.try_get_many::(id.as_str()).is_ok() { - // ignore groups - continue; - } - let value_source = matches - .value_source(id.as_str()) - .expect("id came from matches"); - if value_source != clap::parser::ValueSource::CommandLine { - // Any other source just gets tacked on at the end (like default values) - continue; - } - match id.as_str() { - "_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), - "labels" => extract(matches, id, &mut values, Value::Labels), - "nth-commands" => extract(matches, id, &mut values, Value::NthCommands), - "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!("{id}"), - } - } - - if !self.0.is_empty() { + extract(matches, "_or", &mut values, |_: bool| Value::Or); + extract(matches, "select", &mut values, Value::Classes); + extract(matches, "commands", &mut values, Value::Commands); + extract(matches, "subtypes", &mut values, Value::Subtypes); + extract(matches, "labels", &mut values, Value::Labels); + extract(matches, "nth_commands", &mut values, Value::NthCommands); + extract(matches, "instances", &mut values, Value::Instances); + extract(matches, "show_hidden", &mut values, Value::ShowHidden); + extract(matches, "errors", &mut values, Value::Errors); + + if !values.is_empty() { let mut selection = Selection::default(); for value in values.into_values() { match value { @@ -1066,7 +1104,9 @@ mod tests { use clap::Parser; use enumset::EnumSet; - use crate::output::{Class, Criteria, Selection, StringMatch}; + use crate::output::{ + Class, Criteria, Heading, Item, Selection, StringMatch, pivot::PivotTable, + }; #[test] fn parse_classes() { @@ -1159,9 +1199,44 @@ mod tests { ); } - #[test] - fn apply_criteria() { - //let item = Details::Group(); - todo!() + fn regress_item() -> Item { + [ + Heading::new() + .into_item() + .with_label("Set") + .with_some_command_name("Set"), + [Heading::new() + .into_item() + .with_label("Page Title") + .with_some_command_name("Title")] + .into_iter() + .collect::() + .with_label("Title") + .with_some_command_name("Title"), + [PivotTable::new([]) + .with_title("Reading 1 record from INLINE.") + .with_subtype("Fixed Data Records") + .into_item() + .with_some_command_name("Data List")] + .into_iter() + .collect::() + .with_label("Data List") + .with_some_command_name("Data List"), + Heading::new() + .into_item() + .with_label("Begin Data") + .with_some_command_name("Begin Data"), + [PivotTable::new([]) + .with_title("Data List") + .into_item() + .with_some_command_name("List")] + .into_iter() + .collect::() + .with_label("List") + .with_some_command_name("List"), + ] + .into_iter() + .collect::() + .with_label("Output") } } diff --git a/rust/pspp/src/output/drivers/cairo/driver.rs b/rust/pspp/src/output/drivers/cairo/driver.rs index b676ce0919..31705f99ae 100644 --- a/rust/pspp/src/output/drivers/cairo/driver.rs +++ b/rust/pspp/src/output/drivers/cairo/driver.rs @@ -130,23 +130,18 @@ impl Driver for CairoDriver { pager }); pager.add_item(item.clone()); - dbg!(); while pager.needs_new_page() { - dbg!(); pager.finish_page(); let context = Context::new(&self.surface).unwrap(); context.show_page().unwrap(); pager.add_page(context); } - dbg!(); } } impl Drop for CairoDriver { fn drop(&mut self) { - dbg!(); if self.pager.is_some() { - dbg!(); let context = Context::new(&self.surface).unwrap(); context.show_page().unwrap(); } diff --git a/rust/pspp/src/output/pivot/look_xml.rs b/rust/pspp/src/output/pivot/look_xml.rs index 9a00bfe1c9..d9e59ee56a 100644 --- a/rust/pspp/src/output/pivot/look_xml.rs +++ b/rust/pspp/src/output/pivot/look_xml.rs @@ -542,5 +542,6 @@ mod tests { "##; let table_properties: TableProperties = from_str(XML).unwrap(); dbg!(&table_properties); + todo!() } } diff --git a/rust/pspp/src/output/spv.rs b/rust/pspp/src/output/spv.rs index 3125df475a..a212bde8f6 100644 --- a/rust/pspp/src/output/spv.rs +++ b/rust/pspp/src/output/spv.rs @@ -58,11 +58,11 @@ pub enum Error { } impl Item { - fn from_spv_file(path: impl AsRef) -> Result<(Self, Option), Error> { + pub fn from_spv_file(path: impl AsRef) -> Result<(Self, Option), Error> { Self::from_spv_reader(File::open(path.as_ref())?) } - fn from_spv_zip_archive( + pub fn from_spv_zip_archive( archive: &mut ZipArchive, ) -> Result<(Self, Option), Error> where @@ -93,7 +93,7 @@ impl Item { Ok((items.into_iter().collect(), page_setup)) } - fn from_spv_reader(reader: R) -> Result<(Self, Option), Error> + pub fn from_spv_reader(reader: R) -> Result<(Self, Option), Error> where R: Read + Seek, { @@ -114,7 +114,6 @@ fn read_heading( where R: Read + Seek, { - println!("{structure_member}"); let member = BufReader::new(archive.by_index(file_number)?); let mut heading: Heading = match serde_path_to_error::deserialize( &mut quick_xml::de::Deserializer::from_reader(member), @@ -122,7 +121,6 @@ where Ok(result) => result, Err(error) => panic!("{error}"), }; - dbg!(&heading); let page_setup = heading.page_setup.take(); Ok((heading.decode(archive, structure_member)?, page_setup)) } @@ -132,6 +130,8 @@ where struct Heading { #[serde(rename = "@visibility")] visibility: Option, + #[serde(rename = "@commandName")] + command_name: Option, label: Label, page_setup: Option, @@ -168,22 +168,35 @@ impl Heading { } ContainerContent::Text(container_text) => { items.push( - Text::new_log(container_text.decode()) - .into_item() - .with_command_name(container_text.command_name) - .with_spv_info(SpvInfo::new(structure_member)), + Text::new( + match container_text.text_type { + TextType::Title => crate::output::TextType::Title, + TextType::Log | TextType::Text => { + crate::output::TextType::Log + } + TextType::PageTitle => crate::output::TextType::PageTitle, + }, + container_text.decode(), + ) + .into_item() + .with_command_name(container_text.command_name) + .with_spv_info(SpvInfo::new(structure_member)), ); } } } - HeadingContent::Heading(heading) => { + HeadingContent::Heading(mut heading) => { let show = !heading.visibility.is_some(); + let label = std::mem::take(&mut heading.label.text); + let command_name = heading.command_name.take(); items.push( heading .decode(archive, structure_member)? .into_iter() .collect::() .with_show(show) + .with_label(label) + .with_command_name(command_name) .with_spv_info(SpvInfo::new(structure_member)), ); } @@ -279,7 +292,6 @@ impl Table { light.read_to_end(&mut data)?; let table = LightTable::read(&mut Cursor::new(data))?; let pivot_table = table.decode()?; - println!("{}", &pivot_table); Ok(pivot_table.into_item().with_spv_info( SpvInfo::new(structure_member) .with_members(SpvMembers::Light(self.table_structure.data_path.clone())), @@ -339,4 +351,5 @@ fn test_spv() { .unwrap() .0; println!("{item}"); + todo!() } diff --git a/rust/pspp/src/output/spv/html.rs b/rust/pspp/src/output/spv/html.rs index e0aed0701f..06bc616ac7 100644 --- a/rust/pspp/src/output/spv/html.rs +++ b/rust/pspp/src/output/spv/html.rs @@ -16,7 +16,6 @@ fn find_element<'a>(elements: &'a [Node], name: &str) -> Option<&'a Element> { if let Node::Element(element) = element && element.name == name { - dbg!(element); return Some(element); } } @@ -404,6 +403,7 @@ mod tests { ); dbg!(¶graphs); assert_eq!(paragraphs.len(), 5); + todo!() /* assert_eq!( paragraph, diff --git a/rust/pspp/src/output/spv/light.rs b/rust/pspp/src/output/spv/light.rs index 348932137e..89c0b28986 100644 --- a/rust/pspp/src/output/spv/light.rs +++ b/rust/pspp/src/output/spv/light.rs @@ -47,7 +47,6 @@ pub enum LightError { #[br(little)] #[derive(Debug)] pub struct LightTable { - #[br(dbg)] header: Header, #[br(args(header.version))] titles: Titles, @@ -66,7 +65,7 @@ pub struct LightTable { #[br(parse_with(parse_counted), args(header.version))] dimensions: Vec, axes: Axes, - #[br(dbg, parse_with(parse_counted), args(header.version))] + #[br(parse_with(parse_counted), args(header.version))] cells: Vec, } @@ -363,7 +362,6 @@ fn parse_color() -> BinResult { let pos = reader.stream_position()?; let string = U32String::read_options(reader, endian, ())?; let string = string.decode(WINDOWS_1252); - dbg!(&string); if string.is_empty() { Ok(Color::BLACK) } else { @@ -563,7 +561,6 @@ struct PrintSettings { struct TableSettings { #[br(temp, magic = 1u32)] _x5: i32, - #[br(dbg)] current_layer: i32, #[br(parse_with(parse_bool))] omit_empty: bool, @@ -651,7 +648,6 @@ impl BinRead for Value { (version,): (Version,), ) -> BinResult { let start = reader.stream_position()?; - dbg!(start); for i in 0..4 { let x = ::read_options(reader, endian, ())?; if x != 0 { @@ -659,11 +655,7 @@ impl BinRead for Value { break; } } - Ok(Value(dbg!(RawValue::read_options( - reader, - endian, - (version,) - ))?)) + Ok(Value(RawValue::read_options(reader, endian, (version,))?)) } } @@ -805,7 +797,6 @@ where struct Formats { #[br(parse_with(parse_counted))] column_widths: Vec, - #[br(dbg)] locale: U32String, current_layer: i32, #[br(temp, parse_with(parse_bool))] @@ -814,7 +805,6 @@ struct Formats { _x8: bool, #[br(temp, parse_with(parse_bool))] _x9: bool, - #[br(dbg)] y0: Y0, custom_currency: CustomCurrency, #[br(if(version == Version::V1))] diff --git a/rust/pspp/src/show_spv.rs b/rust/pspp/src/show_spv.rs index 3a680c2dd2..fafe045eac 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::Criteria; +use pspp::output::{Criteria, Item}; use std::{fmt::Display, path::PathBuf}; /// Show information about SPSS viewer files (SPV files). @@ -35,7 +35,11 @@ pub struct ShowSpv { /// Input selection options. #[command(flatten)] - selection: Criteria, + criteria: Criteria, + + /// Include ZIP member names in `dir` output. + #[arg(long = "member-names")] + show_member_names: bool, } /// What to show in a system file. @@ -70,7 +74,54 @@ impl Display for Mode { impl ShowSpv { pub fn run(self) -> Result<()> { - println!("{:#?}", &self); - todo!() + match self.mode { + Mode::Directory => { + let item = Item::from_spv_file(&self.input)?.0; + //let item = self.criteria.apply(item); + for child in item.details.children() { + print_item_directory(&child, 0, self.show_member_names); + } + Ok(()) + } + Mode::GetTableLook => todo!(), + Mode::ConvertTableLook => todo!(), + } + } +} + +fn print_item_directory(item: &Item, level: usize, show_member_names: bool) { + for _ in 0..level { + print!(" "); + } + print!("- {} {:?}", item.details.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 { + print!(" command {command_name:?}"); + } + if let Some(subtype) = item.subtype() + && item.label.as_ref().is_none_or(|label| label != &subtype) + { + print!(" subtype {subtype:?}"); + } + if !item.show { + if item.details.is_heading() { + print!(" (collapsed)"); + } else { + print!(" (hidden)"); + } + } + 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); } }