Skip to content

Commit 06be04a

Browse files
committed
v0.2.2
1 parent 74cc4d6 commit 06be04a

File tree

7 files changed

+121
-17
lines changed

7 files changed

+121
-17
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v0.2.2
4+
- Anonymization: output each format into its own directory
5+
- Support saving/loading anonymizer state (mapping of players <=> ID numbers)
6+
7+
## v0.2.1
8+
- Fix a bug when parsing ELOs.
9+
310
## v0.2.0
411
- Ties are now properly anonymized.
512
- ELO is now included in anonymized logs, but is rounded to the nearest 50 ELO.

Cargo.lock

+35-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "psbattletools"
3-
version = "0.2.0"
3+
version = "0.2.2"
44
edition = "2021"
55
description = "Command-line program to manage Pokémon Showdown battle logs."
66
license = "MIT"
@@ -17,6 +17,9 @@ lazy_static = "1.4.0"
1717
prettytable-rs = "0.8.0"
1818
rayon = "1.5.1"
1919
regex = "1.5.4"
20+
serde = "1.0.136"
21+
serde_derive = "1.0.136"
22+
serde_json = "1.0.79"
2023
structopt = "0.3.23"
2124

2225
[dev-dependencies]

src/anonymize/anonymizer.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// From https://github.com/AnnikaCodes/anonbattle/blob/main/src/anonymizer.rs
22

3+
use serde_derive::{Deserialize, Serialize};
34
use std::collections::HashMap;
45
use std::str::FromStr;
56
use std::sync::Mutex;
@@ -14,6 +15,7 @@ lazy_static! {
1415
}
1516

1617
/// Tracks players
18+
#[derive(Serialize, Deserialize)]
1719
struct SharedState {
1820
players: HashMap<String, String>,
1921
pub current_battle_number: u32,
@@ -40,6 +42,10 @@ impl SharedState {
4042
}
4143
}
4244
}
45+
46+
fn to_json(&self) -> serde_json::Result<String> {
47+
serde_json::to_string(&self)
48+
}
4349
}
4450

4551
/// Anonymizes string JSON while tracking state
@@ -59,10 +65,19 @@ impl Anonymizer {
5965
}
6066
}
6167

68+
pub fn with_json(json: String, is_safe: bool, no_log: bool) -> Self {
69+
let state: SharedState = serde_json::from_str(&json).unwrap();
70+
Self {
71+
state: Mutex::new(state),
72+
is_safe,
73+
no_log,
74+
}
75+
}
76+
6277
/// Anonymizes a log.
6378
///
64-
/// Returns a tuple: (json, battle_number)
65-
pub fn anonymize(&self, raw: &str) -> Result<(String, u32), BattleToolsError> {
79+
/// Returns a tuple: (json, battle_number, format)
80+
pub fn anonymize(&self, raw: &str) -> Result<(String, u32, String), BattleToolsError> {
6681
let json = json::parse(raw)?;
6782

6883
let p1 = json["p1"]
@@ -254,7 +269,12 @@ impl Anonymizer {
254269
tracker.current_battle_number += 1;
255270
tracker.current_battle_number
256271
};
257-
Ok((result, battle_number))
272+
Ok((result, battle_number, json["format"].to_string()))
273+
}
274+
275+
// TODO: actually save this & load it
276+
pub fn get_state_json(&self) -> serde_json::Result<String> {
277+
self.state.lock().unwrap().to_json()
258278
}
259279
}
260280

@@ -310,7 +330,7 @@ mod unit_tests {
310330
#[test]
311331
pub fn anonymization() {
312332
let anonymizer = Anonymizer::new(true, false);
313-
let (json, _) = anonymizer.anonymize(&SAMPLE_JSON).unwrap();
333+
let (json, _, _) = anonymizer.anonymize(&SAMPLE_JSON).unwrap();
314334
assert_ne!(json, *SAMPLE_JSON);
315335

316336
for term in ["00:00:01", "Annika", "annika", "Rust Haters", "rusthaters"] {
@@ -338,7 +358,7 @@ mod unit_tests {
338358
#[test]
339359
pub fn tie() {
340360
let anonymizer = Anonymizer::new(true, false);
341-
let (json, _) = anonymizer.anonymize(&TIE_WINNERSTRING_JSON).unwrap();
361+
let (json, _, _) = anonymizer.anonymize(&TIE_WINNERSTRING_JSON).unwrap();
342362
assert_eq!(
343363
gjson::get(&json, "winner").to_string(),
344364
"".to_string(),
@@ -350,8 +370,8 @@ mod unit_tests {
350370
pub fn no_log() {
351371
let anonymizer_logs = Anonymizer::new(false, false);
352372
let anonymizer_no_logs = Anonymizer::new(false, true);
353-
let (logs_json, _) = anonymizer_logs.anonymize(&SAMPLE_JSON).unwrap();
354-
let (no_logs_json, _) = anonymizer_no_logs.anonymize(&SAMPLE_JSON).unwrap();
373+
let (logs_json, _, _) = anonymizer_logs.anonymize(&SAMPLE_JSON).unwrap();
374+
let (no_logs_json, _, _) = anonymizer_no_logs.anonymize(&SAMPLE_JSON).unwrap();
355375

356376
for should_be_arr in ["log", "inputLog"] {
357377
assert!(

src/anonymize/mod.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod anonymizer;
44
use std::path::PathBuf;
55

66
use crate::{directory::LogParser, BattleToolsError};
7-
use anonymizer::Anonymizer;
7+
pub use anonymizer::Anonymizer;
88
pub struct AnonymizingDirectoryParser {
99
anonymizer: Anonymizer,
1010
output_directory: PathBuf,
@@ -17,6 +17,17 @@ impl AnonymizingDirectoryParser {
1717
output_directory,
1818
}
1919
}
20+
21+
pub fn with_anonymizer(anonymizer: Anonymizer, output_directory: PathBuf) -> Self {
22+
Self {
23+
anonymizer,
24+
output_directory,
25+
}
26+
}
27+
28+
pub fn get_state_json(&self) -> serde_json::Result<String> {
29+
self.anonymizer.get_state_json()
30+
}
2031
}
2132

2233
impl LogParser<()> for AnonymizingDirectoryParser {
@@ -25,8 +36,10 @@ impl LogParser<()> for AnonymizingDirectoryParser {
2536
raw_json: String,
2637
_: &std::path::Path,
2738
) -> Result<(), BattleToolsError> {
28-
let (json, battle_num) = self.anonymizer.anonymize(&raw_json)?;
39+
let (json, battle_num, directory) = self.anonymizer.anonymize(&raw_json)?;
2940
let mut out_file = self.output_directory.clone();
41+
out_file.push(directory);
42+
std::fs::create_dir_all(&out_file)?;
3043
out_file.push(format!("{}.log.json", battle_num));
3144
std::fs::write(out_file, json)?;
3245
Ok(())

src/main.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ struct Options {
110110
help = "The maximum number of threads to use for concurrent processing"
111111
)]
112112
threads: Option<usize>,
113+
#[structopt(
114+
long = "save-state-to",
115+
help = "Save the state of the anonymizer to this file"
116+
)]
117+
save_state_to: Option<PathBuf>,
118+
#[structopt(
119+
long = "load-state-from",
120+
help = "Load the state of the anonymizer from this file"
121+
)]
122+
load_state_from: Option<PathBuf>,
113123
}
114124

115125
#[derive(Debug)]
@@ -121,7 +131,9 @@ pub enum BattleToolsError {
121131
InvalidLog(String),
122132
PathConversion(String),
123133
IncompleteAnonymization(String),
134+
SerializationError(serde_json::Error),
124135
}
136+
125137
impl From<std::io::Error> for BattleToolsError {
126138
fn from(error: std::io::Error) -> Self {
127139
BattleToolsError::IOError(error)
@@ -147,6 +159,11 @@ impl From<rayon::ThreadPoolBuildError> for BattleToolsError {
147159
BattleToolsError::ThreadPoolError(error)
148160
}
149161
}
162+
impl From<serde_json::Error> for BattleToolsError {
163+
fn from(error: serde_json::Error) -> Self {
164+
BattleToolsError::SerializationError(error)
165+
}
166+
}
150167

151168
fn main() -> Result<(), BattleToolsError> {
152169
let options = Options::from_args();
@@ -200,8 +217,21 @@ fn main() -> Result<(), BattleToolsError> {
200217
} => {
201218
// create dir if needed
202219
fs::create_dir_all(&output_dir)?;
203-
let mut anonymizer = AnonymizingDirectoryParser::new(is_safe, no_log, output_dir);
220+
221+
let mut anonymizer = if let Some(load_path) = options.load_state_from {
222+
let json = fs::read_to_string(load_path)?;
223+
let anonymizer = anonymize::Anonymizer::with_json(json, is_safe, no_log);
224+
AnonymizingDirectoryParser::with_anonymizer(anonymizer, output_dir)
225+
} else {
226+
AnonymizingDirectoryParser::new(is_safe, no_log, output_dir)
227+
};
228+
204229
anonymizer.handle_directories(directories, options.exclude)?;
230+
231+
if let Some(save_state_path) = options.save_state_to {
232+
let json = anonymizer.get_state_json()?;
233+
fs::write(save_state_path, json)?;
234+
}
205235
}
206236
}
207237

tests/anonymize.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ fn test_no_terms_and_identicality() {
2424
assert!(output.status.success(), "command failed");
2525

2626
let mut out_file_1 = out_dir.clone();
27-
out_file_1.push("1.log.json");
27+
out_file_1.push("gen8randombattle/1.log.json");
2828
let output_1 = std::fs::read_to_string(&out_file_1)
2929
.unwrap_or_else(|_| panic!("Couldn't read output file {:?}", out_file_1));
3030

3131
let mut out_file_999 = out_dir;
32-
out_file_999.push("999.log.json");
32+
out_file_999.push("gen8randombattle/999.log.json");
3333
let output_999 = std::fs::read_to_string(out_file_999).expect("Couldn't read output file");
3434

3535
for term in ["00:00:01", "Annika", "annika", "Rust Haters", "rusthaters"] {

0 commit comments

Comments
 (0)