clippy
[pspp] / rust / src / raw.rs
index ac8b3a057027cc6649a39c99d294014102da7cec..345a719e47aa635b79f997f4d68aa208ed3d3700 100644 (file)
@@ -4,7 +4,9 @@ use encoding_rs::mem::decode_latin1;
 use flate2::read::ZlibDecoder;
 use num::Integer;
 use std::borrow::Cow;
+use std::cmp::Ordering;
 use std::fmt::{Debug, Formatter, Result as FmtResult};
+use std::ops::Range;
 use std::str::from_utf8;
 use std::{
     collections::VecDeque,
@@ -41,8 +43,8 @@ pub enum Error {
     #[error("At offset {offset:#x}, unrecognized record type {rec_type}.")]
     BadRecordType { offset: u64, rec_type: u32 },
 
-    #[error("At offset {offset:#x}, variable label code ({code}) is not 0 or 1.")]
-    BadVariableLabelCode { offset: u64, code: u32 },
+    #[error("In variable record starting at offset {start_offset:#x}, variable label code {code} at offset {code_offset:#x} is not 0 or 1.")]
+    BadVariableLabelCode { start_offset: u64, code_offset: u64, code: u32 },
 
     #[error(
         "At offset {offset:#x}, numeric missing value code ({code}) is not -3, -2, 0, 1, 2, or 3."
@@ -173,7 +175,7 @@ impl Record {
 
 // If `s` is valid UTF-8, returns it decoded as UTF-8, otherwise returns it
 // decoded as Latin-1 (actually bytes interpreted as Unicode code points).
-fn default_decode<'a>(s: &'a [u8]) -> Cow<'a, str> {
+fn default_decode<>(s: &[u8]) -> Cow<str> {
     from_utf8(s).map_or_else(|_| decode_latin1(s), Cow::from)
 }
 
@@ -183,8 +185,15 @@ pub enum Compression {
     ZLib,
 }
 
+trait Header {
+    fn offsets(&self) -> Range<u64>;
+}
+
 #[derive(Clone)]
 pub struct HeaderRecord {
+    /// Offset in file.
+    pub offsets: Range<u64>,
+
     /// Magic number.
     pub magic: Magic,
 
@@ -235,22 +244,24 @@ impl Debug for HeaderRecord {
     fn fmt(&self, f: &mut Formatter) -> FmtResult {
         writeln!(f, "File header record:")?;
         self.debug_field(f, "Magic", self.magic)?;
-        self.debug_field(f, "Product name", &self.eye_catcher)?;
+        self.debug_field(f, "Product name", self.eye_catcher)?;
         self.debug_field(f, "Layout code", self.layout_code)?;
         self.debug_field(f, "Nominal case size", self.nominal_case_size)?;
         self.debug_field(f, "Compression", self.compression)?;
         self.debug_field(f, "Weight index", self.weight_index)?;
         self.debug_field(f, "Number of cases", self.n_cases)?;
         self.debug_field(f, "Compression bias", self.bias)?;
-        self.debug_field(f, "Creation date", &self.creation_date)?;
-        self.debug_field(f, "Creation time", &self.creation_time)?;
-        self.debug_field(f, "File label", &self.file_label)?;
+        self.debug_field(f, "Creation date", self.creation_date)?;
+        self.debug_field(f, "Creation time", self.creation_time)?;
+        self.debug_field(f, "File label", self.file_label)?;
         self.debug_field(f, "Endianness", self.endian)
     }
 }
 
 impl HeaderRecord {
-    fn read<R: Read>(r: &mut R) -> Result<HeaderRecord, Error> {
+    fn read<R: Read + Seek>(r: &mut R) -> Result<HeaderRecord, Error> {
+        let start = r.stream_position()?;
+
         let magic: [u8; 4] = read_bytes(r)?;
         let magic: Magic = magic.try_into().map_err(|_| Error::NotASystemFile)?;
 
@@ -288,6 +299,7 @@ impl HeaderRecord {
         let _: [u8; 3] = read_bytes(r)?;
 
         Ok(HeaderRecord {
+            offsets: start..r.stream_position()?,
             magic,
             layout_code,
             nominal_case_size,
@@ -304,6 +316,12 @@ impl HeaderRecord {
     }
 }
 
+impl Header for HeaderRecord {
+    fn offsets(&self) -> Range<u64> {
+        self.offsets.clone()
+    }
+}
+
 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
 pub struct Magic([u8; 4]);
 
@@ -321,10 +339,10 @@ impl Magic {
 
 impl Debug for Magic {
     fn fmt(&self, f: &mut Formatter) -> FmtResult {
-        let s = match self {
-            &Magic::SAV => "$FL2",
-            &Magic::ZSAV => "$FL3",
-            &Magic::EBCDIC => "($FL2 in EBCDIC)",
+        let s = match *self {
+            Magic::SAV => "$FL2",
+            Magic::ZSAV => "$FL3",
+            Magic::EBCDIC => "($FL2 in EBCDIC)",
             _ => return write!(f, "{:?}", self.0),
         };
         write!(f, "{s}")
@@ -846,8 +864,8 @@ impl MissingValues {
 
 #[derive(Clone)]
 pub struct VariableRecord {
-    /// Offset from the start of the file to the start of the record.
-    pub offset: u64,
+    /// Range of offsets in file.
+    pub offsets: Range<u64>,
 
     /// Variable width, in the range -1..=255.
     pub width: i32,
@@ -874,12 +892,10 @@ impl Debug for VariableRecord {
             f,
             "Width: {} ({})",
             self.width,
-            if self.width > 0 {
-                "string"
-            } else if self.width == 0 {
-                "numeric"
-            } else {
-                "long string continuation record"
+            match self.width.cmp(&0) {
+                Ordering::Greater => "string",
+                Ordering::Equal => "numeric",
+                Ordering::Less => "long string continuation record",
             }
         )?;
         writeln!(f, "Print format: {:?}", self.print_format)?;
@@ -892,8 +908,9 @@ impl Debug for VariableRecord {
 
 impl VariableRecord {
     fn read<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<VariableRecord, Error> {
-        let offset = r.stream_position()?;
+        let start_offset = r.stream_position()?;
         let width: i32 = endian.parse(read_bytes(r)?);
+        let code_offset = r.stream_position()?;
         let has_variable_label: u32 = endian.parse(read_bytes(r)?);
         let missing_value_code: i32 = endian.parse(read_bytes(r)?);
         let print_format = Spec(endian.parse(read_bytes(r)?));
@@ -914,16 +931,19 @@ impl VariableRecord {
             }
             _ => {
                 return Err(Error::BadVariableLabelCode {
-                    offset,
+                    start_offset,
+                    code_offset,
                     code: has_variable_label,
                 })
             }
         };
 
-        let missing_values = MissingValues::read(r, offset, width, missing_value_code, endian)?;
+        let missing_values = MissingValues::read(r, start_offset, width, missing_value_code, endian)?;
+
+        let end_offset = r.stream_position()?;
 
         Ok(VariableRecord {
-            offset,
+            offsets: start_offset..end_offset,
             width,
             name,
             print_format,
@@ -1219,13 +1239,13 @@ pub enum MultipleResponseType {
 
 impl MultipleResponseType {
     fn parse(input: &[u8]) -> Result<(MultipleResponseType, &[u8]), Error> {
-        let (mr_type, input) = match input.get(0) {
+        let (mr_type, input) = match input.first() {
             Some(b'C') => (MultipleResponseType::MultipleCategory, &input[1..]),
             Some(b'D') => {
                 let (value, input) = parse_counted_string(&input[1..])?;
                 (
                     MultipleResponseType::MultipleDichotomy {
-                        value: value.into(),
+                        value,
                         labels: CategoryLabels::VarLabels,
                     },
                     input,
@@ -1246,7 +1266,7 @@ impl MultipleResponseType {
                 let (value, input) = parse_counted_string(input)?;
                 (
                     MultipleResponseType::MultipleDichotomy {
-                        value: value.into(),
+                        value,
                         labels,
                     },
                     input,
@@ -1273,12 +1293,12 @@ impl MultipleResponseSet {
         };
         let (name, input) = input.split_at(equals);
         let (mr_type, input) = MultipleResponseType::parse(input)?;
-        let Some(b' ') = input.get(0) else {
+        let Some(b' ') = input.first() else {
             return Err(Error::TBD);
         };
         let (label, mut input) = parse_counted_string(&input[1..])?;
         let mut vars = Vec::new();
-        while input.get(0) == Some(&b' ') {
+        while input.first() == Some(&b' ') {
             input = &input[1..];
             let Some(length) = input.iter().position(|b| b" \n".contains(b)) else {
                 return Err(Error::TBD);
@@ -1288,16 +1308,16 @@ impl MultipleResponseSet {
             }
             input = &input[length..];
         }
-        if input.get(0) != Some(&b'\n') {
+        if input.first() != Some(&b'\n') {
             return Err(Error::TBD);
         }
-        while input.get(0) == Some(&b'\n') {
+        while input.first() == Some(&b'\n') {
             input = &input[1..];
         }
         Ok((
             MultipleResponseSet {
                 name: name.into(),
-                label: label.into(),
+                label,
                 mr_type,
                 short_names: vars,
             },