use std::env;
use std::io::{self, BufWriter, Write};
use vrd::Random;
fn parse_int(arg: &str) -> Option<u64> {
if let Some(stripped) = arg.strip_prefix("0x") {
u64::from_str_radix(stripped, 16).ok()
} else {
arg.parse::<u64>().ok()
}
}
pub fn run_cli(
args: Vec<String>,
writer: &mut impl Write,
) -> io::Result<()> {
let count: usize =
args.first().and_then(|a| parse_int(a)).unwrap_or(1) as usize;
let mut rng = match args.get(1).and_then(|a| parse_int(a)) {
Some(seed) => Random::from_u64_seed(seed),
None => Random::new(),
};
let mut out = BufWriter::new(writer);
for _ in 0..count {
writeln!(out, "{}", rng.rand())?;
}
out.flush()?;
Ok(())
}
fn dispatch(
args: Vec<String>,
stdout: &mut impl Write,
stderr: &mut impl Write,
) -> i32 {
match run_cli(args, stdout) {
Ok(()) => 0,
Err(e) => {
let _ = writeln!(stderr, "Error: {}", e);
1
}
}
}
fn main() {
let args: Vec<String> = env::args().skip(1).collect();
let code = dispatch(args, &mut io::stdout(), &mut io::stderr());
std::process::exit(code);
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
struct FailingWriter;
impl Write for FailingWriter {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
Err(io::Error::new(io::ErrorKind::Other, "write fail"))
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn test_parse_int_decimal_hex_invalid() {
assert_eq!(parse_int("10"), Some(10));
assert_eq!(parse_int("0xA"), Some(10));
assert_eq!(parse_int("0xCAFE"), Some(0xCAFE));
assert_eq!(parse_int("invalid"), None);
assert_eq!(parse_int("0xZZ"), None);
assert_eq!(parse_int(""), None);
}
#[test]
fn test_run_cli_default_emits_one_line() {
let mut buf = Cursor::new(Vec::new());
run_cli(vec![], &mut buf).unwrap();
let out = String::from_utf8(buf.into_inner()).unwrap();
assert_eq!(out.lines().count(), 1);
}
#[test]
fn test_run_cli_explicit_count() {
let mut buf = Cursor::new(Vec::new());
run_cli(vec!["5".to_string()], &mut buf).unwrap();
let out = String::from_utf8(buf.into_inner()).unwrap();
assert_eq!(out.lines().count(), 5);
}
#[test]
fn test_run_cli_seed_is_deterministic() {
let mut a = Cursor::new(Vec::new());
let mut b = Cursor::new(Vec::new());
run_cli(vec!["3".to_string(), "0x1234".to_string()], &mut a)
.unwrap();
run_cli(vec!["3".to_string(), "0x1234".to_string()], &mut b)
.unwrap();
assert_eq!(a.into_inner(), b.into_inner());
}
#[test]
fn test_run_cli_invalid_args_use_defaults() {
let mut buf = Cursor::new(Vec::new());
run_cli(
vec!["bogus".to_string(), "alsobogus".to_string()],
&mut buf,
)
.unwrap();
let out = String::from_utf8(buf.into_inner()).unwrap();
assert_eq!(out.lines().count(), 1);
}
#[test]
fn test_run_cli_propagates_write_error() {
let mut writer = FailingWriter;
let res = run_cli(vec!["1".to_string()], &mut writer);
assert!(res.is_err());
}
#[test]
fn test_dispatch_ok_returns_zero() {
let mut out = Cursor::new(Vec::new());
let mut err = Cursor::new(Vec::new());
let code = dispatch(vec![], &mut out, &mut err);
assert_eq!(code, 0);
assert!(err.get_ref().is_empty());
assert!(!out.get_ref().is_empty());
}
#[test]
fn test_dispatch_error_returns_one_and_logs() {
let mut err = Cursor::new(Vec::new());
let code = dispatch(vec![], &mut FailingWriter, &mut err);
assert_eq!(code, 1);
let log = String::from_utf8(err.into_inner()).unwrap();
assert!(log.starts_with("Error:"), "stderr was: {log}");
}
#[test]
fn test_failing_writer_flush_is_ok() {
let mut w = FailingWriter;
assert!(w.flush().is_ok());
}
}