use clap::Parser;
use crossterm::{
cursor::MoveTo,
execute,
terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
};
use rtsort::{compare_human_numeric, compare_normal, compare_numeric};
use std::cmp::Ordering;
use std::io::{self, BufRead, Write, stderr};
#[derive(Parser, Debug)]
#[command(
author,
version,
about = "A real-time sorting CLI utility",
disable_help_flag = true
)]
struct Args {
#[arg(short = 'n', long = "numeric-sort")]
numeric_sort: bool,
#[arg(short = 'h', long = "human-numeric-sort")]
human_numeric_sort: bool,
#[arg(short = 'r', long = "reverse")]
reverse: bool,
#[arg(short = 't', long = "top")]
top: Option<usize>,
#[arg(long, action = clap::ArgAction::Help)]
help: Option<bool>,
}
struct AlternateScreenGuard;
impl AlternateScreenGuard {
fn new() -> io::Result<Self> {
execute!(stderr(), EnterAlternateScreen)?;
Ok(Self)
}
}
impl Drop for AlternateScreenGuard {
fn drop(&mut self) {
let _ = execute!(stderr(), LeaveAlternateScreen);
}
}
fn run_sort_loop(
cmp_fn: fn(&str, &str) -> Ordering,
reverse: bool,
top: Option<usize>,
) -> io::Result<Vec<String>> {
let mut sorted_lines: Vec<String> = Vec::new();
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut stderr = stderr();
let mut guard: Option<AlternateScreenGuard> = None;
let mut line_buffer = String::new();
while handle.read_line(&mut line_buffer)? > 0 {
let original_line = line_buffer.trim_end_matches(['\n', '\r']).to_string();
if guard.is_none() {
guard = Some(AlternateScreenGuard::new()?);
}
let pos = match sorted_lines.binary_search_by(|e| {
let ord = cmp_fn(e, &original_line);
if reverse { ord.reverse() } else { ord }
}) {
Ok(pos) | Err(pos) => pos,
};
if top.is_none_or(|n| sorted_lines.len() < n || pos < n) {
sorted_lines.insert(pos, original_line);
if let Some(n) = top {
sorted_lines.truncate(n);
}
execute!(stderr, Clear(ClearType::All), MoveTo(0, 0))?;
for line in &sorted_lines {
writeln!(stderr, "{line}")?;
}
stderr.flush()?;
}
line_buffer.clear();
}
Ok(sorted_lines)
}
fn main() -> io::Result<()> {
let args = Args::parse();
let cmp_fn: fn(&str, &str) -> Ordering = if args.human_numeric_sort {
compare_human_numeric
} else if args.numeric_sort {
compare_numeric
} else {
compare_normal
};
let sorted_lines = run_sort_loop(cmp_fn, args.reverse, args.top)?;
let mut stdout = io::stdout().lock();
for line in &sorted_lines {
writeln!(stdout, "{line}")?;
}
stdout.flush()?;
Ok(())
}