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

131 lines
3.5 KiB

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