use clap::{Args, Parser, Subcommand, ValueEnum};
use encoding_rs::Encoding;
use pspp::crypto::EncryptedFile;
-use pspp::sys::cooked::{Error, Headers};
+use pspp::sys::cooked::{Error, Headers, SystemFile};
use pspp::sys::raw::{infer_encoding, Decoder, Magic, Reader, Record, Warning};
use std::fs::File;
use std::io::{stdout, BufReader, Write};
decoded_records.push(header.decode(&mut decoder)?);
}
let headers = Headers::new(decoded_records, &mut |e| Self::err(e))?;
- let (dictionary, _metadata, cases) =
- headers.decode(reader.cases(), decoder.encoding, |e| Self::err(e))?;
+ let SystemFile {
+ dictionary, cases, ..
+ } = headers.decode(reader.cases(), decoder.encoding, |e| Self::err(e));
let writer = match self.output {
Some(path) => Box::new(File::create(path)?) as Box<dyn Write>,
None => Box::new(stdout()),
decoded_records.push(header.decode(&mut decoder)?);
}
let headers = Headers::new(decoded_records, &mut |e| eprintln!("{e}"))?;
- let (dictionary, metadata, _cases) =
- headers.decode(reader.cases(), encoding, |e| eprintln!("{e}"))?;
+ let SystemFile {
+ dictionary,
+ metadata,
+ cases: _,
+ } = headers.decode(reader.cases(), encoding, |e| eprintln!("{e}"));
println!("{dictionary:#?}");
println!("{metadata:#?}");
}
#[error("Using default encoding {0}.")]
UsingDefaultEncoding(String),
- #[error("Variable record from offset {:x} to {:x} specifies width {width} not in valid range [-1,255).", offsets.start, offsets.end)]
+ #[error("Variable record from offset {:#x} to {:#x} specifies width {width} not in valid range [-1,255).", offsets.start, offsets.end)]
InvalidVariableWidth { offsets: Range<u64>, width: i32 },
#[error("This file has corrupted metadata written by a buggy version of PSPP. To ensure that other software can read it correctly, save a new copy of the file.")]
WrongNumberOfVarDisplay { expected: usize, actual: usize },
}
+/// The content of an SPSS system file.
+pub struct SystemFile {
+ /// The system file dictionary.
+ pub dictionary: Dictionary,
+
+ /// System file metadata that is not part of the dictionary.
+ pub metadata: Metadata,
+
+ /// Data in the system file.
+ pub cases: Cases,
+}
+
#[derive(Clone, Debug)]
pub struct Headers {
pub header: HeaderRecord<String>,
mut cases: Cases,
encoding: &'static Encoding,
mut warn: impl FnMut(Error),
- ) -> Result<(Dictionary, Metadata, Cases), Error> {
+ ) -> SystemFile {
let mut dictionary = Dictionary::new(encoding);
let file_label = fix_line_ends(self.header.file_label.trim_end_matches(' '));
if n_segments == 1 {
warn(Error::ShortVeryLongString {
short_name: record.short_name.clone(),
- width: record.length
+ width: record.length,
});
continue;
}
width: record.length,
index,
n_segments,
- len: dictionary.variables.len()
+ len: dictionary.variables.len(),
});
continue;
}
if let Some(n_cases) = metadata.n_cases {
cases = cases.with_expected_cases(n_cases);
}
- Ok((dictionary, metadata, cases))
+ SystemFile {
+ dictionary,
+ metadata,
+ cases,
+ }
}
}
+/// System file metadata that is not part of [Dictionary].
+///
+/// [Dictionary]: crate::dictionary::Dictionary
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Metadata {
+ /// Creation date and time.
+ ///
+ /// This comes from the file header, not from the file system.
pub creation: NaiveDateTime,
+
+ /// Endianness of integers and floating-point numbers in the file.
pub endian: Endian,
+
+ /// Compression type (if any).
pub compression: Option<Compression>,
+
+ /// Number of cases in the file, if it says.
+ ///
+ /// This is not trustworthy: there can be more or fewer.
pub n_cases: Option<u64>,
+
+ /// Name of the product that wrote the file.
pub product: String,
+
+ /// Extended name of the product that wrote the file.
pub product_ext: Option<String>,
+
+ /// Version number of the product that wrote the file.
+ ///
+ /// For example, `(1,2,3)` is version 1.2.3.
pub version: Option<(i32, i32, i32)>,
}
/// are divided into multiple, adjacent string variables, approximately one
/// variable for each 252 bytes.
///
-/// - [Headers::decode](super::cooked::Headers::decode) returns [Cases] in
-/// which each [Dictionary](crate::dictionary::Dictionary) variable
-/// corresponds to one [Datum], even for long string variables.
+/// - In the [Cases] in [SystemFile], each [Dictionary] variable corresponds to
+/// one [Datum], even for long string variables.
+///
+/// [Dictionary]: crate::dictionary::Dictionary
+/// [SystemFile]: crate::sys::cooked::SystemFile
pub struct Cases {
reader: Box<dyn ReadSeek>,
case_vars: Vec<CaseVar>,
Details, Item, Text,
},
sys::{
- cooked::Headers,
+ cooked::{Headers, SystemFile},
raw::{infer_encoding, Decoder, Reader},
sack::sack,
},
let mut errors = Vec::new();
let headers = Headers::new(decoded_records, &mut |e| errors.push(e)).unwrap();
- let (dictionary, metadata, cases) =
- headers.decode(cases, encoding, |e| errors.push(e)).unwrap();
+ let SystemFile {
+ dictionary,
+ metadata,
+ cases,
+ } = headers.decode(cases, encoding, |e| errors.push(e));
let (group, data) = metadata.to_pivot_rows();
let metadata_table = PivotTable::new([(Axis3::Y, Dimension::new(group))]).with_data(
data.into_iter()