lich 0.2.0

Minimal password management.
Documentation
#[macro_use]
extern crate clap;
extern crate lich;
extern crate rpassword;
extern crate serde_json;
extern crate unicode_segmentation;

use clap::ErrorKind::ArgumentNotFound;
use std::env;
use std::path::PathBuf;
use unicode_segmentation::UnicodeSegmentation;

#[macro_use]
mod common;
mod delete;
mod entry;
mod init;
mod list;
mod generate;
mod get;
mod rekey;
mod set;

//TODO: Refactor to error early.

fn main() {
    let path = path();

    let matches = clap_app!(lich =>
        (version: env!("CARGO_PKG_VERSION"))
        (about: env!("CARGO_PKG_DESCRIPTION"))
        (@setting SubcommandRequiredElseHelp)
        (@setting VersionlessSubcommands)

        (@subcommand list =>
            (about: "List places")
        )
        (@subcommand get =>
            (about: "Get a password")
            (@arg place: +required "The place associated with the password")
        )
        (@subcommand set =>
            (about: "Change a password")
            (@arg place: +required "The place associated with the password")
        )
        (@subcommand delete =>
            (about: "Remove a password")
            (@arg place: +required "The place associated with the password")
        )
        (@subcommand init =>
            (about: "Initialises the password store")
        )
        (@subcommand rekey =>
            (about: "Change the master password")
        )
        (@subcommand generate =>
            (about: "Generate a new password")
            (@arg length: -l --length +takes_value "Password length")
            (@arg chars: -c --chars +takes_value "Allowed characters")
        )
    ).get_matches();

    let res = match matches.subcommand() {
        ("list",   _) => list::run(path),
        ("get",    Some(m)) => get::run(path, m.value_of("place").unwrap()),
        ("set",    Some(m)) => set::run(path, m.value_of("place").unwrap()),
        ("delete", Some(m)) => delete::run(path, m.value_of("place").unwrap()),
        ("init",   _) => init::run(path),
        ("rekey",  _) => rekey::run(path),
        ("generate", Some(m)) => {
            let length = value_t!(m, "length", usize)
                .map(Some)
                .unwrap_or_else(|e| match e.kind {
                    ArgumentNotFound => None,
                    _ => e.exit()
                });

            let chars = m.value_of("chars")
                .map(|s| s.graphemes(true).collect::<Vec<_>>());
            generate::run(length, chars.as_ref().map(Vec::as_slice))
        },
        _ => unreachable!()
    };

    if let Err(e) = res {
        e.exit();
    }
}

fn path() -> PathBuf {
    match env::var_os("LICH") {
        Some(path) =>
            if path.len() > 0 {
                path.into()
            } else {
                default_path()
            },
        None => default_path()
    }
}

fn default_path() -> PathBuf {
    #[cfg(windows)]
    match env::var_os("APPDATA") {
        Some(appdata) => {
            let mut path = PathBuf::from(appdata);
            path.push("lich");
            path
        },
        None => "lich".into()
    }

    #[cfg(not(windows))]
    match env::home_dir() {
        Some(mut home) => {
            home.push(".lich");
            home
        },
        None => ".lich".into()
    }
}