urn 0.1.0

A crate for handling URNs (Unique Resource Identifiers)
Documentation
use std::{env, fs::File, io::prelude::*, path::PathBuf};

fn dash_to_pascal(s: &str) -> String {
    let ret = s
        .split('-')
        .filter(|s| !s.is_empty())
        .map(|s| s[..1].to_ascii_uppercase() + &s[1..])
        .collect::<Vec<_>>()
        .join("");
    if ret
        .chars()
        .next()
        .as_ref()
        .map_or(false, char::is_ascii_digit)
    {
        "Namespace".to_owned() + &ret
    } else {
        ret
    }
}

fn main() {
    let url1 = "https://www.iana.org/assignments/urn-namespaces/urn-namespaces-1.csv";
    let url2 = "https://www.iana.org/assignments/urn-namespaces/urn-namespaces-2.csv";
    let body1 = reqwest::blocking::get(url1)
        .ok()
        .filter(|resp| resp.status().is_success())
        .and_then(move |resp| resp.bytes().ok().map(|x| x.to_vec()))
        .unwrap_or_else(|| include_bytes!("urn-namespaces-1.csv").to_vec());
    let body2 = reqwest::blocking::get(url2)
        .ok()
        .filter(|resp| resp.status().is_success())
        .and_then(move |resp| resp.bytes().ok().map(|x| x.to_vec()))
        .unwrap_or_else(|| include_bytes!("urn-namespaces-2.csv").to_vec());
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    let mut file = File::create(out_path.join("generated.rs")).unwrap();
    writeln!(file, "#[non_exhaustive]").unwrap();
    writeln!(file, "#[derive(Debug, Eq, PartialEq, Clone, Hash)]").unwrap();
    writeln!(file, "pub enum Namespace {{").unwrap();
    let mut rdr = csv::Reader::from_reader(body1.as_slice());
    for result in rdr.records() {
        let record = result.unwrap();
        let mut comment = Vec::new();
        comment.push(format!("Formal namespace: {}", record.get(0).unwrap()));
        match record.get(1) {
            Some(template) if !template.is_empty() => {
                comment.push(format!("Template: {}", template));
            }
            _ => {}
        }
        match record.get(2) {
            Some(reference) if !reference.is_empty() => {
                comment.push(format!("Reference: {}", reference));
            }
            _ => {}
        }
        writeln!(file, "    /// {}.", comment.join("; ")).unwrap();
        writeln!(file, "    /// See https://www.iana.org/assignments/urn-namespaces/urn-namespaces.xhtml for more info.").unwrap();
        writeln!(file, "    {},", dash_to_pascal(record.get(0).unwrap())).unwrap();
    }
    let mut rdr = csv::Reader::from_reader(body2.as_slice());
    for result in rdr.records() {
        let record = result.unwrap();
        let mut comment = Vec::new();
        comment.push(format!("Informal namespace: {}", record.get(0).unwrap()));
        match record.get(1) {
            Some(template) if !template.is_empty() => {
                comment.push(format!("Template: {}", template));
            }
            _ => {}
        }
        match record.get(2) {
            Some(reference) if !reference.is_empty() => {
                comment.push(format!("Reference: {}", reference));
            }
            _ => {}
        }
        writeln!(file, "    /// {}.", comment.join("; ")).unwrap();
        writeln!(file, "    /// See https://www.iana.org/assignments/urn-namespaces/urn-namespaces.xhtml for more info.").unwrap();
        writeln!(file, "    {},", dash_to_pascal(record.get(0).unwrap())).unwrap();
    }
    writeln!(file, "    Unknown(String),").unwrap();
    writeln!(file, "}}").unwrap();
    writeln!(file).unwrap();
    writeln!(file, "impl Namespace {{").unwrap();
    writeln!(
        file,
        "    /// Check whether the namespace is formal (as defined by IANA)"
    )
    .unwrap();
    writeln!(file, "    pub fn is_formal(&self) -> bool {{").unwrap();
    writeln!(file, "        matches!(self,").unwrap();
    let mut rdr = csv::Reader::from_reader(body1.as_slice());
    writeln!(
        file,
        "{}",
        rdr.records()
            .filter_map(Result::ok)
            .map(|x| format!("Self::{}", dash_to_pascal(x.get(0).unwrap())))
            .collect::<Vec<_>>()
            .join(" |\n            ")
    )
    .unwrap();
    writeln!(file, "        )").unwrap();
    writeln!(file, "    }}").unwrap();
    writeln!(file).unwrap();
    writeln!(
        file,
        "    /// Check whether the namespace is informal (as defined by IANA)"
    )
    .unwrap();
    writeln!(file, "    pub fn is_informal(&self) -> bool {{").unwrap();
    writeln!(file, "        matches!(self,").unwrap();
    let mut rdr = csv::Reader::from_reader(body2.as_slice());
    writeln!(
        file,
        "{}",
        rdr.records()
            .filter_map(Result::ok)
            .map(|x| format!("Self::{}", dash_to_pascal(x.get(0).unwrap())))
            .collect::<Vec<_>>()
            .join(" |\n            ")
    )
    .unwrap();
    writeln!(file, "        )").unwrap();
    writeln!(file, "    }}").unwrap();
    writeln!(file).unwrap();
    writeln!(file, "    pub fn new(id: &str) -> Result<Self> {{").unwrap();
    writeln!(file, "        match id.to_ascii_lowercase().as_str() {{").unwrap();
    let mut rdr = csv::Reader::from_reader(body1.as_slice());
    for result in rdr.records() {
        let record = result.unwrap();
        let nid = record.get(0).unwrap();
        writeln!(
            file,
            "            \"{}\" => Ok(Self::{}),",
            nid,
            dash_to_pascal(nid),
        )
        .unwrap();
    }
    let mut rdr = csv::Reader::from_reader(body2.as_slice());
    for result in rdr.records() {
        let record = result.unwrap();
        let nid = record.get(0).unwrap();
        writeln!(
            file,
            "            \"{}\" => Ok(Self::{}),",
            nid,
            dash_to_pascal(nid),
        )
        .unwrap();
    }
    writeln!(
        file,
        "            s if is_valid_nid(s) => Ok(Self::Unknown(s.to_owned())),"
    )
    .unwrap();
    writeln!(file, "            _ => Err(Error::InvalidNid),").unwrap();
    writeln!(file, "        }}").unwrap();
    writeln!(file, "    }}").unwrap();
    writeln!(file, "}}").unwrap();
    writeln!(file).unwrap();
    writeln!(file, "impl fmt::Display for Namespace {{").unwrap();
    writeln!(file, "    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {{").unwrap();
    writeln!(file, "        write!(f, \"{{}}\", match self {{").unwrap();
    let mut rdr = csv::Reader::from_reader(body1.as_slice());
    for result in rdr.records() {
        let record = result.unwrap();
        let nid = record.get(0).unwrap();
        writeln!(
            file,
            "            Self::{} => \"{}\",",
            dash_to_pascal(nid),
            nid,
        )
        .unwrap();
    }
    let mut rdr = csv::Reader::from_reader(body2.as_slice());
    for result in rdr.records() {
        let record = result.unwrap();
        let nid = record.get(0).unwrap();
        writeln!(
            file,
            "            Self::{} => \"{}\",",
            dash_to_pascal(nid),
            nid,
        )
        .unwrap();
    }
    writeln!(file, "            Self::Unknown(s) => s,").unwrap();
    writeln!(file, "        }})").unwrap();
    writeln!(file, "    }}").unwrap();
    writeln!(file, "}}").unwrap();
}