use std::{path::PathBuf, sync::Arc};
use anyhow::{Error as AnyError, Result, bail};
use clap::Args;
use encoding_rs::Encoding;
use pspp::{
data::{ByteString, Case, Datum},
dictionary::Dictionary,
file::FileType,
output::{Criteria, drivers::Driver},
pc::PcFile,
por::PortableFile,
spv::SpvArchive,
sys::ReadOptions,
};
use super::parse_encoding;
#[derive(Args, Clone, Debug)]
pub struct Convert {
input: PathBuf,
output: Option<PathBuf>,
#[arg(short = 'e', long, value_parser = parse_encoding)]
encoding: Option<&'static Encoding>,
#[clap(short, long)]
password: Option<String>,
#[arg(short = 'c', long = "cases")]
max_cases: Option<usize>,
#[arg(long = "unicode")]
to_unicode: bool,
#[arg(short = 'o')]
output_options: Vec<String>,
#[command(flatten)]
criteria: Criteria,
}
impl Convert {
fn open_driver(&self, default_driver: &str) -> Result<Box<dyn Driver>> {
<dyn Driver>::from_options(self.output.as_ref(), &self.output_options, default_driver)
}
fn write_data(
self,
dictionary: Dictionary,
cases: Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>,
) -> Result<()> {
let cases = cases.take(self.max_cases.unwrap_or(usize::MAX));
let mut output = self.open_driver("csv")?;
if !output.can_write_data_file() {
bail!("Can't write data output to {} driver.", output.name());
}
let mut writer = output.write_data_file(&dictionary)?.unwrap();
for case in cases {
writer.write_case(case?)?;
}
Ok(())
}
pub fn run(self) -> Result<()> {
match FileType::from_file(&self.input)? {
Some(FileType::System { .. }) => {
fn warn(warning: anyhow::Error) {
eprintln!("warning: {warning}");
}
let mut system_file = ReadOptions::new(warn)
.with_encoding(self.encoding)
.with_password(self.password.clone())
.open_file(&self.input)?;
if self.to_unicode {
system_file = system_file.into_unicode();
}
let (dictionary, _, cases) = system_file.into_parts();
let cases = cases.map(|result| result.map_err(AnyError::from));
let cases = Box::new(cases)
as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
self.write_data(dictionary, cases)
}
Some(FileType::Portable) => {
fn warn_portable(warning: pspp::por::Warning) {
eprintln!("warning: {warning}");
}
let portable_file = PortableFile::open_file(&self.input, warn_portable)?;
let (dictionary, _, cases) = portable_file.into_parts();
let cases = cases.map(|result| result.map_err(AnyError::from));
let cases = Box::new(cases)
as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
self.write_data(dictionary, cases)
}
Some(FileType::Pc) => {
fn warn_pc(warning: pspp::pc::Warning) {
eprintln!("warning: {warning}");
}
let pc_file = PcFile::open_file(&self.input, warn_pc)?;
let (dictionary, _, cases) = pc_file.into_parts();
let cases = cases.map(|result| result.map_err(AnyError::from));
let cases = Box::new(cases)
as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
self.write_data(dictionary, cases)
}
Some(FileType::Viewer { .. }) => {
let (items, page_setup) =
SpvArchive::open_file(&self.input, self.password.as_deref())?
.read(|e| eprintln!("{e}"))?
.into_contents();
let mut output = self.open_driver("text")?;
if let Some(page_setup) = &page_setup {
output.setup(page_setup);
}
for item in items {
output.write(&Arc::new(item));
}
Ok(())
}
_ => bail!(
"{}: not a system, portable, or SPSS/PC+ file",
self.input.display()
),
}
}
}