add sample, minimum viable product, add README.md
This commit is contained in:
parent
fd07ea0b2d
commit
34fd92cccc
8 changed files with 238 additions and 18 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
games.csv
|
||||
out.csv
|
||||
result
|
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -99,6 +99,27 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
@ -829,6 +850,7 @@ dependencies = [
|
|||
name = "rust_elaborator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"csv",
|
||||
"fuzzy-matcher",
|
||||
"reqwest",
|
||||
"serde",
|
||||
|
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
csv = "1.3.1"
|
||||
fuzzy-matcher = { version = "0.3.7", features = ["compact"] }
|
||||
reqwest = { version = "0.12.20", features = ["json"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
|
|
47
README.md
Normal file
47
README.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Rust Elaborator
|
||||
|
||||
This program serves to take a list of boardgames as a csv and return a csv with more data about them.
|
||||
|
||||
## Building
|
||||
|
||||
### Clone the git repo locally
|
||||
|
||||
`git clone https://git.mtgmonkey.net/Andromeda/rust-elaborator.git`
|
||||
`cd rust-elaborator`
|
||||
|
||||
### Run the sample
|
||||
|
||||
`cat sample_in.csv | nix run`
|
||||
|
||||
the output `out.csv` should match the provided `sample_out.csv`
|
||||
|
||||
## Usage
|
||||
|
||||
The program reads a csv from stdin and outputs it to `out.csv`. The following command reads the contents of `in.csv` into the program and runs it.
|
||||
|
||||
`cat in.csv | rust_elaborator`
|
||||
|
||||
`in.csv` must be formatted as follows...
|
||||
|
||||
|title|
|
||||
|-|
|
||||
|Monopoly|
|
||||
|Abomination|
|
||||
|7 Wonders|
|
||||
|Uno|
|
||||
|
||||
...in excel or as follows...
|
||||
|
||||
```csv
|
||||
title,
|
||||
Monopoly,
|
||||
Abomination,
|
||||
7 Wonders,
|
||||
Uno,
|
||||
```
|
||||
|
||||
...as plaintext
|
||||
|
||||
where `title` can be anything.
|
||||
Capitalization does not matter.
|
||||
Additional columns will not be present in `out.csv`.
|
24
sample_in.csv
Normal file
24
sample_in.csv
Normal file
|
@ -0,0 +1,24 @@
|
|||
Name,Current favorites,,
|
||||
13 Dead End Drive,,,
|
||||
221b Baker Street,,,
|
||||
7Wonders,,,
|
||||
Abandon All Artichokes,,,
|
||||
Betrayal at House on the Hill,Large group,"Group works against a single player in scenarios, ghosts and ghouls etc. Many scenarios to choose from.",
|
||||
Bing-oh!,,,
|
||||
Chrononauts,,,
|
||||
Claim (4 versions),,,
|
||||
Clank!,,,
|
||||
Flamecraft,Worker placement and resource management,Acquire dragons with different abilities by going to shops in town to purchase resources. Cute.,
|
||||
Flinch,,,
|
||||
Flip City,,,
|
||||
In the Footsteps of Marie Curie,,,
|
||||
Old Maid,,,
|
||||
On the Dot,,,
|
||||
One Night Werewolf,,,
|
||||
RoboChamp,,,
|
||||
Squirmish,,,
|
||||
Steampunk Rally Fusion,,,
|
||||
Stipulations,,,
|
||||
Turing Machine,Logic and computing,Use logic rules and tests to deduce a pattern before anyone else. Requires advanced logic skills.,
|
||||
Tutti Quantum,,,
|
||||
Zigity,,,
|
|
24
sample_out.csv
Normal file
24
sample_out.csv
Normal file
|
@ -0,0 +1,24 @@
|
|||
title,foundtitle,minplayers,maxplayers,playingtime,minplaytime,maxplaytime,age
|
||||
13 Dead End Drive,13 Dead End Drive,2,4,45,45,45,9
|
||||
221b Baker Street,221B Baker Street Expansion Pack,2,6,,,,
|
||||
7Wonders,NOT_FOUND,,,,,,
|
||||
Abandon All Artichokes,Abandon All Artichokes,2,4,20,20,20,10
|
||||
Betrayal at House on the Hill,Betrayal at House on the Hill,3,6,60,60,60,12
|
||||
Bing-oh!,Bing-Oh!,2,6,15,15,15,
|
||||
Chrononauts,Chrononauts,1,6,30,30,30,11
|
||||
Claim (4 versions),NOT_FOUND,,,,,,
|
||||
Clank!,Clank! Adventuring Party: Lightning Reflexes Promo Card,2,6,120,60,120,13
|
||||
Flamecraft,Flamecraft,1,5,60,60,60,10
|
||||
Flinch,Flinch,1,8,20,20,20,7
|
||||
Flip City,Flip City,1,4,50,30,50,8
|
||||
In the Footsteps of Marie Curie,In the Footsteps of Marie Curie,2,4,30,20,30,10
|
||||
Old Maid,Old Maid,2,6,5,5,5,4
|
||||
On the Dot,On the Dot,2,4,5,5,5,10
|
||||
One Night Werewolf,One Night Werewolf,3,7,10,10,10,10
|
||||
RoboChamp,NOT_FOUND,,,,,,
|
||||
Squirmish,Squirmish,2,4,60,20,60,10
|
||||
Steampunk Rally Fusion,Steampunk Rally Fusion,2,8,60,45,60,14
|
||||
Stipulations,Stipulations,4,99,45,30,45,13
|
||||
Turing Machine,Turing Machine,1,4,20,20,20,14
|
||||
Tutti Quantum,Tutti Quantum,2,4,15,15,15,8
|
||||
Zigity,Cranium Zigity,2,99,10,10,10,8
|
|
|
@ -9,6 +9,8 @@ pub struct Id_search_results {
|
|||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Boardgame {
|
||||
#[serde(rename = "@objectid")]
|
||||
pub objectid: i32,
|
||||
pub minplayers: i32,
|
||||
pub maxplayers: i32,
|
||||
pub playingtime: i32,
|
||||
|
|
133
src/main.rs
133
src/main.rs
|
@ -6,20 +6,7 @@ use std::io::prelude::*;
|
|||
#[tokio::main]
|
||||
async fn main() -> Result<(), reqwest::Error> {
|
||||
println!("Welcome to rust_elaborator!");
|
||||
println!(
|
||||
"Someday, this program will allow a csv input and output a csv with more information."
|
||||
);
|
||||
println!("For now, however, it takes text input and outputs text as well.");
|
||||
println!("Enter the name of a boardgame, hit enter, and see information. Then repeat!");
|
||||
println!("Have fun with rust_elaborator!");
|
||||
let stdin = io::stdin();
|
||||
for line in stdin.lock().lines() {
|
||||
let client = reqwest::Client::new();
|
||||
match get_boardgame_from_name(&client, line.unwrap()).await {
|
||||
Some(boardgame) => println!("{:#?}", boardgame),
|
||||
None => println!("Game not found"),
|
||||
}
|
||||
}
|
||||
write_csv().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -41,6 +28,21 @@ async fn get_id_from_name(client: &reqwest::Client, name: String) -> Option<i32>
|
|||
return Some(best_match.objectid);
|
||||
}
|
||||
|
||||
async fn get_name_from_name(client: &reqwest::Client, name: String) -> Option<String> {
|
||||
let request_url = format!("https://boardgamegeek.com/xmlapi/search?search={name}");
|
||||
let text = match make_request(client, &request_url).await {
|
||||
Some(text) => text,
|
||||
None => "".to_string(),
|
||||
};
|
||||
let xml: Keyword_search_results = match serde_xml_rs::from_str(&text) {
|
||||
Ok(xml) => xml,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let mut games: Vec<Boardgame_overview> = xml.boardgames.clone();
|
||||
let (best_match, score) = find_best_boardgame(name, games);
|
||||
return Some(best_match.name);
|
||||
}
|
||||
|
||||
async fn get_boardgame_from_id(client: &reqwest::Client, id: i32) -> Option<Boardgame> {
|
||||
let request_url = format!("https://boardgamegeek.com/xmlapi/boardgame/{id}/");
|
||||
println!("searching for id {}", id);
|
||||
|
@ -90,7 +92,8 @@ fn find_best_boardgame(
|
|||
games.sort_by(|b, a| {
|
||||
matcher
|
||||
.fuzzy_match(
|
||||
&a.name.chars().collect::<Vec<char>>()[..name.len()]
|
||||
&a.name.chars().collect::<Vec<char>>()
|
||||
[..std::cmp::min(name.len(), a.name.chars().collect::<Vec<char>>().len())]
|
||||
.iter()
|
||||
.collect::<String>()
|
||||
.to_lowercase(),
|
||||
|
@ -98,7 +101,8 @@ fn find_best_boardgame(
|
|||
)
|
||||
.cmp(
|
||||
&matcher.fuzzy_match(
|
||||
&b.name.chars().collect::<Vec<char>>()[..name.len()]
|
||||
&b.name.chars().collect::<Vec<char>>()
|
||||
[..std::cmp::min(name.len(), b.name.chars().collect::<Vec<char>>().len())]
|
||||
.iter()
|
||||
.collect::<String>()
|
||||
.to_lowercase(),
|
||||
|
@ -111,14 +115,17 @@ fn find_best_boardgame(
|
|||
(
|
||||
games[0].clone(),
|
||||
match matcher.fuzzy_match(
|
||||
&games[0].name.chars().collect::<Vec<char>>()[..name.len()]
|
||||
&games[0].name.chars().collect::<Vec<char>>()[..std::cmp::min(
|
||||
name.len(),
|
||||
games[0].name.chars().collect::<Vec<char>>().len(),
|
||||
)]
|
||||
.iter()
|
||||
.collect::<String>()
|
||||
.to_lowercase(),
|
||||
&name.to_lowercase(),
|
||||
) {
|
||||
Some(val) => val,
|
||||
Nothing => 0,
|
||||
None => 0,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -136,3 +143,93 @@ fn find_best_match(name: String, mut names: Vec<&String>) -> (String, i32) {
|
|||
matcher.fuzzy_match(&names[0], &name).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn read_csv() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut reader = csv::Reader::from_reader(io::stdin());
|
||||
for result in reader.records() {
|
||||
let record = result.unwrap();
|
||||
let game = &record[0];
|
||||
println!("Game: {}", game);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_csv() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
let mut out = File::create("out.csv").unwrap();
|
||||
let mut writer = csv::Writer::from_writer(out);
|
||||
let mut reader = csv::Reader::from_reader(io::stdin());
|
||||
writer
|
||||
.write_record(&[
|
||||
"title",
|
||||
"foundtitle",
|
||||
"minplayers",
|
||||
"maxplayers",
|
||||
"playingtime",
|
||||
"minplaytime",
|
||||
"maxplaytime",
|
||||
"age",
|
||||
])
|
||||
.unwrap();
|
||||
for result in reader.records() {
|
||||
let record = result.unwrap();
|
||||
let game = &record[0];
|
||||
let client = reqwest::Client::new();
|
||||
let boardgame = get_boardgame_from_name(&client, game.to_string()).await;
|
||||
match boardgame {
|
||||
Some(boardgame) => {
|
||||
let minplayers = if (boardgame.minplayers != 0) {
|
||||
boardgame.minplayers.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let maxplayers = if (boardgame.maxplayers != 0) {
|
||||
boardgame.maxplayers.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let playingtime = if (boardgame.playingtime != 0) {
|
||||
boardgame.playingtime.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let minplaytime = if (boardgame.minplaytime != 0) {
|
||||
boardgame.minplaytime.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let maxplaytime = if (boardgame.maxplaytime != 0) {
|
||||
boardgame.maxplaytime.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let age = if (boardgame.age != 0) {
|
||||
boardgame.age.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
writer
|
||||
.write_record(&[
|
||||
game,
|
||||
get_name_from_name(&client, game.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
minplayers.as_str(),
|
||||
maxplayers.as_str(),
|
||||
playingtime.as_str(),
|
||||
minplaytime.as_str(),
|
||||
maxplaytime.as_str(),
|
||||
age.as_str(),
|
||||
])
|
||||
.unwrap()
|
||||
}
|
||||
None => writer
|
||||
.write_record(&[game, "NOT_FOUND", "", "", "", "", "", ""])
|
||||
.unwrap(),
|
||||
};
|
||||
writer.flush();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue