sys::{
encoding::{default_encoding, get_encoding, Error as EncodingError},
raw::records::{
- Compression, DocumentRecord, EncodingRecord, Extension, FileAttributesRecord,
- FloatInfoRecord, HeaderRecord, IntegerInfoRecord, LongNamesRecord,
- LongStringMissingValueRecord, LongStringValueLabelRecord, MultipleResponseRecord,
+ AttributeWarning, Compression, DocumentRecord, EncodingRecord, Extension,
+ ExtensionWarning, FileAttributesRecord, FloatInfoRecord, HeaderRecord, HeaderWarning,
+ IntegerInfoRecord, LongNameWarning, LongNamesRecord, LongStringMissingValueRecord,
+ LongStringMissingValuesWarning, LongStringValueLabelRecord,
+ LongStringValueLabelWarning, MultipleResponseRecord, MultipleResponseWarning,
NumberOfCasesRecord, ProductInfoRecord, RawDocumentLine, RawFileAttributesRecord,
RawLongNamesRecord, RawProductInfoRecord, RawVariableAttributesRecord,
- RawVariableSetRecord, RawVeryLongStringsRecord, ValueLabelRecord, VarDisplayRecord,
- VariableAttributesRecord, VariableRecord, VariableSetRecord, VeryLongStringsRecord,
- ZHeader, ZTrailer,
+ RawVariableSetRecord, RawVeryLongStringsRecord, ValueLabelRecord, ValueLabelWarning,
+ VarDisplayRecord, VariableAttributesRecord, VariableDisplayWarning, VariableRecord,
+ VariableSetRecord, VariableSetWarning, VariableWarning, VeryLongStringWarning,
+ VeryLongStringsRecord, ZHeader, ZTrailer, ZlibTrailerWarning,
},
},
};
use encoding_rs::Encoding;
use flate2::read::ZlibDecoder;
-use itertools::Itertools;
use smallvec::SmallVec;
use std::{
borrow::Cow,
}
}
-/// A warning for a file header.
-#[derive(ThisError, Debug)]
-pub enum HeaderWarning {
- /// Unexpected compression bias.
- #[error("Compression bias is {0} instead of the usual values of 0 or 100.")]
- UnexpectedBias(f64),
-}
-
-/// Warning for a variable record.
-#[derive(ThisError, Debug)]
-pub enum VariableWarning {
- /// Missing value record with range not allowed for string variable.
- #[error("Missing value record with range not allowed for string variable.")]
- MissingValueStringRange,
-
- /// Missing value not allowed for long string continuation.
- #[error("Missing value not allowed for long string continuation")]
- MissingValueContinuation,
-}
-
-/// Warning for an extension record.
-#[derive(ThisError, Debug)]
-pub enum ExtensionWarning {
- /// Unexpected end of data.
- #[error("Unexpected end of data.")]
- UnexpectedEndOfData,
-
- /// Invalid record size.
- #[error("{record} has bad size {size} bytes instead of the expected {expected_size}.")]
- BadRecordSize {
- /// Name of the record.
- record: &'static str,
- /// Size of the elements in the record, in bytes.
- size: u32,
- /// Expected size of the elements in the record, in bytes.
- expected_size: u32,
- },
-
- /// Invalid record count.
- #[error("{record} has bad count {count} instead of the expected {expected_count}.")]
- BadRecordCount {
- /// Name of the record.
- record: &'static str,
- /// Number of elements in the record.
- count: u32,
- /// Expected number of elements in the record.
- expected_count: u32,
- },
-}
-
-/// Warning for a value label record.
-#[derive(ThisError, Debug)]
-pub enum ValueLabelWarning {
- /// At least one valid variable index for value labels is required but none were specified.
- #[error("At least one valid variable index is required but none were specified.")]
- NoVarIndexes,
-
- /// Mixed variable types in value label record.
- #[error("First variable index is for a {var_type} variable but the following variable indexes are for {} variables: {wrong_types:?}", !var_type)]
- MixedVarTypes {
- /// Variable type.
- var_type: VarType,
- /// Indexes of variables with the other type.
- wrong_types: Vec<u32>,
- },
-
- /// Value label invalid variable indexes.
- #[error(
- "One or more variable indexes were not in the valid range [1,{max}] or referred to string continuations: {invalid:?}"
- )]
- InvalidVarIndexes {
- /// Maximum variable index.
- max: usize,
- /// Invalid variable indexes.
- invalid: Vec<u32>,
- },
-}
-
-/// Warning for a long string missing value record.
-#[derive(ThisError, Debug)]
-pub enum LongStringMissingValuesWarning {
- /// Invalid value length.
- #[error("Value length at offset {offset:#x} is {value_len} instead of the expected 8.")]
- BadValueLength {
- /// Offset of the value length.
- offset: u64,
- /// Actual value length.
- value_len: u32,
- },
-
- /// Invalid variable name.
- #[error("Invalid variable name. {0}")]
- InvalidVariableName(
- /// Variable name error.
- IdError,
- ),
-}
-
-/// Warning for a long string value label record.
-#[derive(ThisError, Debug)]
-pub enum LongStringValueLabelWarning {
- /// Invalid variable name.
- #[error("Invalid variable name. {0}")]
- InvalidVariableName(
- /// Variable name error.
- IdError,
- ),
-}
-
-/// Warning for a long variable name record.
-#[derive(ThisError, Debug)]
-pub enum LongNameWarning {
- /// Missing `=`.
- #[error("Missing `=` separator.")]
- LongNameMissingEquals,
-
- /// Invalid short name.
- #[error("Invalid short name. {0}")]
- InvalidShortName(
- /// Short variable name error.
- IdError,
- ),
-
- /// Invalid long name.
- #[error("Invalid long name. {0}")]
- InvalidLongName(
- /// Long variable name error.
- IdError,
- ),
-}
-
-/// Warning for a very long string variable record.
-#[derive(ThisError, Debug)]
-pub enum VeryLongStringWarning {
- /// Invalid variable name.
- #[error("Invalid variable name. {0}")]
- InvalidLongStringName(
- /// Variable name error.
- IdError,
- ),
-
- /// Missing delimiter.
- #[error("Missing delimiter in {0:?}.")]
- VeryLongStringMissingDelimiter(String),
-
- /// Invalid length.
- #[error("Invalid length in {0:?}.")]
- VeryLongStringInvalidLength(
- /// Length.
- String,
- ),
-}
-
-/// Warning for a multiple response set record.
-#[derive(ThisError, Debug)]
-pub enum MultipleResponseWarning {
- /// Invalid multiple response set name.
- #[error("Invalid multiple response set name. {0}")]
- InvalidMrSetName(
- /// Variable name error.
- IdError,
- ),
-
- /// Invalid variable name.
- #[error("Invalid variable name. {0}")]
- InvalidMrSetVariableName(
- /// Variable name error.
- IdError,
- ),
-
- /// Invalid multiple dichotomy label type.
- #[error("Invalid multiple dichotomy label type.")]
- InvalidMultipleDichotomyLabelType,
-
- /// Invalid multiple response type.
- #[error("Invalid multiple response type.")]
- InvalidMultipleResponseType,
-
- /// Syntax error.
- #[error("Syntax error ({0}).")]
- MultipleResponseSyntaxError(
- /// Detailed error.
- &'static str,
- ),
-
- /// Syntax error parsing counted string (missing trailing space).
- #[error("Syntax error parsing counted string (missing trailing space).")]
- CountedStringMissingSpace,
-
- /// Syntax error parsing counted string (invalid UTF-8).
- #[error("Syntax error parsing counted string (invalid UTF-8).")]
- CountedStringInvalidUTF8,
-
- /// Syntax error parsing counted string (invalid length).
- #[error("Syntax error parsing counted string (invalid length {0:?}).")]
- CountedStringInvalidLength(
- /// Length.
- String,
- ),
-
- /// Syntax error parsing counted string (length goes past end of input).
- #[error("Syntax error parsing counted string (length {0:?} goes past end of input).")]
- CountedStringTooLong(
- /// Length.
- usize,
- ),
-}
-
-/// Warning for a file or variable attribute record.
-#[derive(ThisError, Debug)]
-pub enum AttributeWarning {
- /// Invalid attribute name.
- #[error("Invalid attribute name. {0}")]
- InvalidAttributeName(
- /// Attribute name error.
- IdError,
- ),
-
- /// Invalid variable name in attribute record.
- #[error("Invalid variable name in attribute record. {0}")]
- InvalidAttributeVariableName(
- /// Variable name error.
- IdError,
- ),
-
- /// Attribute record missing left parenthesis.
- #[error("Attribute record missing left parenthesis, in {0:?}.")]
- AttributeMissingLParen(
- /// Bad syntax.
- String,
- ),
-
- /// Attribute lacks value.
- #[error("Attribute for {name}[{}] lacks value.", index + 1)]
- AttributeMissingValue {
- /// Attribute name.
- name: Identifier,
- /// 0-based index.
- index: usize,
- },
-
- /// Attribute missing quotations.
- #[error("Attribute for {name}[{}] missing quotations.", index + 1)]
- AttributeMissingQuotes {
- /// Attribute name.
- name: Identifier,
- /// 0-based index.
- index: usize,
- },
-
- /// Variable attribute missing `:`.
- #[error("Variable attribute missing `:`.")]
- VariableAttributeMissingColon,
-
- /// Duplicate attributes for variable.
- #[error("Duplicate attributes for variable {variable}: {}.", attributes.iter().join(", "))]
- DuplicateVariableAttributes {
- /// Variable name.
- variable: Identifier,
- /// Attributes with duplicates.
- attributes: Vec<Identifier>,
- },
-
- /// Duplicate dataset attributes.
- #[error("Duplicate dataset attributes with names: {}.", attributes.iter().join(", "))]
- DuplicateFileAttributes {
- /// Attributes with duplicates.
- attributes: Vec<Identifier>,
- },
-
- /// File attributes record contains trailing garbage.
- #[error("File attributes record contains trailing garbage.")]
- FileAttributesTrailingGarbage,
-}
-
-/// Warning for a variable display record.
-#[derive(ThisError, Debug)]
-pub enum VariableDisplayWarning {
- /// Wrong number of variable display items.
- #[error("Record contains {count} items but should contain either {first} or {second}.")]
- InvalidVariableDisplayCount {
- /// Actual count.
- count: usize,
- /// First valid count.
- first: usize,
- /// Second valid count.
- second: usize,
- },
-
- /// Invalid variable measurement level value.
- #[error("Invalid variable measurement level value {0}.")]
- InvalidMeasurement(
- /// Invalid value.
- u32,
- ),
-
- /// Invalid variable display alignment value.
- #[error("Invalid variable display alignment value {0}.")]
- InvalidAlignment(
- /// Invalid value.
- u32,
- ),
-}
-
-/// Warning for a variable sets record.
-#[derive(ThisError, Debug)]
-pub enum VariableSetWarning {
- /// Invalid variable name.
- #[error("Invalid variable name. {0}")]
- InvalidVariableSetName(
- /// Variable name error.
- IdError,
- ),
-
- /// Missing name delimiter.
- #[error("Missing name delimiter.")]
- VariableSetMissingEquals,
-}
-
-/// Warning for a ZLIB trailer record.
-#[derive(ThisError, Debug)]
-pub enum ZlibTrailerWarning {
- /// Wrong block size.
- #[error(
- "ZLIB block descriptor {index} reported block size {actual:#x}, when {expected:#x} was expected."
- )]
- ZlibTrailerBlockWrongSize {
- /// 0-based block descriptor index.
- index: usize,
- /// Actual block size.
- actual: u32,
- /// Expected block size.
- expected: u32,
- },
-
- /// Block too big.
- #[error(
- "ZLIB block descriptor {index} reported block size {actual:#x}, when at most {max_expected:#x} was expected."
- )]
- ZlibTrailerBlockTooBig {
- /// 0-based block descriptor index.
- index: usize,
- /// Actual block size.
- actual: u32,
- /// Maximum expected block size.
- max_expected: u32,
- },
-}
-
/// A raw record in a system file.
#[allow(missing_docs)] // Don't warn for missing docs on tuple members.
#[derive(Clone, Debug)]
endian::{Endian, Parse},
identifier::{Error as IdError, Identifier},
sys::raw::{
- read_bytes, read_string, read_vec, AttributeWarning, Decoder, Error, ErrorDetails,
- ExtensionWarning, HeaderWarning, LongNameWarning, LongStringMissingValuesWarning,
- LongStringValueLabelWarning, Magic, MultipleResponseWarning, RawDatum, RawStrArray,
- RawWidth, Record, UntypedDatum, ValueLabelWarning, VarTypes, VariableDisplayWarning,
- VariableSetWarning, VariableWarning, VeryLongStringWarning, Warning, WarningDetails,
- ZlibTrailerWarning,
+ read_bytes, read_string, read_vec, Decoder, Error, ErrorDetails, Magic, RawDatum,
+ RawStrArray, RawWidth, Record, UntypedDatum, VarTypes, Warning, WarningDetails,
},
};
use binrw::BinRead;
+use itertools::Itertools;
+use thiserror::Error as ThisError;
/// Type of compression in a system file.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
ZLib,
}
+/// A warning for a file header.
+#[derive(ThisError, Debug)]
+pub enum HeaderWarning {
+ /// Unexpected compression bias.
+ #[error("Compression bias is {0} instead of the usual values of 0 or 100.")]
+ UnexpectedBias(f64),
+}
+
/// A file header record in a system file.
#[derive(Clone)]
pub struct HeaderRecord<S>
}
}
-/// [crate::format::Format] as represented in a system file.
+/// [Format](crate::format::Format) as represented in a system file.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct RawFormat(
/// The most-significant 16 bits are the type, the next 8 bytes are the
}
}
+/// Warning for a variable record.
+#[derive(ThisError, Debug)]
+pub enum VariableWarning {
+ /// Missing value record with range not allowed for string variable.
+ #[error("Missing value record with range not allowed for string variable.")]
+ MissingValueStringRange,
+
+ /// Missing value not allowed for long string continuation.
+ #[error("Missing value not allowed for long string continuation")]
+ MissingValueContinuation,
+}
+
/// A variable record in a system file.
#[derive(Clone)]
pub struct VariableRecord<S>
}
}
+/// Warning for a value label record.
+#[derive(ThisError, Debug)]
+pub enum ValueLabelWarning {
+ /// At least one valid variable index for value labels is required but none were specified.
+ #[error("At least one valid variable index is required but none were specified.")]
+ NoVarIndexes,
+
+ /// Mixed variable types in value label record.
+ #[error("First variable index is for a {var_type} variable but the following variable indexes are for {} variables: {wrong_types:?}", !var_type)]
+ MixedVarTypes {
+ /// Variable type.
+ var_type: VarType,
+ /// Indexes of variables with the other type.
+ wrong_types: Vec<u32>,
+ },
+
+ /// Value label invalid variable indexes.
+ #[error(
+ "One or more variable indexes were not in the valid range [1,{max}] or referred to string continuations: {invalid:?}"
+ )]
+ InvalidVarIndexes {
+ /// Maximum variable index.
+ max: usize,
+ /// Invalid variable indexes.
+ invalid: Vec<u32>,
+ },
+}
+
/// A value and label in a system file.
#[derive(Clone, Debug)]
pub struct ValueLabel<D, S>
}
}
+/// Warning for a very long string variable record.
+#[derive(ThisError, Debug)]
+pub enum VeryLongStringWarning {
+ /// Invalid variable name.
+ #[error("Invalid variable name. {0}")]
+ InvalidLongStringName(
+ /// Variable name error.
+ IdError,
+ ),
+
+ /// Missing delimiter.
+ #[error("Missing delimiter in {0:?}.")]
+ VeryLongStringMissingDelimiter(String),
+
+ /// Invalid length.
+ #[error("Invalid length in {0:?}.")]
+ VeryLongStringInvalidLength(
+ /// Length.
+ String,
+ ),
+}
+
/// A very long string parsed from a [VeryLongStringsRecord].
#[derive(Clone, Debug)]
pub struct VeryLongString {
impl VeryLongString {
/// Parses a [VeryLongString] from `input` using `decoder`.
- fn parse(decoder: &Decoder, input: &str) -> Result<VeryLongString, WarningDetails> {
+ pub fn parse(decoder: &Decoder, input: &str) -> Result<VeryLongString, WarningDetails> {
let Some((short_name, length)) = input.split_once('=') else {
return Err(VeryLongStringWarning::VeryLongStringMissingDelimiter(input.into()).into());
};
}
}
+/// Warning for a multiple response set record.
+#[derive(ThisError, Debug)]
+pub enum MultipleResponseWarning {
+ /// Invalid multiple response set name.
+ #[error("Invalid multiple response set name. {0}")]
+ InvalidMrSetName(
+ /// Variable name error.
+ IdError,
+ ),
+
+ /// Invalid variable name.
+ #[error("Invalid variable name. {0}")]
+ InvalidMrSetVariableName(
+ /// Variable name error.
+ IdError,
+ ),
+
+ /// Invalid multiple dichotomy label type.
+ #[error("Invalid multiple dichotomy label type.")]
+ InvalidMultipleDichotomyLabelType,
+
+ /// Invalid multiple response type.
+ #[error("Invalid multiple response type.")]
+ InvalidMultipleResponseType,
+
+ /// Syntax error.
+ #[error("Syntax error ({0}).")]
+ MultipleResponseSyntaxError(
+ /// Detailed error.
+ &'static str,
+ ),
+
+ /// Syntax error parsing counted string (missing trailing space).
+ #[error("Syntax error parsing counted string (missing trailing space).")]
+ CountedStringMissingSpace,
+
+ /// Syntax error parsing counted string (invalid UTF-8).
+ #[error("Syntax error parsing counted string (invalid UTF-8).")]
+ CountedStringInvalidUTF8,
+
+ /// Syntax error parsing counted string (invalid length).
+ #[error("Syntax error parsing counted string (invalid length {0:?}).")]
+ CountedStringInvalidLength(
+ /// Length.
+ String,
+ ),
+
+ /// Syntax error parsing counted string (length goes past end of input).
+ #[error("Syntax error parsing counted string (length {0:?} goes past end of input).")]
+ CountedStringTooLong(
+ /// Length.
+ usize,
+ ),
+}
+
/// The type of a multiple-response set.
#[derive(Clone, Debug)]
pub enum MultipleResponseType {
impl MultipleResponseRecord<RawString, RawString> {
/// Parses a multiple-response set from `ext`.
- fn parse(ext: &Extension) -> Result<Record, WarningDetails> {
+ pub fn parse(ext: &Extension) -> Result<Record, WarningDetails> {
ext.check_size(Some(1), None, "multiple response set record")?;
let mut input = &ext.data[..];
Ok((string.into(), rest))
}
+/// Warning for a variable display record.
+#[derive(ThisError, Debug)]
+pub enum VariableDisplayWarning {
+ /// Wrong number of variable display items.
+ #[error("Record contains {count} items but should contain either {first} or {second}.")]
+ InvalidVariableDisplayCount {
+ /// Actual count.
+ count: usize,
+ /// First valid count.
+ first: usize,
+ /// Second valid count.
+ second: usize,
+ },
+
+ /// Invalid variable measurement level value.
+ #[error("Invalid variable measurement level value {0}.")]
+ InvalidMeasurement(
+ /// Invalid value.
+ u32,
+ ),
+
+ /// Invalid variable display alignment value.
+ #[error("Invalid variable display alignment value {0}.")]
+ InvalidAlignment(
+ /// Invalid value.
+ u32,
+ ),
+}
+
impl Measure {
fn try_decode(source: u32) -> Result<Option<Measure>, WarningDetails> {
match source {
}
}
+/// Warning for a long string missing value record.
+#[derive(ThisError, Debug)]
+pub enum LongStringMissingValuesWarning {
+ /// Invalid value length.
+ #[error("Value length at offset {offset:#x} is {value_len} instead of the expected 8.")]
+ BadValueLength {
+ /// Offset of the value length.
+ offset: u64,
+ /// Actual value length.
+ value_len: u32,
+ },
+
+ /// Invalid variable name.
+ #[error("Invalid variable name. {0}")]
+ InvalidVariableName(
+ /// Variable name error.
+ IdError,
+ ),
+}
+
/// Missing values for one long string variable.
#[derive(Clone, Debug)]
pub struct LongStringMissingValues<N>
impl LongStringMissingValueRecord<RawString> {
/// Parses this record from `ext`.
- fn parse(
+ pub fn parse(
ext: &Extension,
endian: Endian,
warn: &mut dyn FnMut(Warning),
impl EncodingRecord {
/// Parses this record from `ext`.
- fn parse(ext: &Extension) -> Result<Record, WarningDetails> {
+ pub fn parse(ext: &Extension) -> Result<Record, WarningDetails> {
ext.check_size(Some(1), None, "encoding record")?;
Ok(Record::Encoding(EncodingRecord(
}
impl NumberOfCasesRecord {
- fn parse(ext: &Extension, endian: Endian) -> Result<Record, WarningDetails> {
+ /// Parses a number of cases record from `ext` using `endian`.
+ pub fn parse(ext: &Extension, endian: Endian) -> Result<Record, WarningDetails> {
ext.check_size(Some(8), Some(2), "extended number of cases record")?;
let mut input = &ext.data[..];
}
}
+/// Warning for a variable sets record.
+#[derive(ThisError, Debug)]
+pub enum VariableSetWarning {
+ /// Invalid variable name.
+ #[error("Invalid variable name. {0}")]
+ InvalidVariableSetName(
+ /// Variable name error.
+ IdError,
+ ),
+
+ /// Missing name delimiter.
+ #[error("Missing name delimiter.")]
+ VariableSetMissingEquals,
+}
+
+/// Raw (text) version of the variable set record in a system file.
#[derive(Clone, Debug)]
pub struct RawVariableSetRecord(TextRecord);
impl RawVariableSetRecord {
- fn parse(extension: Extension) -> Result<Record, WarningDetails> {
+ /// Parses the record from `extension`.
+ pub fn parse(extension: Extension) -> Result<Record, WarningDetails> {
Ok(Record::VariableSets(Self(TextRecord::parse(
extension,
"variable sets record",
)?)))
}
+
+ /// Decodes the record using `decoder`.
pub fn decode(self, decoder: &mut Decoder) -> VariableSetRecord {
let mut sets = Vec::new();
let input = decoder.decode(&self.0.text);
}
}
+/// Raw (text) version of a product info record in a system file.
#[derive(Clone, Debug)]
pub struct RawProductInfoRecord(TextRecord);
impl RawProductInfoRecord {
- fn parse(extension: Extension) -> Result<Record, WarningDetails> {
+ /// Parses the record from `extension`.
+ pub fn parse(extension: Extension) -> Result<Record, WarningDetails> {
Ok(Record::ProductInfo(Self(TextRecord::parse(
extension,
"product info record",
)?)))
}
+
+ /// Decodes the record using `decoder`.
pub fn decode(self, decoder: &mut Decoder) -> ProductInfoRecord {
ProductInfoRecord(decoder.decode(&self.0.text).into())
}
}
+/// Warning for a file or variable attribute record.
+#[derive(ThisError, Debug)]
+pub enum AttributeWarning {
+ /// Invalid attribute name.
+ #[error("Invalid attribute name. {0}")]
+ InvalidAttributeName(
+ /// Attribute name error.
+ IdError,
+ ),
+
+ /// Invalid variable name in attribute record.
+ #[error("Invalid variable name in attribute record. {0}")]
+ InvalidAttributeVariableName(
+ /// Variable name error.
+ IdError,
+ ),
+
+ /// Attribute record missing left parenthesis.
+ #[error("Attribute record missing left parenthesis, in {0:?}.")]
+ AttributeMissingLParen(
+ /// Bad syntax.
+ String,
+ ),
+
+ /// Attribute lacks value.
+ #[error("Attribute for {name}[{}] lacks value.", index + 1)]
+ AttributeMissingValue {
+ /// Attribute name.
+ name: Identifier,
+ /// 0-based index.
+ index: usize,
+ },
+
+ /// Attribute missing quotations.
+ #[error("Attribute for {name}[{}] missing quotations.", index + 1)]
+ AttributeMissingQuotes {
+ /// Attribute name.
+ name: Identifier,
+ /// 0-based index.
+ index: usize,
+ },
+
+ /// Variable attribute missing `:`.
+ #[error("Variable attribute missing `:`.")]
+ VariableAttributeMissingColon,
+
+ /// Duplicate attributes for variable.
+ #[error("Duplicate attributes for variable {variable}: {}.", attributes.iter().join(", "))]
+ DuplicateVariableAttributes {
+ /// Variable name.
+ variable: Identifier,
+ /// Attributes with duplicates.
+ attributes: Vec<Identifier>,
+ },
+
+ /// Duplicate dataset attributes.
+ #[error("Duplicate dataset attributes with names: {}.", attributes.iter().join(", "))]
+ DuplicateFileAttributes {
+ /// Attributes with duplicates.
+ attributes: Vec<Identifier>,
+ },
+
+ /// File attributes record contains trailing garbage.
+ #[error("File attributes record contains trailing garbage.")]
+ FileAttributesTrailingGarbage,
+}
+
+/// A file or variable attribute in a system file.
#[derive(Clone, Debug)]
pub struct Attribute {
+ /// The attribute's name.
pub name: Identifier,
+
+ /// The attribute's values.
pub values: Vec<String>,
}
impl Attribute {
+ /// Parses an attribute from the beginning of `input` using `decoder`. Uses
+ /// `offsets` to report warnings. Returns the decoded attribute and the
+ /// part of `input` that remains to be parsed following the attribute.
fn parse<'a>(
decoder: &mut Decoder,
offsets: &Range<u64>,
}
impl Attributes {
+ /// Parses a set of varaible or file attributes from `input` using
+ /// `decoder`. Uses `offsets` for reporting warnings. If not `None`,
+ /// `sentinel` terminates the attributes. Returns the attributes and the
+ /// part of `input` that remains after parsing the attributes.
fn parse<'a>(
decoder: &mut Decoder,
offsets: &Range<u64>,
}
}
+/// A raw (text) file attributes record in a system file.
#[derive(Clone, Debug)]
pub struct RawFileAttributesRecord(TextRecord);
+/// A decoded file attributes record in a system file.
#[derive(Clone, Debug, Default)]
pub struct FileAttributesRecord(pub Attributes);
impl RawFileAttributesRecord {
- fn parse(extension: Extension) -> Result<Record, WarningDetails> {
+ /// Parses this record from `extension`.
+ pub fn parse(extension: Extension) -> Result<Record, WarningDetails> {
Ok(Record::FileAttributes(Self(TextRecord::parse(
extension,
"file attributes record",
)?)))
}
+
+ /// Decodes this record using `decoder`.
pub fn decode(self, decoder: &mut Decoder) -> FileAttributesRecord {
let input = decoder.decode(&self.0.text);
match Attributes::parse(decoder, &self.0.offsets, &input, None)
}
}
+/// A set of variable attributes in a system file.
#[derive(Clone, Debug)]
pub struct VarAttributes {
+ /// The long name of the variable associated with the attributes.
pub long_var_name: Identifier,
+
+ /// The attributes.
pub attributes: Attributes,
}
impl VarAttributes {
+ /// Parses a variable attribute set from `input` using `decoder`. Uses
+ /// `offsets` for reporting warnings.
fn parse<'a>(
decoder: &mut Decoder,
offsets: &Range<u64>,
},
));
}
- let var_attribute = VarAttributes {
- long_var_name,
- attributes,
- };
- Ok((var_attribute, rest))
+ Ok((
+ VarAttributes {
+ long_var_name,
+ attributes,
+ },
+ rest,
+ ))
}
}
+/// A raw (text) variable attributes record in a system file.
#[derive(Clone, Debug)]
pub struct RawVariableAttributesRecord(TextRecord);
+/// A decoded variable attributes record in a system file.
#[derive(Clone, Debug)]
pub struct VariableAttributesRecord(pub Vec<VarAttributes>);
impl RawVariableAttributesRecord {
- fn parse(extension: Extension) -> Result<Record, WarningDetails> {
+ /// Parses a variable attributes record.
+ pub fn parse(extension: Extension) -> Result<Record, WarningDetails> {
Ok(Record::VariableAttributes(Self(TextRecord::parse(
extension,
"variable attributes record",
)?)))
}
+
+ /// Decodes a variable attributes record using `decoder`.
pub fn decode(self, decoder: &mut Decoder) -> VariableAttributesRecord {
let decoded = decoder.decode(&self.0.text);
let mut input = decoded.as_ref();
}
}
+/// Warning for a long variable name record.
+#[derive(ThisError, Debug)]
+pub enum LongNameWarning {
+ /// Missing `=`.
+ #[error("Missing `=` separator.")]
+ LongNameMissingEquals,
+
+ /// Invalid short name.
+ #[error("Invalid short name. {0}")]
+ InvalidShortName(
+ /// Short variable name error.
+ IdError,
+ ),
+
+ /// Invalid long name.
+ #[error("Invalid long name. {0}")]
+ InvalidLongName(
+ /// Long variable name error.
+ IdError,
+ ),
+}
+
+/// A long variable name in a system file.
#[derive(Clone, Debug)]
pub struct LongName {
+ /// The variable's short name.
pub short_name: Identifier,
+
+ /// The variable's long name.
pub long_name: Identifier,
}
impl LongName {
- fn parse(input: &str, decoder: &Decoder) -> Result<Self, WarningDetails> {
+ /// Parses a long variable name from `input` using `decoder`.
+ pub fn parse(input: &str, decoder: &Decoder) -> Result<Self, WarningDetails> {
let Some((short_name, long_name)) = input.split_once('=') else {
return Err(LongNameWarning::LongNameMissingEquals.into());
};
}
}
+/// A long variable name record in a system file.
#[derive(Clone, Debug)]
pub struct LongNamesRecord(pub Vec<LongName>);
+/// A product info record in a system file.
#[derive(Clone, Debug)]
pub struct ProductInfoRecord(pub String);
+/// A variable set in a system file.
#[derive(Clone, Debug)]
pub struct VariableSet {
+ /// Name of the variable set.
pub name: String,
+
+ /// The long variable names of the members of the set.
pub variable_names: Vec<Identifier>,
}
impl VariableSet {
+ /// Parses a variable set from `input` using `decoder`. Uses `offsets` to
+ /// report warnings.
fn parse(
input: &str,
decoder: &mut Decoder,
}
}
+/// A variable set record in a system file.
#[derive(Clone, Debug)]
pub struct VariableSetRecord {
+ /// Range of file offsets occupied by the record.
pub offsets: Range<u64>,
+
+ /// The variable sets in the record.
pub sets: Vec<VariableSet>,
}
}
}
+/// Warning for an extension record.
+#[derive(ThisError, Debug)]
+pub enum ExtensionWarning {
+ /// Unexpected end of data.
+ #[error("Unexpected end of data.")]
+ UnexpectedEndOfData,
+
+ /// Invalid record size.
+ #[error("{record} has bad size {size} bytes instead of the expected {expected_size}.")]
+ BadRecordSize {
+ /// Name of the record.
+ record: &'static str,
+ /// Size of the elements in the record, in bytes.
+ size: u32,
+ /// Expected size of the elements in the record, in bytes.
+ expected_size: u32,
+ },
+
+ /// Invalid record count.
+ #[error("{record} has bad count {count} instead of the expected {expected_count}.")]
+ BadRecordCount {
+ /// Name of the record.
+ record: &'static str,
+ /// Number of elements in the record.
+ count: u32,
+ /// Expected number of elements in the record.
+ expected_count: u32,
+ },
+}
+
+/// An extension record in a system file.
+///
+/// Most of the records in system files are "extension records". This structure
+/// collects everything in an extension record for later processing.
#[derive(Clone, Debug)]
pub struct Extension {
+ /// File offsets occupied by the extension record.
+ ///
+ /// These are the offsets of the `data` portion of the record, not including
+ /// the header that specifies the subtype, size, and count.
pub offsets: Range<u64>,
/// Record subtype.
}
}
+/// Warning for a long string value label record.
+#[derive(ThisError, Debug)]
+pub enum LongStringValueLabelWarning {
+ /// Invalid variable name.
+ #[error("Invalid variable name. {0}")]
+ InvalidVariableName(
+ /// Variable name error.
+ IdError,
+ ),
+}
+
+/// One set of long string value labels record in a system file.
#[derive(Clone, Debug)]
pub struct LongStringValueLabels<N, S>
where
S: Debug,
{
+ /// The variable being labeled.
pub var_name: N,
+
+ /// The variable's width (greater than 8, since it's a long string).
pub width: u32,
/// `(value, label)` pairs, where each value is `width` bytes.
}
impl LongStringValueLabels<RawString, RawString> {
+ /// Decodes a set of long string value labels using `decoder`.
fn decode(
&self,
decoder: &mut Decoder,
}
}
+/// A long string value labels record in a system file.
#[derive(Clone, Debug)]
pub struct LongStringValueLabelRecord<N, S>
where
N: Debug,
S: Debug,
{
+ /// File offsets occupied by the record.
pub offsets: Range<u64>,
+
+ /// The labels.
pub labels: Vec<LongStringValueLabels<N, S>>,
}
impl LongStringValueLabelRecord<RawString, RawString> {
+ /// Parses this record from `ext` using `endian`.
fn parse(ext: &Extension, endian: Endian) -> Result<Record, WarningDetails> {
ext.check_size(Some(1), None, "long string value labels record")?;
labels: label_set,
}))
}
-}
-impl LongStringValueLabelRecord<RawString, RawString> {
+ /// Decodes this record using `decoder`.
pub fn decode(self, decoder: &mut Decoder) -> LongStringValueLabelRecord<Identifier, String> {
let mut labels = Vec::with_capacity(self.labels.len());
for label in &self.labels {
}
}
+/// ZLIB header, for [Compression::ZLib].
#[derive(Clone, Debug)]
pub struct ZHeader {
/// File offset to the start of the record.
}
impl ZHeader {
- pub fn read<R: Read + Seek>(r: &mut R, endian: Endian) -> Result<ZHeader, Error> {
+ /// Reads a ZLIB header from `r` using `endian`.
+ pub fn read<R>(r: &mut R, endian: Endian) -> Result<ZHeader, Error>
+ where
+ R: Read + Seek,
+ {
let offset = r.stream_position()?;
let zheader_offset: u64 = endian.parse(read_bytes(r)?);
let ztrailer_offset: u64 = endian.parse(read_bytes(r)?);
}
}
+/// A ZLIB trailer in a system file.
#[derive(Clone, Debug)]
pub struct ZTrailer {
/// File offset to the start of the record.
pub blocks: Vec<ZBlock>,
}
+/// Warning for a ZLIB trailer record.
+#[derive(ThisError, Debug)]
+pub enum ZlibTrailerWarning {
+ /// Wrong block size.
+ #[error(
+ "ZLIB block descriptor {index} reported block size {actual:#x}, when {expected:#x} was expected."
+ )]
+ ZlibTrailerBlockWrongSize {
+ /// 0-based block descriptor index.
+ index: usize,
+ /// Actual block size.
+ actual: u32,
+ /// Expected block size.
+ expected: u32,
+ },
+
+ /// Block too big.
+ #[error(
+ "ZLIB block descriptor {index} reported block size {actual:#x}, when at most {max_expected:#x} was expected."
+ )]
+ ZlibTrailerBlockTooBig {
+ /// 0-based block descriptor index.
+ index: usize,
+ /// Actual block size.
+ actual: u32,
+ /// Maximum expected block size.
+ max_expected: u32,
+ },
+}
+
+/// A ZLIB block descriptor in a system file.
#[derive(Clone, Debug)]
pub struct ZBlock {
/// Offset of block of data if simple compression were used.
}
impl ZTrailer {
- pub fn read<R: Read + Seek>(
+ /// Reads a ZLIB trailer from `reader` using `endian`. `bias` is the
+ /// floating-point bias for confirmation against the trailer, and `zheader`
+ /// is the previously read ZLIB header. Uses `warn` to report warnings.
+ pub fn read<R>(
reader: &mut R,
endian: Endian,
bias: f64,
zheader: &ZHeader,
warn: &mut dyn FnMut(Warning),
- ) -> Result<Option<ZTrailer>, Error> {
+ ) -> Result<Option<ZTrailer>, Error>
+ where
+ R: Read + Seek,
+ {
let start_offset = reader.stream_position()?;
if reader
.seek(SeekFrom::Start(zheader.ztrailer_offset))