use std::cmp;
use crate::config::{Config, Delimiter};
use crate::select::SelectColumns;
use crate::CliResult;
use std::str::from_utf8;
use self::Number::{Float, Int};
use clap::Parser;
#[derive(Parser, Debug)]
pub struct Args {
#[arg()]
pub arg_input: Option<String>,
#[arg(short = 's', long = "select", default_value = "")]
pub flag_select: SelectColumns,
#[arg(short = 'N', long = "numeric")]
pub flag_numeric: bool,
#[arg(short = 'R', long = "reverse")]
pub flag_reverse: bool,
#[arg(short = 'o', long = "output", value_name = "file")]
pub flag_output: Option<String>,
#[arg(short = 'n', long = "no-headers")]
pub flag_no_headers: bool,
#[arg(short = 'd', long = "delimiter", value_name = "arg")]
pub flag_delimiter: Option<Delimiter>,
}
pub fn run(args: &Args) -> CliResult<()> {
let numeric = args.flag_numeric;
let reverse = args.flag_reverse;
let rconfig = Config::new(&args.arg_input)
.delimiter(args.flag_delimiter)
.no_headers(args.flag_no_headers)
.select(args.flag_select.clone());
let mut rdr = rconfig.reader()?;
let headers = rdr.byte_headers()?.clone();
let sel = rconfig.selection(&headers)?;
let mut all = rdr.byte_records().collect::<Result<Vec<_>, _>>()?;
match (numeric, reverse) {
(false, false) => all.sort_by(|r1, r2| {
let a = sel.select(r1);
let b = sel.select(r2);
iter_cmp(a, b)
}),
(true, false) => all.sort_by(|r1, r2| {
let a = sel.select(r1);
let b = sel.select(r2);
iter_cmp_num(a, b)
}),
(false, true) => all.sort_by(|r1, r2| {
let a = sel.select(r1);
let b = sel.select(r2);
iter_cmp(b, a)
}),
(true, true) => all.sort_by(|r1, r2| {
let a = sel.select(r1);
let b = sel.select(r2);
iter_cmp_num(b, a)
}),
}
let mut wtr = Config::new(&args.flag_output).writer()?;
rconfig.write_headers(&mut rdr, &mut wtr)?;
for r in all.into_iter() {
wtr.write_byte_record(&r)?;
}
Ok(wtr.flush()?)
}
pub fn iter_cmp<A, L, R>(mut a: L, mut b: R) -> cmp::Ordering
where
A: Ord,
L: Iterator<Item = A>,
R: Iterator<Item = A>,
{
loop {
match (a.next(), b.next()) {
(None, None) => return cmp::Ordering::Equal,
(None, _) => return cmp::Ordering::Less,
(_, None) => return cmp::Ordering::Greater,
(Some(x), Some(y)) => match x.cmp(&y) {
cmp::Ordering::Equal => (),
non_eq => return non_eq,
},
}
}
}
pub fn iter_cmp_num<'a, L, R>(mut a: L, mut b: R) -> cmp::Ordering
where
L: Iterator<Item = &'a [u8]>,
R: Iterator<Item = &'a [u8]>,
{
loop {
match (next_num(&mut a), next_num(&mut b)) {
(None, None) => return cmp::Ordering::Equal,
(None, _) => return cmp::Ordering::Less,
(_, None) => return cmp::Ordering::Greater,
(Some(x), Some(y)) => match compare_num(x, y) {
cmp::Ordering::Equal => (),
non_eq => return non_eq,
},
}
}
}
#[derive(Clone, Copy, PartialEq)]
enum Number {
Int(i64),
Float(f64),
}
fn compare_num(n1: Number, n2: Number) -> cmp::Ordering {
match (n1, n2) {
(Int(i1), Int(i2)) => i1.cmp(&i2),
(Int(i1), Float(f2)) => compare_float(i1 as f64, f2),
(Float(f1), Int(i2)) => compare_float(f1, i2 as f64),
(Float(f1), Float(f2)) => compare_float(f1, f2),
}
}
fn compare_float(f1: f64, f2: f64) -> cmp::Ordering {
f1.partial_cmp(&f2).unwrap_or(cmp::Ordering::Equal)
}
fn next_num<'a, X>(xs: &mut X) -> Option<Number>
where
X: Iterator<Item = &'a [u8]>,
{
xs.next()
.and_then(|bytes| from_utf8(bytes).ok())
.and_then(|s| {
if let Ok(i) = s.parse::<i64>() {
Some(Number::Int(i))
} else if let Ok(f) = s.parse::<f64>() {
Some(Number::Float(f))
} else {
None
}
})
}