use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use rusqlite::Connection;
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
};
use world_tables_base::{Key, EntityLabel, Country, State, City, Currency, WorldRegion, WorldSubregion};
use world_tables_data::MIGRATIONS;
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
#[clap(display_order = 1)]
Local {
#[arg(short, long, display_order = 1, value_name = "DB_FILE")]
dbpath: Option<PathBuf>,
},
#[clap(display_order = 2)]
Server {
#[clap(short, long, display_order = 1, default_value_t = String::from("127.0.0.1"))]
address: String,
#[clap(short, long, display_order = 2, default_value_t = 3000)]
port: u16,
},
}
impl Cli {
fn execute(self) -> Result<()> {
match self.command {
Commands::Local { dbpath } => {
let dbpath = if let Some(path) = dbpath {
path
} else {
PathBuf::from("world.db3")
};
let mut conn = Connection::open(&dbpath).context("Could not open database file")?;
conn.pragma_update(None, "journal_mode", "WAL")?;
conn.pragma_update(None, "synchronous", "NORMAL")?;
conn.pragma_update(None, "foreign_keys", "ON")?;
MIGRATIONS.to_latest(&mut conn)?;
let mut reader = csv::Reader::from_reader(include_str!("../data/countries.csv").as_bytes());
let countries = reader
.deserialize()
.map(|result| {
let record: HashMap<String, String> = result?;
Ok(record)
})
.collect::<Result<Vec<HashMap<_, _>>>>()?;
let currencies = countries
.iter()
.map(|rec| {
Currency {
iso: Key::new(rec["currency"].to_owned()),
name: rec["currency_name"].to_owned(),
symbol: rec["currency_symbol"].to_owned(),
..Default::default()
}
})
.collect::<HashSet<_>>();
for currency in currencies {
currency.save(&mut conn)?;
}
for record in &countries {
let region = match WorldRegion::key_with_name(&conn, &record["region"])? {
Key(None) => EntityLabel::None,
some => EntityLabel::KeyLabel(some, record["region"].to_owned()),
};
let subregion = match WorldSubregion::key_with_name(&conn, &record["subregion"])? {
Key(None) => EntityLabel::None,
some => EntityLabel::KeyLabel(some, record["subregion"].to_owned()),
};
let country = Country {
iso2: Key::new(record["iso2"].to_owned()),
iso3: record["iso3"].to_owned(),
name: record["name"].to_owned(),
code: record["numeric_code"].parse().context("Failed parsing numeric code")?,
capital: EntityLabel::KeyLabel(Key(None), record["capital"].to_owned()),
currency: EntityLabel::KeyLabel(Key::new(record["currency"].to_owned()), record["currency_name"].to_owned()),
tld: record["tld"].to_owned(),
native: record["native"].to_owned(),
region,
subregion,
latitude: record["latitude"].parse().context("Failed parsing country latitude")?,
longitude: record["longitude"].parse().context("Failed parsing country longitude")?,
emoji: record["emoji"].to_owned(),
emoji_u: record["emojiU"].to_owned(),
..Default::default()
};
country.save(&mut conn).unwrap();
}
conn.execute("CREATE UNIQUE INDEX country_names ON countries(name);", []).unwrap();
let mut reader = csv::Reader::from_reader(include_str!("../data/states.csv").as_bytes());
for record in reader.deserialize() {
let record: HashMap<String, String> = record?;
let state = State {
name: record["name"].to_owned(),
country: EntityLabel::KeyLabel(Key::new(record["country_code"].to_owned()), record["country_name"].to_owned()),
code: record["state_code"].to_owned(),
latitude: record["latitude"].parse().ok(),
longitude: record["longitude"].parse().ok(),
..Default::default()
};
state.save(&mut conn).unwrap();
}
conn.execute("CREATE INDEX state_names ON states(name);", []).unwrap();
let mut reader = csv::Reader::from_reader(include_str!("../data/cities.csv").as_bytes());
for record in reader.deserialize() {
let record: HashMap<String, String> = record?;
let state = match State::key_with_name(&conn, &record["state_name"])? {
Key(None) => EntityLabel::None,
some => EntityLabel::KeyLabel(some, record["state_name"].to_owned()),
};
let city = City {
name: record["name"].to_owned(),
state,
country: EntityLabel::KeyLabel(Key::new(record["country_code"].to_owned()), record["country_name"].to_owned()),
latitude: record["latitude"].parse().ok(),
longitude: record["longitude"].parse().ok(),
..Default::default()
};
city.save(&mut conn).unwrap();
}
conn.execute("CREATE INDEX city_names ON cities(name);", []).unwrap();
for record in countries {
conn.execute(
"UPDATE countries SET capital_id = (SELECT id FROM cities WHERE cities.name = countries.capital AND cities.country_id = ?1)
WHERE iso2 = ?1;",
[&record["iso2"]]
).unwrap();
}
}
Commands::Server {..} => {
todo!();
}
}
Ok(())
}
}
fn main() -> Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
Cli::parse().execute()
}