add --no-var-names rust rust1
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 7 Jul 2025 00:29:52 +0000 (17:29 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 7 Jul 2025 00:29:52 +0000 (17:29 -0700)
rust/pspp/src/main.rs
rust/pspp/src/sys/cooked.rs
rust/pspp/src/sys/raw.rs
rust/pspp/src/sys/test.rs

index b8fffb3c64ad9372c6a1a9e4de66767304f8a25c..c874f5dcd7e6b569c3386d376faa536ffb4d44c7 100644 (file)
@@ -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<OutputFormat>,
 
     /// 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<u64>,
+
+    #[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:#?}");
         }
index 36c3e80b9983b3b01551329cc394a44208f0ed16..db32a83e0af3ec4b06ea104595d0730b60d8b824 100644 (file)
@@ -457,10 +457,10 @@ impl Headers {
 
     pub fn decode(
         mut self,
-        mut cases: Option<Cases>,
+        mut cases: Cases,
         encoding: &'static Encoding,
         mut warn: impl FnMut(Error),
-    ) -> Result<(Dictionary, Metadata, Option<Cases>), 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))
     }
index 94067aaafa1b8b05c41bfeea62f2d70f39f1368b..70ea9f3cdc43dbae6ad5b8115ec6acb74ec347e4 100644 (file)
@@ -1065,8 +1065,8 @@ where
     pub fn headers<'b>(&'b mut self) -> ReadHeaders<'a, 'b, R> {
         ReadHeaders(self)
     }
-    pub fn cases(self) -> Option<Cases> {
-        self.cases
+    pub fn cases(self) -> Cases {
+        self.cases.unwrap_or_default()
     }
 }
 
index 304bea70b6168af45c5ce274591ff188a6aa7ba2..1e083bd47d6e2b67ff867666e4106565fe10ab3d 100644 (file)
@@ -620,39 +620,37 @@ fn test_sysfile(sysfile: Vec<u8>, 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::<Vec<_>>(),
-                            );
-                        }
-                        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::<Vec<_>>(),
+                        );
+                    }
+                    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))
         }