aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
//! dev-suite cli tool to install and update devsuite and it's tooling
use anyhow::{
  format_err,
  Result,
};
use configamajig::*;
use dialoguer::{
  theme::ColorfulTheme,
  Checkboxes,
  Select,
};
use shared::find_root;
#[cfg(target_family = "unix")]
use std::os::unix::fs::OpenOptionsExt;
#[cfg(target_os = "macos")]
use std::path::PathBuf;
use std::{
  fs::{
    create_dir_all,
    OpenOptions,
  },
  process::Command,
};
use which::which;

#[derive(structopt::StructOpt)]
enum Args {
  /// Download and install all of dev-suite
  Install,
  /// Initialize the repo to use dev-suite and it's tools
  Init,
  /// Commands for configuration of dev-suite
  Config(Config),
}

#[derive(structopt::StructOpt)]
/// dev-suite config commands
enum Config {
  /// dev-suite config commands for the current user
  User(User),
  /// dev-suite config commands for the current repo
  Repo(Repo),
}

#[derive(structopt::StructOpt)]
enum User {
  /// Initialize the user with a name
  Init { name: String },
  /// Show the current user
  Show,
}

#[derive(structopt::StructOpt)]
enum Repo {
  /// Initialize the repo with a config
  Init,
  /// Show the repo config
  Show,
  /// Add someone as a maintainer
  Add(Add),
}
#[derive(structopt::StructOpt)]
enum Add {
  /// Make self the maintainer
  Me,
}

#[paw::main]
fn main(args: Args) {
  if let Err(e) = match args {
    Args::Init => init(),
    Args::Install => install(),
    Args::Config(conf) => match conf {
      Config::User(user) => match user {
        User::Init { name } => create_user_config(name),
        User::Show => show_user_config(),
      },
      Config::Repo(repo) => match repo {
        Repo::Init => create_repo_config(),
        Repo::Show => show_repo_config(),
        Repo::Add(add) => match add {
          Add::Me => add_self_to_maintainers(),
        },
      },
    },
  } {
    eprintln!("{}", e);
    std::process::exit(1);
  }
}

/// Initialize a git repo with all the tools wanted for it
fn init() -> Result<()> {
  // Make sure we're in a valid git repo
  let _ = find_root()?;
  let checkboxes = &["hooked - Managed git hooks", "ticket - In repo tickets"];
  let defaults = &[true, true];
  let selections = Checkboxes::with_theme(&ColorfulTheme::default())
    .with_prompt("Which tools do you want to enable? (defaults to all)")
    .items(&checkboxes[..])
    .defaults(&defaults[..])
    .interact()?
    .into_iter()
    .map(|s| match s {
      0 => Tools::Hooked,
      1 => Tools::Ticket,
      _ => unreachable!(),
    })
    .collect::<Vec<Tools>>();

  if selections.is_empty() {
    println!("Nothing selected. dev-suite not enabled in this repository.");
  } else {
    create_repo_config()?;
    add_self_to_maintainers().map_err(|_| {
      format_err!(
      "It looks like this is your first time using dev-suite. Initialize your \
       config with 'ds config user <name>' then rerun 'ds init'."
    )
    })?;
    for selection in selections {
      match selection {
        Tools::Hooked => {
          which("hooked")
            .map(drop)
            .map_err(|_| format_err!(
              "It looks like hooked is not on your $PATH. Did you run 'ds install'?"
            ))?;

          let langs = &["Python", "Ruby", "Bash"];
          let mut lang_choice =
            langs[Select::with_theme(&ColorfulTheme::default())
              .with_prompt(
                "Which language do you wish to use for your git hooks?",
              )
              .items(langs)
              .interact()?]
            .to_string();
          lang_choice.make_ascii_lowercase();
          let _ = Command::new("hooked")
            .arg("init")
            .arg(&lang_choice)
            .spawn()?
            .wait()?;
        }
        Tools::Ticket => {
          which("ticket")
            .map(drop)
            .map_err(|_| format_err!(
              "It looks like ticket is not on your $PATH. Did you run 'ds install'?"
            ))?;
          let _ = Command::new("ticket").arg("init").spawn()?.wait()?;
        }
      }
    }
  }
  Ok(())
}

/// Tools available for installation or usage by dev-suite
enum Tools {
  Hooked,
  Ticket,
}

/// Install all of dev-suite
fn install() -> Result<()> {
  #[cfg(target_os = "macos")]
  static BASE_URL: &str =
    "https://dev-suite-spaces.nyc3.digitaloceanspaces.com/osx/";
  #[cfg(target_os = "linux")]
  static BASE_URL: &str =
    "https://dev-suite-spaces.nyc3.digitaloceanspaces.com/linux/";
  #[cfg(target_os = "windows")]
  static BASE_URL: &str =
    "https://dev-suite-spaces.nyc3.digitaloceanspaces.com/windows/";

  static TOOLS: [&str; 2] = ["hooked", "ticket"];

  #[cfg(target_os = "macos")]
  let mut location = PathBuf::from("/usr/local/bin");
  #[cfg(target_os = "linux")]
  let mut location = dirs::executable_dir().unwrap();
  #[cfg(target_os = "windows")]
  let mut location = dirs::data_local_dir().unwrap().join("dev-suite");

  let client = reqwest::blocking::Client::new();
  create_dir_all(&location)?;

  for tool in &TOOLS {
    location.push(tool);
    if location.exists() {
      println!("{} already exists, skipping", tool);
      let _ = location.pop();
    } else {
      println!("Installing {}", tool);
      let url = BASE_URL.to_owned() + tool;
      let mut program = client.get(&url).send()?;

      #[cfg(target_family = "unix")]
      let mut file = OpenOptions::new()
        .create(true)
        .write(true)
        .mode(0o755)
        .open(&location)?;
      #[cfg(target_family = "windows")]
      let mut file = {
        let _ = location.set_extension("exe");
        OpenOptions::new()
          .create(true)
          .write(true)
          .open(&location)?
      };
      let _ = program.copy_to(&mut file)?;
      let _ = location.pop();
    }
  }

  // We need to add this to the PATH for the local user
  #[cfg(target_os = "windows")]
  {
    println!("Adding {} to your %PATH%", location.display());
    let mut command = std::ffi::OsStr::new("setx PATH ").to_os_string();
    command.push(location.into_os_string());
    command.push(";%PATH%");
    let _ = Command::new("cmd").arg("/C").arg(command).output()?;
    println!("You'll need to restart your computer for the %PATH% changes to take effect");
  }
  println!("Installation complete");

  Ok(())
}