You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
genmarkov/src/main.rs

143 lines
3.6 KiB

//#[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();
}