use std::{io, thread};
use csv::{ByteRecord, ReaderBuilder, Reader};
use crate::compile::create_transformer;
use crate::transform::{Transformer, Transformation, CellValue, ApplyResult};
use crate::options::Options;
use crate::writer::writer_thread;
type TransformationsChain = Vec<Transformation>;
pub type MaybeTransformationsChain = Result<TransformationsChain, String>;
const QUEUE_SIZE: usize = 10000;
fn apply_transformations_chain(
transformations_chain: &TransformationsChain,
record: &ByteRecord,
line_number: usize,
) -> String {
let apply_result: ApplyResult = transformations_chain.iter().fold(
Ok(CellValue::empty_string()),
|result, transformation| result.and_then(
|cell_value| transformation.apply(
cell_value,
record,
line_number,
)
)
);
if let Ok(cell_value) = apply_result {
cell_value.to_string()
} else {
eprintln!("{}", apply_result.err().unwrap());
String::new()
}
}
fn transform(
record: ByteRecord,
transformer: &Transformer,
line_number: usize,
) -> ByteRecord {
let output: Vec<String> = transformer.columns.iter().map(
|column| apply_transformations_chain(
column,
&record,
line_number,
)
).collect();
ByteRecord::from(output)
}
pub fn process_from_reader<T: io::Read>(
mut reader: Reader<T>,
options: &Options,
start_line_number: usize,
) -> Result<usize, String> {
let headers = reader.headers().unwrap().clone();
let transformer = create_transformer(
&options.config,
&headers,
&options.variables,
)?;
let (tx, rx) = crossbeam_channel::bounded(QUEUE_SIZE);
if start_line_number == 1 {
tx.send(transformer.headers.as_byte_record().clone()).unwrap();
}
let writer_handle = thread::spawn(move || writer_thread(rx));
let mut current_line_number = start_line_number.clone();
for (line_number, result) in reader.byte_records().enumerate() {
let record = result.unwrap();
current_line_number = start_line_number + line_number;
tx.send(transform(
record,
&transformer,
current_line_number,
)).unwrap();
}
drop(tx);
writer_handle.join().unwrap();
Ok(current_line_number + 1)
}
fn process_from_stdin(options: Options) -> Result<(), String> {
let reader = ReaderBuilder::new()
.flexible(true)
.from_reader(io::stdin());
process_from_reader(reader, &options, 1)?;
Ok(())
}
fn process_from_file_list(options: &Options) -> Result<(), String> {
let mut line_number = 1;
for file_path in options.input_files.as_ref().unwrap().iter() {
let reader = ReaderBuilder::new()
.flexible(true)
.from_path(file_path).map_err(
|err| err.to_string(),
)?;
line_number = process_from_reader(
reader,
&options,
line_number,
)?;
}
Ok(())
}
pub fn process(options: Options) -> Result<(), String> {
match options.input_files {
None => process_from_stdin(options),
Some(_) => process_from_file_list(&options),
}
}