fw 1.4.2

faster workspace management
#[macro_use]
extern crate clap;

#[macro_use]
extern crate slog;
extern crate slog_term;

#[macro_use]
extern crate serde_derive;
extern crate serde_json;

extern crate git2;

extern crate rayon;

extern crate core;

extern crate atty;

extern crate ansi_term;

extern crate rand;

extern crate pbr;

#[cfg(test)]
#[macro_use]
extern crate maplit;

extern crate regex;

#[cfg(test)]
extern crate spectral;

use clap::{App, AppSettings, Arg, SubCommand};
use errors::AppError;
use slog::{DrainExt, Level, LevelFilter, Logger};
use std::str::FromStr;
use std::time::SystemTime;

fn logger_from_verbosity(verbosity: u64, quiet: &bool) -> Logger {
  let log_level: Level = match verbosity {
  _ if *quiet => Level::Error,
  0 => Level::Warning,
  1 => Level::Info,
  2 => Level::Debug,
  3 | _ => Level::Trace,
  };

  let drain = slog_term::StreamerBuilder::new()
    .auto_color()
    .stderr()
    .build();
  let filter = LevelFilter::new(drain, log_level);
  let logger = Logger::root(filter.fuse(), o!());
  debug!(logger, "Logger ready" ; "level" => format!("{:?}", log_level));
  logger
}

fn main() {
  let matches = App::new("fw")
    .version(crate_version!())
    .author("Brocode inc <bros@brocode.sh>")
    .about("fast workspace manager")
    .setting(AppSettings::SubcommandRequired)
    .arg(Arg::with_name("v")
           .short("v")
           .multiple(true)
           .help("Sets the level of verbosity"))
    .arg(Arg::with_name("q").short("q").help("Make fw quiet"))
    .subcommand(SubCommand::with_name("sync").about("Sync workspace"))
    .subcommand(SubCommand::with_name("print-zsh-setup").about("Prints zsh completion code.").arg(Arg::with_name("--with-fzf").short("-f").help("Integrate with fzf")))
    .subcommand(SubCommand::with_name("setup")
                  .about("Setup config from existing workspace")
                  .arg(Arg::with_name("WORKSPACE_DIR")
                         .value_name("WORKSPACE_DIR")
                         .index(1)
                         .required(true)))
    .subcommand(SubCommand::with_name("import")
                  .about("Import existing git folder to fw")
                  .arg(Arg::with_name("PROJECT_DIR")
                         .value_name("PROJECT_DIR")
                         .index(1)
                         .required(true)))
    .subcommand(SubCommand::with_name("add")
                  .about("Add project to config")
                  .arg(Arg::with_name("NAME")
                         .value_name("NAME")
                         .index(2)
                         .required(false))
                  .arg(Arg::with_name("URL")
                         .value_name("URL")
                         .index(1)
                         .required(true)))
    .subcommand(SubCommand::with_name("foreach")
                  .about("Run script on each project")
                  .arg(Arg::with_name("CMD")
                          .value_name("CMD")
                          .required(true))
                  .arg(Arg::with_name("tag")
                          .long("tag")
                          .short("t")
                          .help("Filter projects by tag. More than 1 is allowed.")
                          .required(false)
                          .takes_value(true)
                          .multiple(true)))
    .subcommand(SubCommand::with_name("export")
                  .about("Exports project as fw shell script")
                  .arg(Arg::with_name("PROJECT_NAME")
                         .value_name("PROJECT_NAME")
                         .index(1)
                         .required(true)))
    .subcommand(SubCommand::with_name("print-path")
                  .about("Print project path on stdout")
                  .arg(Arg::with_name("PROJECT_NAME")
                         .value_name("PROJECT_NAME")
                         .index(1)
                         .required(true)))
    .subcommand(SubCommand::with_name("projectile").about("Write projectile bookmarks"))
    .subcommand(SubCommand::with_name("ls").about("List projects"))
    .subcommand(SubCommand::with_name("gen-workon")
                  .about("Generate sourceable shell code to work on project")
                  .arg(Arg::with_name("PROJECT_NAME")
                         .value_name("PROJECT_NAME")
                         .index(1)
                         .required(true))
                  .arg(Arg::with_name("quick")
                         .required(false)
                         .short("x")
                         .help("Don't generate post_workon shell code, only cd into the folder")))
    .subcommand(SubCommand::with_name("update")
                  .about("Modifies project settings.")
                  .arg(Arg::with_name("NAME").value_name("NAME").required(true))
                  .arg(Arg::with_name("git")
                         .value_name("URL")
                         .long("git-url")
                         .takes_value(true)
                         .required(false))
                  .arg(Arg::with_name("override-path")
                         .value_name("override-path")
                         .long("override-path")
                         .takes_value(true)
                         .required(false))
                  .arg(Arg::with_name("after-workon")
                         .value_name("after-workon")
                         .long("after-workon")
                         .takes_value(true)
                         .required(false))
                  .arg(Arg::with_name("after-clone")
                         .value_name("after-clone")
                         .long("after-clone")
                         .takes_value(true)
                         .required(false)))
    .subcommand(SubCommand::with_name("tag")
                  .alias("tags")
                  .about("Allows working with tags.")
                  .subcommand(SubCommand::with_name("ls")
                                .alias("list")
                                .about("Lists tags")
                                .arg(Arg::with_name("PROJECT_NAME")
                                       .value_name("PROJECT_NAME")
                                       .required(false)))
                  .subcommand(SubCommand::with_name("tag-project")
                                .about("Add tag to project")
                                .arg(Arg::with_name("PROJECT_NAME")
                                       .value_name("PROJECT_NAME")
                                       .required(true))
                                .arg(Arg::with_name("tag-name")
                                       .value_name("tag")
                                       .required(true)))
                  .subcommand(SubCommand::with_name("untag-project")
                                .about("Removes tag from project")
                                .arg(Arg::with_name("PROJECT_NAME")
                                       .value_name("PROJECT_NAME")
                                       .required(true))
                                .arg(Arg::with_name("tag-name")
                                       .value_name("tag")
                                       .required(true)))
                  .subcommand(SubCommand::with_name("rm")
                                .about("Deletes a tag. Will not untag projects.")
                                .arg(Arg::with_name("tag-name")
                                       .value_name("tag name")
                                       .required(true)))
                  .subcommand(SubCommand::with_name("add")
                                .alias("update")
                                .alias("create")
                                .about("Creates a new tag. Replaces existing.")
                                .arg(Arg::with_name("tag-name")
                                       .value_name("tag name")
                                       .required(true))
                                .arg(Arg::with_name("after-workon")
                                       .value_name("after-workon")
                                       .long("after-workon")
                                       .takes_value(true)
                                       .required(false))
                                .arg(Arg::with_name("priority")
                                       .value_name("priority")
                                       .long("priority")
                                       .takes_value(true)
                                       .required(false))
                                .arg(Arg::with_name("workspace")
                                       .value_name("workspace")
                                       .long("workspace")
                                       .takes_value(true)
                                       .required(false))
                                .arg(Arg::with_name("after-clone")
                                       .value_name("after-clone")
                                       .long("after-clone")
                                       .takes_value(true)
                                       .required(false))))
    .get_matches();

  let logger = logger_from_verbosity(matches.occurrences_of("v"), &matches.is_present("q"));
  let config = config::get_config(&logger);

  let subcommand_name = matches.subcommand_name()
                               .expect("subcommand required by clap.rs")
                               .to_owned();
  let subcommand_matches = matches.subcommand_matches(&subcommand_name)
                                  .expect("subcommand matches enforced by clap.rs");
  let subcommand_logger = logger.new(o!("command" => subcommand_name.clone()));
  let now = SystemTime::now();
  let result: Result<String, AppError> = match subcommand_name.as_ref() {
                                         "sync" => sync::synchronize(config, &subcommand_logger),
                                         "add" => {
                                           config::add_entry(config,
                                                             subcommand_matches.value_of("NAME"),
                                                             subcommand_matches.value_of("URL")
                                                                               .expect("argument required by clap.rs"),
                                                             &subcommand_logger)
                                         }
                                         "update" => {
    let name: &str = subcommand_matches.value_of("NAME")
                                       .expect("argument required by clap.rs");
    let git: Option<String> = subcommand_matches.value_of("git").map(str::to_string);
    let after_workon: Option<String> = subcommand_matches.value_of("after-workon")
                                                         .map(str::to_string);
    let after_clone: Option<String> = subcommand_matches.value_of("after-clone")
                                                        .map(str::to_string);
    let override_path: Option<String> = subcommand_matches.value_of("override-path")
                                                          .map(str::to_string);
    config::update_entry(config,
                         name,
                         git,
                         after_workon,
                         after_clone,
                         override_path,
                         &subcommand_logger)
  }
                                         "setup" => {
                                           setup::setup(subcommand_matches.value_of("WORKSPACE_DIR")
                                                                          .expect("argument required by clap.rs"),
                                                        &subcommand_logger)
                                         }
                                         "import" => {
                                           setup::import(config, subcommand_matches.value_of("PROJECT_DIR")
                                                                          .expect("argument required by clap.rs"),
                                                        &subcommand_logger)
                                         }
                                         "gen-workon" => {
                                           workon::gen(subcommand_matches.value_of("PROJECT_NAME")
                                                                         .expect("argument required by clap.rs"),
                                                       config,
                                                       subcommand_matches.is_present("quick"),
                                                       &subcommand_logger)
                                         }
                                         "projectile" => projectile::projectile(config, &subcommand_logger),
                                         "print-path" => {
                                           workon::print_path(config,
                                                              subcommand_matches.value_of("PROJECT_NAME")
                                                                                .expect("argument required by clap.rs"), &subcommand_logger)
                                         }
                                         "export" => {
                                           export::export_project(config,
                                                                  subcommand_matches.value_of("PROJECT_NAME")
                                                                                    .expect("argument required by clap.rs"))
                                         }
                                         "foreach" => {
                                           sync::foreach(config,
                                                         subcommand_matches.value_of("CMD")
                                                                           .expect("argument required by clap.rs"),
                                                         &subcommand_matches.values_of_lossy("tag").unwrap_or_default().into_iter().collect(),
                                                         &subcommand_logger)
                                         }
    "print-zsh-setup" => {
      print_zsh_setup(subcommand_matches.is_present("--with-fzf"))
    },
                                         "tag" => {
    let subsubcommand_name: String = subcommand_matches.subcommand_name()
                                                       .expect("subcommand matches enforced by clap.rs")
                                                       .to_owned();
    let subsubcommand_matches: clap::ArgMatches =
      subcommand_matches.subcommand_matches(&subsubcommand_name)
                        .expect("subcommand matches enforced by clap.rs")
                        .to_owned();
    execute_tag_subcommand(config,
                           &subsubcommand_name,
                           &subsubcommand_matches,
                           &subcommand_logger)
  }
                                         "ls" => workon::ls(config),
                                         _ => Result::Err(AppError::InternalError("Command not implemented")),
                                         }
                                         .and_then(|_| now.elapsed().map_err(AppError::ClockError))
                                         .map(|duration| format!("{}sec", duration.as_secs()));

  match result {
  Ok(time) => debug!(subcommand_logger, "Done"; "time" => time),
  Err(error) => {
    crit!(subcommand_logger, "Error running command"; "error" => format!("{:?}", error));
    std::process::exit(1)
  }

  }
}

fn execute_tag_subcommand(maybe_config: Result<config::Config, AppError>,
                          tag_command_name: &str,
                          tag_matches: &clap::ArgMatches,
                          logger: &Logger)
                          -> Result<(), AppError> {
  match tag_command_name.as_ref() {
  "ls" => {
    let maybe_project_name: Option<String> = tag_matches.value_of("PROJECT_NAME").map(str::to_string);
    tag::list_tags(maybe_config, maybe_project_name, logger)
  }
  "tag-project" => {
    let project_name: String = tag_matches.value_of("PROJECT_NAME")
                                          .map(str::to_string)
                                          .expect("argument enforced by clap.rs");
    let tag_name: String = tag_matches.value_of("tag-name")
                                      .map(str::to_string)
                                      .expect("argument enforced by clap.rs");
    tag::add_tag(maybe_config, project_name, tag_name, logger)
  }
  "untag-project" => {
    let project_name: String = tag_matches.value_of("PROJECT_NAME")
                                          .map(str::to_string)
                                          .expect("argument enforced by clap.rs");
    let tag_name: String = tag_matches.value_of("tag-name")
                                      .map(str::to_string)
                                      .expect("argument enforced by clap.rs");
    tag::remove_tag(maybe_config, project_name, &tag_name, logger)
  }
  "rm" => {
    let tag_name: String = tag_matches.value_of("tag-name")
                                      .map(str::to_string)
                                      .expect("argument enforced by clap.rs");
    tag::delete_tag(maybe_config, &tag_name, logger)
  }
  "add" => {
    let tag_name: String = tag_matches.value_of("tag-name")
                                      .map(str::to_string)
                                      .expect("argument enforced by clap.rs");
    let after_workon: Option<String> = tag_matches.value_of("after-workon").map(str::to_string);
    let after_clone: Option<String> = tag_matches.value_of("after-clone").map(str::to_string);
    let tag_workspace: Option<String> = tag_matches.value_of("workspace").map(str::to_string);
    let priority: Option<u8> = tag_matches.value_of("priority")
                                          .map(u8::from_str)
                                          .map(|p| p.expect("invalid tag priority value, must be an u8"));
    tag::create_tag(maybe_config,
                    tag_name,
                    after_workon,
                    after_clone,
                    priority,
                    tag_workspace,
                    logger)
  }
  _ => Result::Err(AppError::InternalError("Command not implemented")),
  }
}

fn print_zsh_setup(use_fzf: bool) -> Result<(), AppError> {
  let fw_completion = include_str!("shell/setup.zsh");
  let basic_workon = include_str!("shell/workon.zsh");
  let fzf_workon = include_str!("shell/workon-fzf.zsh");
  println!("{}", fw_completion);
  if use_fzf {
    println!("{}", fzf_workon);
  } else {
    println!("{}", basic_workon);
  }
  Ok(())
}

mod errors;
mod config;
mod sync;
mod setup;
mod workon;
mod projectile;
mod tag;
mod export;