From: Ben Pfaff Date: Wed, 29 Jul 2026 20:15:14 +0000 (-0700) Subject: Allow `pspp convert` to accept multiple input files. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7340e3654596c8a8920f9629c422229a025a61f7;p=pspp Allow `pspp convert` to accept multiple input files. --- diff --git a/rust/doc/src/invoking/pc.md b/rust/doc/src/invoking/pc.md index 39ea8d5bf7..baf2c4ac62 100644 --- a/rust/doc/src/invoking/pc.md +++ b/rust/doc/src/invoking/pc.md @@ -54,7 +54,7 @@ The following ``s are available: `pspp pc` accepts the following options: -* `-o ` +* `-O ` Adds `` to the output engine configuration. See [Output Drivers](output.md) for information on how to configure output. diff --git a/rust/doc/src/invoking/por.md b/rust/doc/src/invoking/por.md index 1b9ff408e6..9ffb6493cf 100644 --- a/rust/doc/src/invoking/por.md +++ b/rust/doc/src/invoking/por.md @@ -92,7 +92,7 @@ The following ``s are available: `pspp por` accepts the following options: -* `-o ` +* `-O ` Adds `` to the output engine configuration. See [Output Drivers](output.md) for information on how to configure output. diff --git a/rust/doc/src/invoking/pspp-convert.md b/rust/doc/src/invoking/pspp-convert.md index 25f9db4c16..825a57cbb6 100644 --- a/rust/doc/src/invoking/pspp-convert.md +++ b/rust/doc/src/invoking/pspp-convert.md @@ -4,49 +4,55 @@ The `pspp convert` command reads a file and writes it to another, usually in a different format. The basic syntax is: ``` -pspp convert [OUTPUT] +pspp convert ... [-o ] ``` -which reads `` and writes a copy of it to `[OUTPUT]`, or to the -terminal if `[OUTPUT]` is omitted. +which reads each `` and writes a copy to `` if the `-o` +option is specified, or to the terminal otherwise. `pspp convert` can convert following kinds of files: * Data files: - When `` is an SPSS system file or portable file or an SPSS/PC+ - system file, then `[OUTPUT]` can be a data file format. The - currently supported data file formats are: + When `` is a single SPSS system file or portable file or + SPSS/PC+ system file, the currently supported output file formats + are: - [Comma-separated value (CSV) files] - [SPSS system files] - [SPSS portable files] - If `[OUTPUT]` is omitted, the default is to write the data to stdout - in CSV format. + If `-o ` is omitted, the default is to write the data to + stdout in CSV format. * Viewer files: - When `` is an SPSS viewer (SPV) file, then `[OUTPUT]` may be - any [PSPP output format], including: + When `` is one or more SPSS viewer (SPV) files, then the + output may be any [PSPP output format], including: - [plain text] - [PDF] - [HTML] - If `[OUTPUT]` is omitted, the default is to write the viewer file to - stdout in plain text format. + If `-o ` is omitted, the default is to write the viewer file + to stdout in plain text format. For viewer file input, [selection options](#viewer-file-selection-options) can select a subset of the viewer file for output. + If more than one viewer file is specified as input, they are + concatenated in the order specified to produce the output. + * Chart XML files: The SPSS graph editor can export charts as individual XML files. PSPP can read and convert these to any of the support output formats. + Any number of viewer files or chart XML files may be specified as + input. + [Converting SPSS Viewer Files]: pspp-convert-spv.md [Comma-separated value (CSV) files]: output.md#comma-separated-value-output-csv [SPSS system files]: output.md#system-file-output-sav @@ -60,6 +66,11 @@ terminal if `[OUTPUT]` is omitted. `pspp convert` accepts the following options: +* `-o ` + Sets the output file name. The extension on `` determines + the default output format. Without this option, PSPP writes output + to standard output. + * `-e ` `--encoding=` Sets the character encoding used to read text strings in the input @@ -100,7 +111,7 @@ terminal if `[OUTPUT]` is omitted. > ⚠️ The password (and other command-line options) may be visible to > other users on multiuser systems. -* `-o ` +* `-O ` Adds `` to the output engine configuration. See [Output Drivers](output.md) for information on how to configure output. diff --git a/rust/doc/src/invoking/pspp-show.md b/rust/doc/src/invoking/pspp-show.md index db8c3fa5ef..588837c46d 100644 --- a/rust/doc/src/invoking/pspp-show.md +++ b/rust/doc/src/invoking/pspp-show.md @@ -81,7 +81,7 @@ The following ``s are available: that sets a limit on the number of cases to read. Without this option, PSPP will not read any cases. -* `-o ` +* `-O ` Adds `` to the output engine configuration. See [Output Drivers](output.md) for information on how to configure output. diff --git a/rust/pspp/src/cli/convert.rs b/rust/pspp/src/cli/convert.rs index 175eec211a..f70f2474cf 100644 --- a/rust/pspp/src/cli/convert.rs +++ b/rust/pspp/src/cli/convert.rs @@ -46,7 +46,7 @@ pub struct Convert { /// /// * SPSS exported chart files (usually `.xml`). #[arg(verbatim_doc_comment)] - input: PathBuf, + input: Vec, /// Output file name (if omitted, output is written to stdout). /// @@ -61,7 +61,7 @@ pub struct Convert { /// * plain text /// * PDF /// * HTML - #[arg(verbatim_doc_comment)] + #[arg(verbatim_doc_comment, long, short = 'o')] output: Option, /// The encoding to use for reading the input file. @@ -86,7 +86,7 @@ pub struct Convert { to_unicode: bool, /// Output driver configuration options. - #[arg(short = 'o')] + #[arg(short = 'O')] output_options: Vec, /// Selection options for SPV input files. @@ -103,6 +103,10 @@ impl Convert { dictionary: Dictionary, cases: Box>>, AnyError>>>, ) -> Result<()> { + if self.input.len() > 1 { + bail!("'convert' only supports a single data file input"); + } + // Take only the first `self.max_cases` cases. let cases = cases.take(self.max_cases.unwrap_or(usize::MAX)); @@ -119,73 +123,92 @@ impl Convert { } pub fn run(self) -> Result<()> { - match FileType::from_file(&self.input)? { - Some(FileType::System { .. }) => { - fn warn(warning: anyhow::Error) { - eprintln!("warning: {warning}"); - } + let mut data = None; + let mut page_setup = None; + let mut items = Vec::new(); - let mut system_file = ReadOptions::new(warn) - .with_encoding(self.encoding) - .with_password(self.password.clone()) - .open_file(&self.input)?; - if self.to_unicode { - system_file = system_file.into_unicode(); - } - let (dictionary, _, cases) = system_file.into_parts(); - let cases = cases.map(|result| result.map_err(AnyError::from)); - let cases = Box::new(cases) - as Box>>, AnyError>>>; - self.write_data(dictionary, cases) + for input in &self.input { + if data.is_some() { + bail!("convert: Only one data file may be specified."); } - Some(FileType::Portable) => { - fn warn_portable(warning: pspp::por::Warning) { - eprintln!("warning: {warning}"); - } - let portable_file = PortableFile::open_file(&self.input, warn_portable)?; - let (dictionary, _, cases) = portable_file.into_parts(); - let cases = cases.map(|result| result.map_err(AnyError::from)); - let cases = Box::new(cases) - as Box>>, AnyError>>>; - self.write_data(dictionary, cases) - } - Some(FileType::Pc) => { - fn warn_pc(warning: pspp::pc::Warning) { - eprintln!("warning: {warning}"); + match FileType::from_file(input)? { + Some(FileType::System { .. }) => { + fn warn(warning: anyhow::Error) { + eprintln!("warning: {warning}"); + } + + let mut system_file = ReadOptions::new(warn) + .with_encoding(self.encoding) + .with_password(self.password.clone()) + .open_file(input)?; + if self.to_unicode { + system_file = system_file.into_unicode(); + } + let (dictionary, _, cases) = system_file.into_parts(); + let cases = cases.map(|result| result.map_err(AnyError::from)); + let cases = Box::new(cases) + as Box>>, AnyError>>>; + data = Some((dictionary, cases)); } - - let pc_file = PcFile::open_file(&self.input, warn_pc)?; - let (dictionary, _, cases) = pc_file.into_parts(); - let cases = cases.map(|result| result.map_err(AnyError::from)); - let cases = Box::new(cases) - as Box>>, AnyError>>>; - self.write_data(dictionary, cases) - } - Some(FileType::Viewer { .. }) => { - let mut archive = SpvArchive::open_file(&self.input, self.password.as_deref())?; - let (items, page_setup) = archive - .read_outline(|e| eprintln!("{e}"))? - .filter(&self.criteria) - .read(&mut archive, &mut |e| eprintln!("{e}"))? - .into_contents(); - let mut output = self.open_driver("text")?; - if let Some(page_setup) = &page_setup { - output.setup(page_setup); + Some(FileType::Portable) => { + fn warn_portable(warning: pspp::por::Warning) { + eprintln!("warning: {warning}"); + } + + let portable_file = PortableFile::open_file(input, warn_portable)?; + let (dictionary, _, cases) = portable_file.into_parts(); + let cases = cases.map(|result| result.map_err(AnyError::from)); + let cases = Box::new(cases) + as Box>>, AnyError>>>; + data = Some((dictionary, cases)); + } + Some(FileType::Pc) => { + fn warn_pc(warning: pspp::pc::Warning) { + eprintln!("warning: {warning}"); + } + + let pc_file = PcFile::open_file(input, warn_pc)?; + let (dictionary, _, cases) = pc_file.into_parts(); + let cases = cases.map(|result| result.map_err(AnyError::from)); + let cases = Box::new(cases) + as Box>>, AnyError>>>; + data = Some((dictionary, cases)); } - for item in items { - output.write(&Arc::new(item)); + Some(FileType::Viewer { .. }) => { + let mut archive = SpvArchive::open_file(input, self.password.as_deref())?; + let (mut archive_items, archive_page_setup) = archive + .read_outline(|e| eprintln!("{e}"))? + .filter(&self.criteria) + .read(&mut archive, &mut |e| eprintln!("{e}"))? + .into_contents(); + page_setup = page_setup.or(archive_page_setup); + items.append(&mut archive_items); } - Ok(()) + Some(FileType::Graph) => { + let graph = Item::read_graph(input)?; + items.push(graph); + } + Some(other) => bail!("{}: cannot convert {other} file", input.display()), + _ => bail!("{}: unknown input file format", input.display()), + } + } + + if data.is_some() && !items.is_empty() { + bail!("convert: Data and viewer files may not both be specified."); + } + + if let Some((dictionary, cases)) = data { + self.write_data(dictionary, cases) + } else { + let mut output = self.open_driver("text")?; + if let Some(page_setup) = &page_setup { + output.setup(page_setup); } - Some(FileType::Graph) => { - let graph = Item::read_graph(&self.input)?; - let mut output = self.open_driver("text")?; - output.write(&Arc::new(graph)); - Ok(()) + for item in items { + output.write(&Arc::new(item)); } - Some(other) => bail!("{}: cannot convert {other} file", self.input.display()), - _ => bail!("{}: unknown input file format", self.input.display()), + Ok(()) } } } diff --git a/rust/pspp/src/cli/pc.rs b/rust/pspp/src/cli/pc.rs index 84775a1b15..420d8cf17f 100644 --- a/rust/pspp/src/cli/pc.rs +++ b/rust/pspp/src/cli/pc.rs @@ -58,7 +58,7 @@ pub struct Dictionary { max_cases: usize, /// Output driver configuration options. - #[arg(short = 'o')] + #[arg(short = 'O')] output_options: Vec, } @@ -73,7 +73,7 @@ pub struct Metadata { output: Option, /// Output driver configuration options. - #[arg(short = 'o')] + #[arg(short = 'O')] output_options: Vec, } diff --git a/rust/pspp/src/cli/por.rs b/rust/pspp/src/cli/por.rs index 81fccffdf6..0cadb4b69f 100644 --- a/rust/pspp/src/cli/por.rs +++ b/rust/pspp/src/cli/por.rs @@ -162,7 +162,7 @@ pub struct Metadata { output: Option, /// Output driver configuration options. - #[arg(short = 'o', help_heading = "Output options")] + #[arg(short = 'O', help_heading = "Output options")] output_options: Vec, } @@ -202,7 +202,7 @@ pub struct Histogram { output: Option, /// Output driver configuration options. - #[arg(short = 'o', help_heading = "Output options")] + #[arg(short = 'O', help_heading = "Output options")] output_options: Vec, } diff --git a/rust/pspp/src/cli/show.rs b/rust/pspp/src/cli/show.rs index f3b2791ae2..392cf029b2 100644 --- a/rust/pspp/src/cli/show.rs +++ b/rust/pspp/src/cli/show.rs @@ -106,7 +106,7 @@ pub struct Dictionary { max_cases: u64, /// Output driver configuration options. - #[arg(short = 'o', help_heading = "Output options")] + #[arg(short = 'O', help_heading = "Output options")] output_options: Vec, } @@ -189,7 +189,7 @@ pub struct Encodings { max_cases: u64, /// Output driver configuration options. - #[arg(short = 'o', help_heading = "Output options")] + #[arg(short = 'O', help_heading = "Output options")] output_options: Vec, } @@ -243,7 +243,7 @@ pub struct Raw { max_cases: u64, /// Output driver configuration options. - #[arg(short = 'o', help_heading = "Output options")] + #[arg(short = 'O', help_heading = "Output options")] output_options: Vec, } @@ -288,7 +288,7 @@ pub struct Decoded { encoding: Option<&'static Encoding>, /// Output driver configuration options. - #[arg(short = 'o', help_heading = "Output options")] + #[arg(short = 'O', help_heading = "Output options")] output_options: Vec, }