pijul 0.12.2

A patch-based distributed version control system, easy to use and fast. Command-line interface.
use clap::{Arg, ArgMatches, SubCommand};
use commands::{default_explain, BasicOptions, StaticSubcommand};
use error::Error;
use meta::{Meta, Repository};

pub fn invocation() -> StaticSubcommand {
    return SubCommand::with_name("remote")
        .about("View and modify saved remote repositories.")
        .subcommand(default_subcommand())
        .subcommand(delete_subcommand())
        .subcommand(list_subcommand())
        .subcommand(set_subcommand());
}

pub fn list_subcommand() -> StaticSubcommand {
    SubCommand::with_name("list").about("List all saved repositories.")
}

pub fn set_subcommand() -> StaticSubcommand {
    SubCommand::with_name("set")
        .about("Add or update a remote.")
        .arg(
            Arg::with_name("name")
                .help("Short name to identify this repository.")
                .required(true),
        )
        .arg(
            Arg::with_name("remote")
                .help("URL to the remote repository.")
                .required(true),
        )
        .arg(
            Arg::with_name("port")
                .short("p")
                .long("port")
                .help("Port of the remote ssh server.")
                .takes_value(true)
                .validator(|val| {
                    let x: Result<u16, _> = val.parse();
                    match x {
                        Ok(_) => Ok(()),
                        Err(_) => Err(val),
                    }
                }),
        )
}

pub fn default_subcommand() -> StaticSubcommand {
    SubCommand::with_name("default")
        .about("Set default remote for push and pull.")
        .arg(
            Arg::with_name("name")
                .help("Name of the default repository.")
                .required(true),
        )
        .arg(
            Arg::with_name("push")
                .help("Only set the push repository (and other types set by flags)")
                .long("push"),
        )
        .arg(
            Arg::with_name("pull")
                .help("Only set the pull repository (and other types set by flags)")
                .long("pull"),
        )
}

pub fn delete_subcommand() -> StaticSubcommand {
    SubCommand::with_name("delete")
        .about("Delete a remote.")
        .arg(
            Arg::with_name("name")
                .help("Name of the repository to delete.")
                .required(true),
        )
}

pub fn run(arg_matches: &ArgMatches) -> Result<(), Error> {
    let opts = BasicOptions::from_args(arg_matches)?;
    match arg_matches.subcommand() {
        ("list", _) => run_list(&opts),
        ("set", Some(sub_args)) => run_set(&opts, sub_args),
        ("delete", Some(sub_args)) => run_delete(&opts, sub_args),
        ("default", Some(sub_args)) => run_default(&opts, sub_args),
        _ => Ok(()),
    }
}

pub fn run_default(opts: &BasicOptions, args: &ArgMatches) -> Result<(), Error> {
    let name = args.value_of("name").unwrap(); // required
    let arg_pull = args.is_present("pull");
    let arg_push = args.is_present("push");
    let mut meta = Meta::load(&opts.repo_root).unwrap_or(Meta::new());

    // validate that the name exists
    if !meta.remote.contains_key(name) {
        println!("{} is not a named remote repository.", name);
        println!("You can add a remote with pijul remote set, or list current remotes with pijul remote list");
        return Err(Error::MissingRemoteRepository);
    }

    // if all options are unset, default to all
    // otherwise only use the options that were set
    let (set_push, set_pull) = match (arg_pull, arg_push) {
        (false, false) => (true, true),
        _ => (arg_pull, arg_push),
    };

    if set_push {
        meta.push = Some(name.to_string());
    }

    if set_pull {
        meta.pull = Some(name.to_string());
    }

    meta.save(&opts.repo_root)?;
    Ok(())
}

pub fn run_list(opts: &BasicOptions) -> Result<(), Error> {
    let meta = Meta::load(&opts.repo_root).unwrap_or(Meta::new());
    for (name, remote) in meta.remote.iter() {
        println!("{} => {}", name, remote);
    }
    Ok(())
}

pub fn run_set(opts: &BasicOptions, args: &ArgMatches) -> Result<(), Error> {
    let name = args.value_of("name").unwrap(); // required argument
    let remote = args.value_of("remote").unwrap(); // required argument
    let mut meta = Meta::load(&opts.repo_root).unwrap_or(Meta::new());
    let repo: Repository = Repository {
        address: remote.to_string(),
        port: args.value_of("port").map(|x| x.parse().unwrap()),
    };
    meta.remote.insert(name.to_string(), repo);
    meta.save(&opts.repo_root)?;
    Ok(())
}

pub fn run_delete(opts: &BasicOptions, args: &ArgMatches) -> Result<(), Error> {
    let name = args.value_of("name").unwrap(); // required argument
    let mut meta = Meta::load(&opts.repo_root).unwrap_or(Meta::new());
    meta.remote.remove(name);

    // remove from default push/pull
    if meta
        .push
        .as_ref()
        .map(|push| push == &name)
        .unwrap_or(false)
    {
        meta.push = None;
    }
    if meta
        .pull
        .as_ref()
        .map(|pull| pull == &name)
        .unwrap_or(false)
    {
        meta.pull = None;
    }

    meta.save(&opts.repo_root)?;
    Ok(())
}

pub fn explain(res: Result<(), Error>) {
    default_explain(res)
}