From df71a2e5c7973072b1c64a5863b1e6c05bb0bfb9 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 6 Jul 2025 17:29:52 -0700 Subject: [PATCH] add --no-var-names --- rust/pspp/src/main.rs | 52 ++++++++++++++++++++-------------- rust/pspp/src/sys/cooked.rs | 10 +++---- rust/pspp/src/sys/raw.rs | 4 +-- rust/pspp/src/sys/test.rs | 56 ++++++++++++++++++------------------- 4 files changed, 64 insertions(+), 58 deletions(-) diff --git a/rust/pspp/src/main.rs b/rust/pspp/src/main.rs index b8fffb3c64..c874f5dcd7 100644 --- a/rust/pspp/src/main.rs +++ b/rust/pspp/src/main.rs @@ -39,9 +39,6 @@ enum OutputFormat { /// Comma-separated values using each variable's print format (variable /// names are written as the first line) Csv, - - /// SPSS system file. - Sav, } /// Convert SPSS data files into other formats. @@ -55,11 +52,26 @@ struct Convert { /// Format for output file (if omitted, the intended format is inferred /// based on file extension). + #[arg(short = 'O')] output_format: Option, /// The encoding to use. - #[arg(long, value_parser = parse_encoding)] + #[arg(short = 'e', long, value_parser = parse_encoding)] encoding: Option<&'static Encoding>, + + /// Maximum number of cases to print. + #[arg(short = 'c', long = "cases")] + max_cases: Option, + + #[command(flatten, next_help_heading = "Options for CSV output")] + csv_options: CsvOptions, +} + +#[derive(Args, Clone, Debug)] +struct CsvOptions { + /// Omit writing variable names as the first line of output. + #[arg(long)] + no_var_names: bool, } impl Convert { @@ -88,18 +100,18 @@ impl Convert { None => Box::new(stdout()), }; let mut output = csv::WriterBuilder::new().from_writer(writer); - output.write_record(dictionary.variables.iter().map(|var| var.name.as_str()))?; - - if let Some(cases) = cases { - for case in cases { - output.write_record(case?.into_iter().zip(dictionary.variables.iter()).map( - |(datum, variable)| { - datum - .display(variable.print_format, variable.encoding) - .to_string() - }, - ))?; - } + if !self.csv_options.no_var_names { + output.write_record(dictionary.variables.iter().map(|var| var.name.as_str()))?; + } + + for (_case_number, case) in (0..self.max_cases.unwrap_or(u64::MAX)).zip(cases) { + output.write_record(case?.into_iter().zip(dictionary.variables.iter()).map( + |(datum, variable)| { + datum + .display(variable.print_format, variable.encoding) + .to_string() + }, + ))?; } Ok(()) } @@ -200,10 +212,8 @@ fn dissect( let header = header?; println!("{:?}", header); } - if let Some(cases) = reader.cases() { - for (_index, case) in (0..max_cases).zip(cases) { - println!("{:?}", case?); - } + for (_index, case) in (0..max_cases).zip(reader.cases()) { + println!("{:?}", case?); } } Mode::Decoded => { @@ -242,7 +252,7 @@ fn dissect( } let headers = Headers::new(decoded_records, &mut |e| eprintln!("{e}"))?; let (dictionary, metadata, _cases) = - headers.decode(None, encoding, |e| eprintln!("{e}"))?; + headers.decode(reader.cases(), encoding, |e| eprintln!("{e}"))?; println!("{dictionary:#?}"); println!("{metadata:#?}"); } diff --git a/rust/pspp/src/sys/cooked.rs b/rust/pspp/src/sys/cooked.rs index 36c3e80b99..db32a83e0a 100644 --- a/rust/pspp/src/sys/cooked.rs +++ b/rust/pspp/src/sys/cooked.rs @@ -457,10 +457,10 @@ impl Headers { pub fn decode( mut self, - mut cases: Option, + mut cases: Cases, encoding: &'static Encoding, mut warn: impl FnMut(Error), - ) -> Result<(Dictionary, Metadata, Option), Error> { + ) -> Result<(Dictionary, Metadata, Cases), Error> { let mut dictionary = Dictionary::new(encoding); let file_label = fix_line_ends(self.header.file_label.trim_end_matches(' ')); @@ -800,9 +800,7 @@ impl Headers { variable.short_names = short_names; variable.resize(width); } - cases = cases - .take() - .map(|cases| cases.with_widths(dictionary.variables.iter().map(|var| var.width))); + cases = cases.with_widths(dictionary.variables.iter().map(|var| var.width)); } if self.long_names.is_empty() { @@ -973,7 +971,7 @@ impl Headers { let metadata = Metadata::decode(&self, warn); if let Some(n_cases) = metadata.n_cases { - cases = cases.take().map(|cases| cases.with_expected_cases(n_cases)) + cases = cases.with_expected_cases(n_cases); } Ok((dictionary, metadata, cases)) } diff --git a/rust/pspp/src/sys/raw.rs b/rust/pspp/src/sys/raw.rs index 94067aaafa..70ea9f3cdc 100644 --- a/rust/pspp/src/sys/raw.rs +++ b/rust/pspp/src/sys/raw.rs @@ -1065,8 +1065,8 @@ where pub fn headers<'b>(&'b mut self) -> ReadHeaders<'a, 'b, R> { ReadHeaders(self) } - pub fn cases(self) -> Option { - self.cases + pub fn cases(self) -> Cases { + self.cases.unwrap_or_default() } } diff --git a/rust/pspp/src/sys/test.rs b/rust/pspp/src/sys/test.rs index 304bea70b6..1e083bd47d 100644 --- a/rust/pspp/src/sys/test.rs +++ b/rust/pspp/src/sys/test.rs @@ -620,39 +620,37 @@ fn test_sysfile(sysfile: Vec, expected: &str, expected_filename: &Path) { if let Some(pt) = dictionary.output_variable_sets().to_pivot_table() { output.push(Arc::new(pt.into())); } - if let Some(cases) = cases { - let variables = Group::new("Variable") - .with_multiple(dictionary.variables.iter().map(|var| &**var)); - let mut case_numbers = Group::new("Case").with_label_shown(); - let mut data = Vec::new(); - for case in cases { - match case { - Ok(case) => { - case_numbers - .push(Value::new_integer(Some((case_numbers.len() + 1) as f64))); - data.push( - case.into_iter() - .map(|datum| Value::new_datum(&datum, dictionary.encoding)) - .collect::>(), - ); - } - Err(error) => { - output.push(Arc::new(Item::from(Text::new_log(error.to_string())))); - } + let variables = + Group::new("Variable").with_multiple(dictionary.variables.iter().map(|var| &**var)); + let mut case_numbers = Group::new("Case").with_label_shown(); + let mut data = Vec::new(); + for case in cases { + match case { + Ok(case) => { + case_numbers + .push(Value::new_integer(Some((case_numbers.len() + 1) as f64))); + data.push( + case.into_iter() + .map(|datum| Value::new_datum(&datum, dictionary.encoding)) + .collect::>(), + ); + } + Err(error) => { + output.push(Arc::new(Item::from(Text::new_log(error.to_string())))); } } - if !data.is_empty() { - let mut pt = PivotTable::new([ - (Axis3::X, Dimension::new(variables)), - (Axis3::Y, Dimension::new(case_numbers)), - ]); - for (row_number, row) in data.into_iter().enumerate() { - for (column_number, datum) in row.into_iter().enumerate() { - pt.insert(&[column_number, row_number], datum); - } + } + if !data.is_empty() { + let mut pt = PivotTable::new([ + (Axis3::X, Dimension::new(variables)), + (Axis3::Y, Dimension::new(case_numbers)), + ]); + for (row_number, row) in data.into_iter().enumerate() { + for (column_number, datum) in row.into_iter().enumerate() { + pt.insert(&[column_number, row_number], datum); } - output.push(Arc::new(pt.into())); } + output.push(Arc::new(pt.into())); } Item::new(Details::Group(output)) } -- 2.30.2