Skip to content

Commit c17e34e

Browse files
committed
refactor: split out lib from cli
1 parent 16213e4 commit c17e34e

File tree

10 files changed

+192
-96
lines changed

10 files changed

+192
-96
lines changed

Cargo.lock

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,37 @@
1-
[package]
2-
name = "buddy-up"
1+
[workspace]
2+
members = ["crates/cli", "crates/lib"]
3+
resolver = "3"
4+
5+
[workspace.package]
36
version = "0.1.2"
47
authors = ["Christoph Koehler <christoph@zerodeviation.net>"]
5-
description = "Buddy up a changing group of people into unique pairs over time."
68
edition = "2024"
79
license = "MIT OR Apache-2.0"
810
repository = "https://github.com/ckoehler/buddy-up"
911
readme = "README.md"
10-
publish = true
11-
1212

13-
[dependencies]
14-
anyhow = "1.0.96"
15-
chrono = "0.4.40"
16-
clap = { version = "4.5.31", features = ["derive"] }
17-
comfy-table = "7.1.4"
18-
csv = "1.3.1"
19-
genetic_algorithm = "0.18.1"
20-
glob = "0.3.2"
21-
serde = { version = "1.0.218", features = ["derive"] }
22-
serde_json = "1.0.139"
13+
[workspace.dependencies]
14+
buddy-up-lib = { version = "0.1.2", path = "crates/lib"}
2315
tracing = { version = "0.1.41", features = ["log"] }
2416
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "ansi"] }
2517
tracing-error = "0.2.1"
2618

27-
[[bin]]
28-
name = "buddy"
29-
path = "src/main.rs"
30-
31-
3219
[workspace.lints.clippy]
33-
pedantic = { level = "deny", priority = -1 }
20+
# pedantic = { level = "deny", priority = -1 }
3421
cast_precision_loss = "allow"
3522
similar_names = "allow"
3623

37-
[lints]
38-
workspace = true
39-
4024
# The profile that 'cargo dist' will build with
4125
[profile.dist]
4226
inherits = "release"
4327
lto = "thin"
28+
29+
[profile.wasm-dev]
30+
inherits = "dev"
31+
opt-level = 1
32+
33+
[profile.server-dev]
34+
inherits = "dev"
35+
36+
[profile.android-dev]
37+
inherits = "dev"

crates/cli/Cargo.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[package]
2+
name = "buddy-up"
3+
description = "Buddy up a changing group of people into unique pairs over time."
4+
version.workspace = true
5+
authors.workspace = true
6+
edition.workspace = true
7+
license.workspace = true
8+
repository.workspace = true
9+
publish = true
10+
11+
12+
[dependencies]
13+
anyhow = "1.0.96"
14+
clap = { version = "4.5.31", features = ["derive"] }
15+
glob = "0.3.2"
16+
serde = { version = "1.0.218", features = ["derive"] }
17+
serde_json = "1.0.139"
18+
buddy-up-lib.workspace = true
19+
tracing.workspace = true
20+
tracing-subscriber.workspace = true
21+
tracing-error.workspace = true
22+
23+
[[bin]]
24+
name = "buddy"
25+
path = "src/main.rs"
26+
27+
[lints]
28+
workspace = true

src/main.rs renamed to crates/cli/src/main.rs

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
mod algorithm;
2-
mod input;
3-
mod output;
4-
5-
use algorithm::History;
6-
use algorithm::Person;
7-
use algorithm::merge;
81
use anyhow::Context;
92
use anyhow::Result;
3+
use buddy_up_lib::History;
4+
use buddy_up_lib::Person;
5+
use buddy_up_lib::{json_history, print_table, process};
106
use clap::{Parser, Subcommand};
11-
use glob::glob;
127
use std::path::Path;
138
use std::path::PathBuf;
149
use tracing::{debug, info};
@@ -62,26 +57,17 @@ fn main() -> Result<()> {
6257
Ok(())
6358
}
6459

65-
fn pair(input: &Path, output_dir: &Path) -> Result<()> {
66-
let output_dir = output_dir.to_string_lossy();
60+
fn pair(input: &Path, history_dir: &Path) -> Result<()> {
61+
let output_dir = history_dir.to_string_lossy();
6762

68-
let people = input::process(input)?;
63+
let people = process(input)?;
6964

7065
// generate history from pair files
71-
let mut history = History::new();
72-
// read pairs from dir of files
73-
let pattern = format!("{output_dir}/*.json");
74-
let mut tr_history_files = 0;
75-
for path in glob(&pattern).expect("Glob pattern works") {
76-
debug!("Reading history file {path:?}");
77-
let pairs = std::fs::read_to_string(path?)?;
78-
let pairs: Vec<(Person, Person)> = serde_json::from_str(&pairs)?;
79-
let pairs = pairs.iter().map(|p| (p.0.id, p.1.id)).collect();
80-
tr_history_files += 1;
81-
merge(&mut history, &pairs);
82-
}
83-
let tr_num_pairs = history.len();
66+
let history = History::from_dir(&output_dir)?;
67+
68+
let tr_num_pairs = history.stats().pairs;
8469
let tr_max_num_pairs = (people.len().pow(2) - people.len()) / 2;
70+
let tr_history_files = history.stats().files_read;
8571
info!(
8672
"Read {tr_history_files} history files, found {tr_num_pairs} existing pairs (max possible: {tr_max_num_pairs})."
8773
);
@@ -91,7 +77,7 @@ fn pair(input: &Path, output_dir: &Path) -> Result<()> {
9177
// the algorithm only operates on ids, so get those only. We can map them back to names for
9278
// output later.
9379
let people_ids = people.keys().copied().collect();
94-
let pairs = algorithm::pair(people_ids, &history);
80+
let pairs = buddy_up_lib::pair(people_ids, &history);
9581

9682
// put names back into the pairs for saving
9783
let pairs: Vec<(Person, Person)> = pairs
@@ -106,10 +92,10 @@ fn pair(input: &Path, output_dir: &Path) -> Result<()> {
10692

10793
// serialize to json and save
10894
// TODO: that type gymnastic tho
109-
output::json_history(&pairs, output_dir.into_owned().into()).context("Saving history")?;
95+
json_history(&pairs, output_dir.into_owned().into()).context("Saving history")?;
11096

11197
// now print the pairs
112-
output::print_table(&pairs);
98+
print_table(&pairs);
11399
Ok(())
114100
}
115101

crates/lib/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "buddy-up-lib"
3+
description = "The brains behind the buddy-up crate."
4+
version.workspace = true
5+
authors.workspace = true
6+
edition.workspace = true
7+
license.workspace = true
8+
repository.workspace = true
9+
publish = true
10+
11+
[dependencies]
12+
anyhow = "1.0.96"
13+
chrono = "0.4.40"
14+
comfy-table = "7.1.4"
15+
csv = "1.3.1"
16+
genetic_algorithm = "0.18.1"
17+
glob = "0.3.2"
18+
serde = { version = "1.0.218", features = ["derive"] }
19+
serde_json = "1.0.139"
20+
tracing = { version = "0.1.41", features = ["log"] }
21+
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "ansi"] }
22+
tracing-error = "0.2.1"
23+
24+
[lints]
25+
workspace = true
Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,98 @@
1+
use crate::Person;
2+
use anyhow::Result;
3+
use glob::glob;
14
use serde::Deserialize;
25
use serde::Serialize;
36
use std::collections::HashMap;
7+
use tracing::debug;
48

59
#[derive(Debug, Clone, Serialize, Deserialize)]
6-
pub struct History(HashMap<(usize, usize), usize>);
10+
pub struct History {
11+
map: HashMap<(usize, usize), usize>,
12+
#[serde(skip)]
13+
stats: HistoryStats,
14+
}
15+
16+
impl Default for History {
17+
fn default() -> Self {
18+
Self::new()
19+
}
20+
}
721

822
impl History {
23+
pub fn from_dir(dir: &str) -> Result<Self> {
24+
let mut history = Self::new();
25+
26+
// read pairs from dir of files
27+
let pattern = format!("{dir}/*.json");
28+
for path in glob(&pattern).expect("Glob pattern works") {
29+
debug!("Reading history file {path:?}");
30+
let pairs = std::fs::read_to_string(path?)?;
31+
let pairs: Vec<(Person, Person)> = serde_json::from_str(&pairs)?;
32+
let pairs = pairs.iter().map(|p| (p.0.id, p.1.id)).collect();
33+
history.stats.files_read += 1;
34+
merge(&mut history, &pairs);
35+
}
36+
history.stats.pairs = history.len();
37+
Ok(history)
38+
}
39+
940
#[allow(dead_code)]
1041
fn max_iteration(&self) -> usize {
11-
*self.0.values().max().unwrap_or(&0)
42+
*self.map.values().max().unwrap_or(&0)
1243
}
1344

14-
pub fn new() -> Self {
45+
fn new() -> Self {
1546
let scores = HashMap::new();
16-
Self(scores)
47+
Self {
48+
map: scores,
49+
stats: HistoryStats::default(),
50+
}
51+
}
52+
53+
pub fn stats(&self) -> HistoryStats {
54+
self.stats
1755
}
1856

1957
fn insert(&mut self, pair: (usize, usize), iteration: usize) {
2058
// if the first variation of the pair exists, insert it there; if not
2159
// it doesn't really matter if the other one exists, just insert it as the other.
2260
// Whether that one exists or not, it'll insert there.
23-
if self.0.contains_key(&pair) {
24-
self.0.insert(pair, iteration);
61+
if self.map.contains_key(&pair) {
62+
self.map.insert(pair, iteration);
2563
} else {
26-
self.0.insert((pair.1, pair.0), iteration);
64+
self.map.insert((pair.1, pair.0), iteration);
2765
}
2866
}
2967

3068
pub fn len(&self) -> usize {
31-
self.0.len()
69+
self.map.len()
70+
}
71+
72+
pub fn is_empty(&self) -> bool {
73+
self.len() == 0
3274
}
3375

3476
fn contains(&self, pair: &(usize, usize)) -> bool {
35-
self.0.contains_key(pair) || self.0.contains_key(&(pair.1, pair.0))
77+
self.map.contains_key(pair) || self.map.contains_key(&(pair.1, pair.0))
3678
}
3779

3880
pub fn get(&self, pair: (usize, usize)) -> Option<usize> {
39-
Some(match self.0.get(&pair) {
81+
Some(match self.map.get(&pair) {
4082
Some(x) => *x,
41-
None => *self.0.get(&(pair.1, pair.0))?,
83+
None => *self.map.get(&(pair.1, pair.0))?,
4284
})
4385
}
4486

45-
//pub fn values(&self) -> Vec<usize> {
46-
// self.0.values().copied().collect()
47-
//}
48-
4987
pub fn min(&self) -> usize {
50-
*self.0.values().min().unwrap_or(&0)
88+
*self.map.values().min().unwrap_or(&0)
5189
}
5290
pub fn max(&self) -> usize {
53-
*self.0.values().max().unwrap_or(&0)
91+
*self.map.values().max().unwrap_or(&0)
5492
}
5593
}
5694

57-
pub fn merge(history: &mut History, pairs: &Vec<(usize, usize)>) {
95+
fn merge(history: &mut History, pairs: &Vec<(usize, usize)>) {
5896
for p in pairs {
5997
if history.contains(p) {
6098
let it = history.get(*p).unwrap();
@@ -66,6 +104,12 @@ pub fn merge(history: &mut History, pairs: &Vec<(usize, usize)>) {
66104
}
67105
}
68106

107+
#[derive(Debug, Copy, Clone, Default)]
108+
pub struct HistoryStats {
109+
pub files_read: usize,
110+
pub pairs: usize,
111+
}
112+
69113
#[cfg(test)]
70114
mod test {
71115
use super::*;
Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,8 @@
1-
mod history;
1+
pub mod history;
22
use genetic_algorithm::{chromosome::GenesOwner, strategy::evolve::prelude::*};
3-
use serde::Deserialize;
4-
use serde::Serialize;
3+
use history::History;
54
use tracing::{debug, trace};
65

7-
pub use history::History;
8-
pub use history::merge;
9-
10-
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
11-
pub struct Person {
12-
pub id: usize,
13-
name: String,
14-
}
15-
16-
impl std::fmt::Display for Person {
17-
// This trait requires `fmt` with this exact signature.
18-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19-
write!(f, "{}", self.name)
20-
}
21-
}
22-
23-
impl Person {
24-
pub fn new(id: usize, name: String) -> Self {
25-
Self { id, name }
26-
}
27-
}
28-
296
pub fn pair(ids: Vec<usize>, last: &History) -> Vec<(usize, usize)> {
307
//let len = ids.len();
318
let genotype = UniqueGenotype::builder()
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ pub fn process(input: &Path) -> Result<HashMap<usize, String>> {
1111
let mut rdr = csv::ReaderBuilder::new()
1212
.has_headers(false)
1313
.from_reader(reader);
14-
// TODO: check for unique ids somewhere here
1514
let mut people = HashMap::new();
1615
let mut tr_input_len = 0;
1716
for rec in rdr.records() {

0 commit comments

Comments
 (0)