//#[macro_use] extern crate serde; 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, /// Load the chain from the provided output file name before appending to it. #[arg(short, long)] load: Option, /// Force over-writes of files, and force output of binary data to TTY. #[arg(short, long)] force: bool, /// The number of lines to output from the chain. lines: Option, // TODO: Allow 0 for only save + load operations. //TODO: Num of lines, etc. } fn buffered_read_all_lines io::Result<()>>(input: &mut T, mut then: F) -> io::Result { 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(stream: &mut S) -> io::Result> where S: io::Read + ?Sized { format::load_chain_from_sync(stream) } fn save_chain(stream: &mut S, chain: &Chain) -> 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 { 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) -> 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); //TODO: When chain is not empty (i.e. loaded,) it is okay for the input to be empty. (XXX: Should we *ignore* stdin for this? Empty stdin (CTRL+D on TTY) works. Do we actually need to change this behaviour? I don't think so.) (TODO: Add option to skip reading when loading file, maybe?) buffered_read_all_lines(&mut stdin, |string| { chain.feed(&string.split_whitespace() .filter(|word| !word.is_empty()) .map(|s| s.to_owned()).collect::>()); Ok(()) }).expect("Failed to read from stdin"); if !chain.is_empty() { let lines = cli.lines.map(NonZeroUsize::get).unwrap_or(1); if lines > 1 { for string in chain.str_iter_for(lines) { println!("{}", string); } } else { println!("{}", chain.generate_str()); } } complete_chain(&cli, chain).unwrap(); }