//#[macro_use] extern crate serde; use smallvec::SmallVec; use clap::{ Parser, Subcommand, ValueEnum, }; use chain::{ Chain, }; use std::{ num::NonZeroUsize, path::{ PathBuf, Path, }, io::{ BufRead, self, }, }; #[derive(Debug, Parser)] #[command(name = env!("CARGO_PKG_NAME"), version, about = env!("CARGO_PKG_DESCRIPTION"), long_about = None)] pub struct Cli { /// Save the chain to the provided output file name after appending to it. #[arg(short, long)] save: Option<PathBuf>, /// Load the chain from the provided output file name before appending to it. #[arg(short, long)] load: Option<PathBuf>, // TODO: Group with `save` & add `-F/--file` opt for save & load from same file. /// Force over-writes of files, and force output of binary data to TTY. #[arg(short, long)] force: bool, /// Do not read into chain from `stdin` if there is a loaded chain that is not empty. #[arg(short, long)] no_consume: bool, /// Do not read into chain from `stdin` ever. #[arg(short, long="write")] write_only: bool, // XXX: Should we group this with `no_consume`? /// The number of lines to output from the chain. Default is 1. /// /// If 0, generation from chain is skipped. #[arg(default_value_t = 1)] lines: usize, } fn buffered_read_all_lines<T: BufRead+?Sized, F: FnMut(&str) -> io::Result<()>>(input: &mut T, mut then: F) -> io::Result<usize> { let mut buffer = String::new(); let mut read; let mut total=0; while {read = input.read_line(&mut buffer)?; read!=0} { if buffer.trim().len() > 0 { then(&buffer[..])?; } buffer.clear(); total += read; } Ok(total) } #[inline] fn parse_cli() -> Cli { use clap::Parser; Cli::parse() } mod format; fn load_chain<S>(stream: &mut S) -> io::Result<Chain<String>> where S: io::Read + ?Sized { format::load_chain_from_sync(stream) } fn save_chain<S>(stream: &mut S, chain: &Chain<String>) -> io::Result<()> where S: io::Write + ?Sized { format::save_chain_to_sync(stream, chain, true) //TODO: Change compression to be off for small chains...? We will need to store the chain size info somewhere else. } fn create_chain(cli: &Cli) -> Chain<String> { if let Some(load) = &cli.load { let mut input = std::fs::OpenOptions::new() .read(true) .open(&load).expect("Failed to open chain load file"); load_chain(&mut input).expect("Failed to load chain from file") } else { Chain::new() } } fn complete_chain(cli: &Cli, chain: Chain<String>) -> io::Result<()> { if let Some(save) = &cli.save { let mut output = std::fs::OpenOptions::new() .create_new(! cli.force) .create(cli.force) .write(true) .truncate(cli.force) .open(&save).expect("Failed to open chain save file"); save_chain(&mut output, &chain).expect("Failed to save chain to file") // TODO: Error type } Ok(()) } fn main() { let cli = parse_cli(); let stdin = io::stdin(); let mut stdin = stdin.lock(); let mut chain = create_chain(&cli); if !(cli.write_only || (cli.no_consume && ! chain.is_empty())) { buffered_read_all_lines(&mut stdin, |string| { chain.feed(&string.split_whitespace() .filter(|word| !word.is_empty()) .map(|s| s.to_owned()).collect::<SmallVec<[_; 16]>>()); Ok(()) }).expect("Failed to read from stdin"); } if !chain.is_empty() { match cli.lines { 0 => (), 1 => println!("{}", chain.generate_str()), lines => { for string in chain.str_iter_for(lines) { println!("{}", string); } }, }; } complete_chain(&cli, chain).unwrap(); }