pijul 0.12.2

A patch-based distributed version control system, easy to use and fast. Command-line interface.
use super::BasicOptions;
use clap::{AppSettings, Arg, ArgMatches, SubCommand};
use commands::StaticSubcommand;
use error::Error;
use meta;
use meta::{load_signing_key, Meta};
use std::io::{stderr, Write};
use std::process::exit;

pub fn invocation() -> StaticSubcommand {
    return SubCommand::with_name("key")
        .about("Manage signing and SSH keys")
        .subcommand(
            SubCommand::with_name("upload")
                .about("Upload keys to a remote server")
                .arg(Arg::with_name("port")
                     .long("port")
                     .short("p")
                     .help("Port of the SSH server.")
                     .takes_value(true)
                     .required(false))
                .arg(Arg::with_name("local")
                     .long("local")
                     .help("Save keys for the local repository only")
                     .takes_value(false)
                     .required(false))
                .arg(Arg::with_name("address")
                     .help("Address to use, for instance pijul_org@nest.pijul.com.")
                     .takes_value(true)
                     .required(true))
        )
        .subcommand(
            SubCommand::with_name("gen")
                .about("Generate keys. This command generates an SSH key if --signing-id is not given")
                .arg(Arg::with_name("signing-id")
                     .long("signing-id")
                     .help("Generate a signing key for this user id (user ids are email addresses)")
                     .takes_value(true))
                .arg(Arg::with_name("repository")
                     .long("for-repository")
                     .help("Save keys for the given repository only")
                     .takes_value(true)
                     .required(false))
        )
        .subcommand(
            SubCommand::with_name("register")
                .setting(AppSettings::Hidden)
                .about("Register a signature key given in binary on the standard input")
        );
}

pub enum Params<'a> {
    Upload { address: &'a str, port: Option<u16> },
    Gen { signing: Option<&'a str> },
    Register,
    None,
}

pub fn parse_args<'a>(args: &'a ArgMatches) -> Result<Params<'a>, Error> {
    match args.subcommand() {
        ("upload", Some(args)) => Ok(Params::Upload {
            address: args.value_of("address").unwrap(),
            port: args.value_of("port").and_then(|x| x.parse().ok()),
        }),
        ("gen", Some(args)) => Ok(Params::Gen {
            signing: args.value_of("signing-id"),
        }),
        ("register", _) => Ok(Params::Register),
        _ => Ok(Params::None),
    }
}

pub fn run(arg_matches: &ArgMatches) -> Result<(), Error> {
    let mut global = meta::Global::load().unwrap_or_else(|_| meta::Global::new());
    match parse_args(arg_matches)? {
        Params::Upload { address, port } => {
            let local_key = BasicOptions::from_args(arg_matches).ok().and_then(|opts| {
                Meta::load(&opts.repo_root)
                    .ok()
                    .and_then(|meta| meta.signing_key)
            });
            let key = local_key
                .or(global.signing_key)
                .map(|s| load_signing_key(s));
            match key {
                Some(Ok(keys)) => {
                    if keys.keys.is_empty() {
                        return Ok(());
                    }
                    if let Some(remote) = super::remote::parse_ssh_remote_nopath(address, port) {
                        debug!("sending key");
                        remote.session()?.send_key(keys)?
                    }
                }
                Some(Err(e)) => return Err(e),
                None => return Ok(()),
            }
        }
        Params::Gen { signing } => {
            if let Some(identity) = signing {
                global.generate_global_signing_key(identity, None)?
            } else {
                meta::generate_global_ssh_key()?
            }
        }
        Params::Register => unimplemented!(),
        Params::None => {}
    }
    Ok(())
}

pub fn explain(r: Result<(), Error>) {
    if let Err(e) = r {
        if let Error::InARepository { path } = e {
            writeln!(stderr(), "Repository {:?} already exists", path).unwrap();
        } else {
            writeln!(stderr(), "error: {}", e).unwrap();
        }
        exit(1)
    }
}