//#[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 < PathBuf > ,
/// Load the chain from the provided output file name before appending to it.
#[ arg(short, long) ]
load : Option < PathBuf > ,
/// 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 < NonZeroUsize > , // TODO: Allow 0 for only save + load operations.
//TODO: Num of lines, etc.
}
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 ) ;
//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 ::< Vec < _ > > ( ) ) ;
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 ( ) ;
}