Details, Item, Text,
},
sys::{
- cooked::ReaderOptions,
+ cooked::ReadOptions,
raw::{self, ErrorDetails},
sack::sack,
},
#[test]
fn encrypted_file_without_password() {
- let error = ReaderOptions::new()
+ let error = ReadOptions::new()
.open_file("src/crypto/testdata/test-encrypted.sav", |_| {
panic!();
})
R: Read + Seek + 'static,
{
let mut warnings = Vec::new();
- let output = match ReaderOptions::new().open_reader(sysfile, |warning| warnings.push(warning)) {
+ let output = match ReadOptions::new().open_reader(sysfile, |warning| warnings.push(warning)) {
Ok(system_file) => {
let (dictionary, metadata, cases) = system_file.into_parts();
let (group, data) = metadata.to_pivot_rows();
-#![allow(dead_code, missing_docs)]
+#![allow(dead_code)]
use core::f64;
use std::{
borrow::Cow,
collections::HashMap,
fmt::Write as _,
fs::File,
- io::{Cursor, Seek, Write},
+ io::{BufWriter, Cursor, Seek, Write},
path::Path,
};
V3,
}
+/// Options for writing a system file.
#[derive(Copy, Clone, Debug)]
pub struct WriteOptions {
/// How to compress (if at all) data in the system file.
}
impl WriteOptions {
+ /// Constructs a new set of default options.
pub fn new() -> Self {
Self::default()
}
+
+ /// Returns `self` with the compression format set to `compression`.
pub fn with_compression(self, compression: Option<Compression>) -> Self {
Self {
compression,
..self
}
}
+
+ /// Returns `self` with the version set to `version`.
pub fn with_version(self, version: Version) -> Self {
Self { version, ..self }
}
+
+ /// Writes `dictionary` to `path` in system file format. Returns a [Writer]
+ /// that can be used for writing cases to the new file.
pub fn write_file(
self,
dictionary: &Dictionary,
path: impl AsRef<Path>,
- ) -> Result<Writer<File>, BinError> {
- self.write_writer(dictionary, File::create(path)?)
+ ) -> Result<Writer<BufWriter<File>>, BinError> {
+ self.write_writer(dictionary, BufWriter::new(File::create(path)?))
}
+
+ /// Writes `dictionary` to `writer` in system file format. Returns a
+ /// [Writer] that can be used for writing cases to the new file.
pub fn write_writer<W>(
self,
dictionary: &Dictionary,
}
}
+const BIAS: f64 = 100.0;
+
impl<'a, W> DictionaryWriter<'a, W>
where
W: Write + Seek,
self.write_header()?;
self.write_variables()?;
self.write_value_labels()?;
+ self.write_documents()?;
self.write_integer_record()?;
self.write_float_record()?;
self.write_var_sets()?;
0
},
n_cases: u32::MAX,
- bias: 100.0,
+ bias: BIAS,
creation_date: as_byte_array(now.format("%d %b %y").to_string()),
creation_time: as_byte_array(now.format("%H:%M:%S").to_string()),
file_label: as_byte_array(self.dictionary.file_label.clone().unwrap_or_default()),
Ok(())
}
- pub fn write_documents(&mut self) -> Result<(), BinError> {
+ fn write_documents(&mut self) -> Result<(), BinError> {
if !self.dictionary.documents.is_empty() {
(6u32, self.dictionary.documents.len() as u32).write_le(self.writer)?;
for line in &self.dictionary.documents {
pub struct Writer<W> {
compression: Option<Compression>,
case_vars: Vec<CaseVar>,
+ opcodes: Vec<u8>,
+ data: Vec<u8>,
inner: W,
}
Self {
compression: options.compression,
case_vars,
+ opcodes: Vec::with_capacity(8),
+ data: Vec::with_capacity(64),
inner,
}
}
where
W: Write + Seek,
{
+ /// Writes `case` to the system file.
pub fn write_case(&mut self, case: &Case) -> Result<(), BinError> {
match self.compression {
- Some(_) => todo!(),
+ Some(Compression::Simple) => self.write_case_compressed(case),
+ Some(Compression::ZLib) => todo!(),
None => self.write_case_uncompressed(case),
}
}
}
Ok(())
}
+ fn flush_compressed(
+ opcodes: &mut Vec<u8>,
+ data: &mut Vec<u8>,
+ inner: &mut W,
+ ) -> Result<(), BinError> {
+ if !opcodes.is_empty() {
+ opcodes.resize(8, 0);
+ inner.write_all(opcodes)?;
+ inner.write(data)?;
+ opcodes.clear();
+ data.clear();
+ }
+ Ok(())
+ }
+ fn put_opcode(
+ opcodes: &mut Vec<u8>,
+ data: &mut Vec<u8>,
+ inner: &mut W,
+ opcode: u8,
+ ) -> Result<(), BinError> {
+ if opcodes.len() >= 8 {
+ Self::flush_compressed(opcodes, data, inner)?;
+ }
+ opcodes.push(opcode);
+ Ok(())
+ }
+ fn write_case_compressed(&mut self, case: &Case) -> Result<(), BinError> {
+ for (var, datum) in zip_eq(&self.case_vars, &case.0) {
+ match var {
+ CaseVar::Numeric => match datum.as_number().unwrap() {
+ None => {
+ Self::put_opcode(&mut self.opcodes, &mut self.data, &mut self.inner, 255)?
+ }
+ Some(number) => {
+ if number >= 1.0 - BIAS
+ && number <= 251.0 - BIAS
+ && number == number.trunc()
+ {
+ Self::put_opcode(
+ &mut self.opcodes,
+ &mut self.data,
+ &mut self.inner,
+ (number + BIAS) as u8,
+ )?
+ } else {
+ Self::put_opcode(
+ &mut self.opcodes,
+ &mut self.data,
+ &mut self.inner,
+ 253,
+ )?;
+
+ number.write_le(&mut Cursor::new(&mut self.data)).unwrap();
+ }
+ }
+ },
+
+ CaseVar::String { width: _, encoding } => todo!(),
+ }
+ }
+ Ok(())
+ }
}