fw 2.14.0

faster workspace management
use crate::errors::AppError;
use crate::util::logger_from_verbosity;
use slog::Logger;
use slog::{crit, debug, o, warn};
use std::collections::BTreeSet;
use std::str::FromStr;
use std::time::SystemTime;

fn main() {
  openssl_probe::init_ssl_cert_env_vars();
  let return_code = _main();
  std::process::exit(return_code)
}

fn _main() -> i32 {
  let matches = crate::app::app().get_matches();

  let logger = logger_from_verbosity(matches.occurrences_of("v"), matches.is_present("q"));

  let config = config::read_config(&logger);
  if config.is_err() {
    warn!(
      logger,
      "Could not read v2.0 config: {:?}. If you are running the setup right now this is expected.", config
    );
  };

  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" => {
      let worker = subcommand_matches
        .value_of("parallelism")
        .and_then(|i| i.parse::<i32>().ok())
        .expect("enforced by clap.rs");

      sync::synchronize(
        config,
        subcommand_matches.is_present("no-progress-bar"),
        subcommand_matches.is_present("only-new"),
        !subcommand_matches.is_present("no-fast-forward-merge"),
        &subcommand_matches
          .values_of("tag")
          .unwrap_or_default()
          .into_iter()
          .map(ToOwned::to_owned)
          .collect(),
        worker,
        &subcommand_logger,
      )
    }
    "add-remote" => {
      let name: &str = subcommand_matches.value_of("NAME").expect("argument required by clap.rs");
      let remote_name: &str = subcommand_matches.value_of("REMOTE_NAME").expect("argument required by clap.rs");
      let url: &str = subcommand_matches.value_of("URL").expect("argument required by clap.rs");
      project::add_remote(config, name, remote_name.to_string(), url.to_string())
    }
    "remove-remote" => {
      let name: &str = subcommand_matches.value_of("NAME").expect("argument required by clap.rs");
      let remote_name: &str = subcommand_matches.value_of("REMOTE_NAME").expect("argument required by clap.rs");
      project::remove_remote(config, name, remote_name.to_string(), &subcommand_logger)
    }
    "add" => {
      let name: Option<&str> = subcommand_matches.value_of("NAME");
      let url: &str = subcommand_matches.value_of("URL").expect("argument required by clap.rs");
      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);
      let tags: Option<BTreeSet<String>> = subcommand_matches.values_of("tag").map(|v| v.into_iter().map(ToOwned::to_owned).collect());
      project::add_entry(config, name, url, after_workon, after_clone, override_path, tags, &subcommand_logger)
    }
    "remove" => project::remove_project(
      config,
      subcommand_matches.value_of("NAME").expect("argument required by clap.rs"),
      subcommand_matches.is_present("purge-directory"),
      &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);
      project::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,
    ),
    "org-import" => setup::org_import(
      config,
      subcommand_matches.value_of("ORG_NAME").expect("argument required by clap.rs"),
      subcommand_matches.is_present("include-archived"),
      &subcommand_logger,
    ),
    "gitlab-import" => {
      let state = subcommand_matches
        .value_of("include")
        .expect("argument required by clap.rs")
        .parse()
        .expect("argument values restricted by clap.rs");
      setup::gitlab_import(config, state, &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,
    ),
    "gen-reworkon" => workon::gen_reworkon(config, &subcommand_logger),
    "reworkon" => workon::reworkon(config, &subcommand_logger),
    "inspect" => project::inspect(
      subcommand_matches.value_of("PROJECT_NAME").expect("argument required by clap.rs"),
      config,
      subcommand_matches.is_present("json"),
      &subcommand_logger,
    ),
    "projectile" => projectile::projectile(config, &subcommand_logger),
    "intellij" => intellij::intellij(config, &subcommand_logger, !subcommand_matches.is_present("no-warn")),
    "print-path" => project::print_path(
      config,
      subcommand_matches.value_of("PROJECT_NAME").expect("argument required by clap.rs"),
      &subcommand_logger,
    ),
    "foreach" => spawn::foreach(
      config,
      subcommand_matches.value_of("CMD").expect("argument required by clap.rs"),
      &subcommand_matches
        .values_of("tag")
        .unwrap_or_default()
        .into_iter()
        .map(ToOwned::to_owned)
        .collect(),
      &subcommand_logger,
      &subcommand_matches.value_of("parallel").map(ToOwned::to_owned),
    ),
    "print-zsh-setup" => crate::shell::print_zsh_setup(subcommand_matches.is_present("with-fzf"), subcommand_matches.is_present("with-skim")),
    "print-bash-setup" => crate::shell::print_bash_setup(subcommand_matches.is_present("with-fzf"), subcommand_matches.is_present("with-skim")),
    "print-fish-setup" => crate::shell::print_fish_setup(subcommand_matches.is_present("with-fzf"), subcommand_matches.is_present("with-skim")),
    "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" => project::ls(
      config,
      &subcommand_matches
        .values_of("tag")
        .unwrap_or_default()
        .into_iter()
        .map(ToOwned::to_owned)
        .collect(),
    ),
    _ => 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);
      0
    }
    Err(error) => {
      crit!(subcommand_logger, "Error running command"; "error" => format!("{:?}", error));
      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 {
    "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)
    }
    "inspect" => {
      let tag_name: String = tag_matches.value_of("tag-name").map(str::to_string).expect("argument enforced by clap.rs");
      tag::inspect_tag(maybe_config, &tag_name)
    }
    "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)
    }
    "autotag" => tag::autotag(
      maybe_config,
      tag_matches.value_of("CMD").expect("argument required by clap.rs"),
      &tag_matches.value_of("tag-name").map(str::to_string).expect("argument enforced by clap.rs"),
      logger,
      &tag_matches.value_of("parallel").map(ToOwned::to_owned),
    ),
    _ => Result::Err(AppError::InternalError("Command not implemented")),
  }
}

mod app;
mod config;
mod errors;
mod git;
mod intellij;
mod project;
mod projectile;
mod setup;
mod shell;
mod spawn;
mod sync;
mod tag;
mod util;
mod workon;
mod ws;