git-req 2.3.0

Check out merge requests from your GitLab/GitHub hosted repos with ease!
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;

use man::prelude::*;
use regex::Regex;
use yaml_rust::yaml::Hash;
use yaml_rust::{Yaml, YamlLoader};

fn s2y<T: ToString>(s: T) -> Yaml {
    Yaml::String(s.to_string())
}

fn parse_str<T: ToString>(a: &Hash, k: T) -> String {
    let s = a.get(&s2y(k));
    match s {
        Some(ele) => ele.as_str().unwrap().to_string(),
        None => "".to_string(),
    }
}

fn set_authors(p: Manual) -> Manual {
    let authors = env::var("CARGO_PKG_AUTHORS").unwrap();
    let re = Regex::new(r"([\w|\s]+)\s+<([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9]+)>").unwrap();
    re.captures_iter(&authors)
        .map(|x| {
            (
                x.get(1).map_or("", |m| m.as_str()),
                x.get(2).map_or("", |m| m.as_str()),
            )
        })
        .fold(p, |p, c| p.author(Author::new(c.0).email(c.1)))
}

fn set_args(mut p: Manual, y: &Yaml) -> Manual {
    let args = y["args"].as_vec().unwrap();
    for karg in args {
        let (key, val) = karg.as_hash().unwrap().iter().next().unwrap();
        let key = key.as_str().unwrap();
        let arg = val.as_hash().unwrap();
        if arg.get(&s2y("index")).is_none() {
            let takes_value = arg.get(&s2y("takes_value"));
            if takes_value.is_none() || !takes_value.unwrap().as_bool().unwrap() {
                let mut f = Flag::new()
                    .long(parse_str(&arg, "long").as_str())
                    .help(parse_str(&arg, "help").as_str());
                let short = parse_str(&arg, "short");
                if !short.is_empty() {
                    f = f.short(short.as_str());
                }
                p = p.flag(f);
            } else {
                let o_val = parse_str(&arg, "value_name");
                let key = if !o_val.is_empty() {
                    o_val.as_str()
                } else {
                    key
                };
                let mut o = Opt::new(key)
                    .long(parse_str(&arg, "long").as_str())
                    .help(parse_str(&arg, "help").as_str());
                let short = parse_str(&arg, "short");
                let default_value = parse_str(&arg, "default_value");
                if !short.is_empty() {
                    o = o.short(short.as_str());
                }
                if !default_value.is_empty() {
                    o = o.default_value(default_value.as_str());
                }
                p = p.option(o);
            }
        } else {
            p = p.arg(Arg::new(key));
        }
    }
    p
}

fn generate_manpage(yml: &Yaml, trg_dir: &Path) -> std::io::Result<()> {
    let out_location = trg_dir.join("git-req.1");

    let page = Manual::new("git-req").about(yml["about"].as_str().unwrap());
    let page = set_authors(page);
    let page = set_args(page, yml);

    let mut output = File::create(out_location)?;
    write!(output, "{}", page.render())?;

    Ok(())
}

fn main() -> std::io::Result<()> {
    let trg_dir = match env::var("CARGO_TARGET_DIR") {
        Ok(s) => s,
        Err(_) => String::from("target"),
    };
    let trg_dir = Path::new(&trg_dir).join(&env::var("PROFILE").unwrap());

    let yml = YamlLoader::load_from_str(include_str!("cli-flags.yml")).unwrap();
    let yml = yml.get(0).unwrap();

    generate_manpage(yml, &trg_dir)?;

    Ok(())
}