oui-data 0.2.0

Library for looking up information from the IEEE OUI database.
Documentation
use std::{
    collections::HashSet,
    env,
    fs::File,
    io::{self, BufWriter, Write},
    path::Path,
};

fn main() {
    let out_dir = env::var_os("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("oui.rs");
    let out = &mut BufWriter::new(File::create(dest_path).unwrap());

    let mut used_ouis = HashSet::new();

    out.write_all(" /// Raw database of OUI records.\n".as_bytes()).unwrap();
    out.write_all(
        "pub static OUI_ENTRIES: phf::Map<&'static str, OuiData> = phf::phf_map! {\n".as_bytes(),
    )
    .unwrap();

    generate_oui_data("data/oui.csv", out, &mut used_ouis).unwrap();
    generate_oui_data("data/oui28.csv", out, &mut used_ouis).unwrap();
    generate_oui_data("data/oui36.csv", out, &mut used_ouis).unwrap();
    generate_oui_data("data/cid.csv", out, &mut used_ouis).unwrap();
    generate_oui_data("data/iab.csv", out, &mut used_ouis).unwrap();

    out.write_all("};\n".as_bytes()).unwrap();

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

fn generate_oui_data(
    path: &str,
    out: &mut BufWriter<File>,
    used_ouis: &mut HashSet<String>,
) -> io::Result<()> {
    let mut rdr = csv::Reader::from_path(path)?;
    for result in rdr.records() {
        let record = result?;
        let registry = get_registry(record.get(0).unwrap());
        let oui = clean_string(record.get(1).unwrap()).to_ascii_uppercase();
        let organization = clean_string(record.get(2).unwrap());

        if used_ouis.contains(&oui) {
            println!("Discarding duplicate OUI: {oui}: {organization}");
            continue;
        }
        used_ouis.insert(oui.clone());

        let oui_data = format!(
            "\"{oui}\" => OuiData {{
                registry: {registry},
                oui: \"{oui}\",
                organization: \"{organization}\"
            }},\n",
        );
        out.write_all(oui_data.as_bytes())?;
    }

    println!("cargo:rerun-if-changed={path}");

    Ok(())
}

fn get_registry(registry: &str) -> &'static str {
    match registry {
        "MA-L" => "Registry::Mal",
        "MA-M" => "Registry::Mam",
        "MA-S" => "Registry::Mas",
        "CID" => "Registry::Cid",
        "IAB" => "Registry::Iab",
        _ => panic!("Unknown registry: {registry}"),
    }
}

fn clean_string(s: &str) -> String {
    s.replace('\u{00A0}', " ")
        .replace('\\', "\\\\")
        .replace('\n', "\\n")
        .replace("  ", "\\n")
        .replace('"', "\\\"")
        .trim()
        .to_string()
}