use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::PathBuf;
use std::time::Duration;
use clap::{Args, ValueEnum};
use rs_poker::open_hand_history::anonymize::{
AnonymizeConfig, Anonymizer, NameStrategy, StreamError, TimeFuzzConfig, anonymize_stream,
};
#[derive(Debug, Clone, Copy, ValueEnum)]
enum NameStrategyArg {
Keep,
PerHand,
Stable,
}
impl From<NameStrategyArg> for NameStrategy {
fn from(v: NameStrategyArg) -> Self {
match v {
NameStrategyArg::Keep => NameStrategy::Keep,
NameStrategyArg::PerHand => NameStrategy::PerHand,
NameStrategyArg::Stable => NameStrategy::Stable,
}
}
}
#[derive(Args, Debug)]
pub struct AnonymizeArgs {
input: PathBuf,
#[arg(short, long, default_value = "-")]
output: PathBuf,
#[arg(long, value_enum, default_value_t = NameStrategyArg::Stable)]
names: NameStrategyArg,
#[arg(long)]
keep_site: bool,
#[arg(long)]
keep_network: bool,
#[arg(long)]
keep_version: bool,
#[arg(long)]
keep_tables: bool,
#[arg(long)]
keep_game_numbers: bool,
#[arg(long)]
keep_times: bool,
#[arg(long, default_value_t = 30)]
shift_minutes: u64,
#[arg(long, default_value_t = 5)]
jitter_seconds: u64,
#[arg(long)]
seed: Option<u64>,
}
#[derive(Debug, thiserror::Error)]
pub enum AnonymizeError {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error(transparent)]
Stream(#[from] StreamError),
}
pub fn run(args: AnonymizeArgs) -> Result<(), AnonymizeError> {
let config = build_config(&args);
let mut anonymizer = Anonymizer::new(config);
let input: Box<dyn BufRead> = open_input(&args.input)?;
let mut output: Box<dyn Write> = open_output(&args.output)?;
let count = anonymize_stream(input, &mut output, &mut anonymizer)?;
output.flush()?;
eprintln!("anonymized {count} hand(s)");
Ok(())
}
fn build_config(args: &AnonymizeArgs) -> AnonymizeConfig {
let time_fuzz = if args.keep_times {
None
} else {
Some(TimeFuzzConfig {
max_global_shift: Duration::from_secs(args.shift_minutes * 60),
max_per_hand_jitter: Duration::from_secs(args.jitter_seconds),
})
};
AnonymizeConfig {
name_strategy: args.names.into(),
name_pool: None,
rotate_site: !args.keep_site,
rotate_network: !args.keep_network,
rotate_internal_version: !args.keep_version,
rotate_table_name: !args.keep_tables,
rotate_game_numbers: !args.keep_game_numbers,
time_fuzz,
seed: args.seed,
}
}
fn open_input(path: &PathBuf) -> io::Result<Box<dyn BufRead>> {
if path.as_os_str() == "-" {
Ok(Box::new(BufReader::new(io::stdin().lock())))
} else {
Ok(Box::new(BufReader::new(File::open(path)?)))
}
}
fn open_output(path: &PathBuf) -> io::Result<Box<dyn Write>> {
if path.as_os_str() == "-" {
Ok(Box::new(BufWriter::new(io::stdout().lock())))
} else {
Ok(Box::new(BufWriter::new(File::create(path)?)))
}
}