locode 0.1.0

Standardized country and city codes
Documentation
#[cfg(not(feature = "_dev_build_fresh"))]
fn main() {
    println!("cargo:rerun-if-changed=build.rs");
}

#[cfg(feature = "_dev_build_fresh")]
fn main() {
    use std::fs;

    println!("cargo:rerun-if-changed=build.rs");

    let iso3166_rs = generate_iso3166(RawCsvIso3166::from_csv_file());
    let iata_rs = generate_iata_city(RawCsvIata::from_csv_file());

    #[cfg(feature = "feat-build-fresh")]
    {
        use std::env;

        let out_dir = env::var("OUT_DIR").expect("Failed to get OUT_DIR");

        fs::write(format!("{out_dir}/iso3166.rs"), iso3166_rs)
            .expect("Failed to write generated ISO 3166 file");
        fs::write(format!("{out_dir}/iata.rs"), iata_rs)
            .expect("Failed to write generated IATA file");
    }

    #[cfg(not(feature = "feat-build-fresh"))]
    {
        use std::process;

        fs::write("./src/iso3166.rs", iso3166_rs).expect("Failed to write generated ISO 3166 file");
        fs::write("./src/iata.rs", iata_rs).expect("Failed to write generated IATA file");

        process::Command::new("rustfmt")
            .arg("./src/iso3166.rs")
            .arg("./src/iata.rs")
            .status()
            .expect("Failed to format the file");
    }
}

#[cfg(feature = "_dev_build_fresh")]
fn generate_iso3166(iso3166s: Vec<RawCsvIso3166>) -> String {
    use std::fmt::Write as _;
    use std::fs;

    println!("cargo:rerun-if-changed=template/iso3166.rs");

    let mut generated =
        fs::read_to_string("template/iso3166.rs").expect("Failed to read ISO 3166 template file");

    generated.push_str("\nmapping! {\n");

    for RawCsvIso3166 {
        alpha_2_code,
        alpha_3_code,
        numeric_code,
        independent: _,
        short_name_en,
        short_name_uppercase_en,
        full_name_en,
        short_name_zh,
    } in iso3166s
    {
        generated
            .write_fmt(format_args!(
                "    {alpha_2_code} {alpha_3_code} => {numeric_code}, {short_name_en:?}, \
                 {short_name_uppercase_en:?}, {full_name_en:?}, {short_name_zh:?};\n"
            ))
            .expect("`write_fmt` failed");
    }

    generated.push_str("}\n");

    generated
}

#[cfg(feature = "_dev_build_fresh")]
#[derive(serde_derive::Deserialize)]
struct RawCsvIso3166 {
    alpha_2_code: String,
    alpha_3_code: String,
    numeric_code: u16,
    #[allow(unused)]
    independent: String,
    short_name_en: Option<String>,
    short_name_uppercase_en: Option<String>,
    full_name_en: Option<String>,
    short_name_zh: Option<String>,
}

#[cfg(feature = "_dev_build_fresh")]
impl RawCsvIso3166 {
    fn from_csv_file() -> Vec<Self> {
        let path = std::env::var("LOCODE_RAW_ISO3166_CSV").ok();

        let mut rdr = csv::ReaderBuilder::new()
            .has_headers(true)
            .from_path(
                path.as_deref()
                    .unwrap_or("data/locode-raw-iso3166/country-codes-with-cn.csv"),
            )
            .expect("Failed to read CSV file");

        rdr.deserialize()
            .map(|result| result.expect("Failed to deserialize CSV row"))
            .collect()
    }
}

#[cfg(feature = "_dev_build_fresh")]
fn generate_iata_city(mut items: Vec<RawCsvIata>) -> String {
    use std::fmt::Write as _;
    use std::fs;

    println!("cargo:rerun-if-changed=template/iata.rs");

    let mut generated =
        fs::read_to_string("template/iata.rs").expect("Failed to read IATA template file");

    items.sort_unstable_by(|l, r| l.city_code.cmp(&r.city_code));
    items.dedup_by(|l, r| l.city_code.eq(&r.city_code));

    generated.push_str("\nmapping! {\n");

    for RawCsvIata {
        time_zone,
        city_code,
        country,
        city,
        ..
    } in items
    {
        generated
            .write_fmt(format_args!(
                "    {city_code} {country} => {city:?}, \"{time_zone}\";\n"
            ))
            .expect("`write_fmt` failed");
    }

    generated.push_str("}\n");

    generated
}

#[cfg(feature = "_dev_build_fresh")]
#[allow(unused)]
#[derive(serde_derive::Deserialize)]
struct RawCsvIata {
    code: String,
    icao: String,
    name: String,
    latitude: String,
    longitude: String,
    elevation: String,
    url: Option<String>,
    time_zone: String,
    city_code: String,
    country: String,
    city: Option<String>,
    state: Option<String>,
    county: Option<String>,
    r#type: String,
}

#[cfg(feature = "_dev_build_fresh")]
impl RawCsvIata {
    fn from_csv_file() -> Vec<Self> {
        let path = std::env::var("LOCODE_RAW_IATA_CSV").ok();

        let mut rdr = csv::ReaderBuilder::new()
            .has_headers(true)
            .from_path(
                path.as_deref()
                    .unwrap_or("data/locode-raw-airports/airports.csv"),
            )
            .expect("Failed to read CSV file");

        rdr.deserialize()
            .map(|result| result.expect("Failed to deserialize CSV row"))
            .collect()
    }
}