workspace 0.4.1

a command-line project manager
#[macro_use]
mod macros;
mod app;
mod exit;
mod shell;
mod tilde;
mod workspace;

use clap::ArgMatches;
use colored::Colorize;
use failure::Fail;

use std::env;
use std::fs;
use std::io::Write;
use std::path;
use std::process;

use crate::exit::Exit;
use crate::tilde::Tilde;
use crate::workspace::Workspace;

pub static mut VERBOSE: bool = false;

fn main() {
    let matches = app::cli().get_matches();

    unsafe {
        VERBOSE = matches.is_present("verbose");
    }

    if !matches.is_present("shell-wrapper") && matches.subcommand_matches("shell").is_none() {
        warn!("You are using the workspace binary, which is the backend for the `ws` function.");
        indent_warn!(
            "To set `ws` up in your shell, see the README.md or run `workspace shell --help`"
        )
    }

    match matches.subcommand() {
        ("open", Some(matches)) => {
            let name: &str = matches.value_of("NAME").unwrap();
            let ws = Workspace::get(name)
                .unwrap_or_exit(&format!("A workspace called '{}' does not exist", name))
                .unwrap_or_else(|error| {
                    let path = Workspace::file_path(name);
                    error!("{} from {}", error, path.tilde_format());
                    if let Some(cause) = error.cause() {
                        indent_error!("{}", cause);
                    }
                    if let Some(backtrace) = error.backtrace() {
                        log!("{}", backtrace);
                    }
                    process::exit(1)
                });
            if !ws.path.exists() {
                error!("The location of this workspace does not exist anymore");
                indent_error!("the path '{}' was moved or deleted", ws.path.tilde_format());
                process::exit(1);
            }
            let dir_only = matches.is_present("directory");
            ws.open(dir_only);
        }

        ("add", Some(matches)) => {
            let name = matches.value_of("NAME").unwrap().to_string();
            if Workspace::exists(&name) {
                error!("A workspace called '{}' already exists", name);
                process::exit(1);
            }
            let path = env::current_dir().unwrap_or_exit("Could not read current directory");

            // Check for other workspaces with the same path
            let sames: Vec<_> = Workspace::all()
                .into_iter()
                .filter_map(|(name, result)| {
                    if let (Some(name), Ok(workspace)) = (name, result) {
                        if workspace.path == path {
                            return Some(name);
                        }
                    }
                    None
                })
                .collect();

            if !sames.is_empty() {
                warn!(
                    "Found {} pointing to this directory: {}",
                    if sames.len() == 1 {
                        "another workspace"
                    } else {
                        "other workspaces"
                    },
                    sames.join(", ")
                );
                confirm!("Create a new workspace here anyway");
            }

            let ws = Workspace {
                path,
                commands: workspace::Commands::default(),
                tabs: Vec::default(),
            };
            ws.write(&name);
            Workspace::edit(&name);
            println!("Created workspace '{}' in {}", name, ws.path.tilde_format());
        }

        ("edit", Some(matches)) => {
            let name = matches.value_of("NAME").unwrap();
            if !Workspace::exists(&name) {
                error!("A workspace called '{}' does not exist", name);
                process::exit(1);
            }
            Workspace::edit(name);
        }

        ("rename", Some(matches)) => {
            let old_name = matches.value_of("OLD_NAME").unwrap();
            let new_name = matches.value_of("NEW_NAME").unwrap();
            if !Workspace::exists(&old_name) {
                error!("A workspace called '{}' does not exist", old_name);
                process::exit(1);
            }
            if Workspace::exists(&new_name) {
                error!(
                    "Cannot rename to '{}' because a workspace with that name already exists",
                    new_name
                );
                process::exit(1)
            }
            std::fs::rename(
                Workspace::file_path(old_name),
                Workspace::file_path(new_name),
            )
            .unwrap_or_exit("Could not rename config file");
        }

        ("delete", Some(matches)) => {
            let name: &str = matches.value_of("NAME").unwrap();
            if !Workspace::file_path(name).exists() {
                error!("A workspace called '{}' does not exist", name);
                process::exit(1);
            }

            if !matches.is_present("yes") {
                confirm!("Delete the workspace '{}'", name);
            }

            Workspace::delete(name);
            println!("Deleted workspace '{}'", name);
        }

        ("list", Some(_)) => {
            let all = Workspace::all();
            if all.is_empty() {
                eprintln!("No workspaces found.\nRun `ws add <NAME>` to create one.");
                return;
            }

            use term_grid::{Direction, Filling, Grid, GridOptions};
            let mut grid = Grid::new(GridOptions {
                filling: Filling::Spaces(2),
                direction: Direction::LeftToRight,
            });

            for (name, result) in all {
                let path: String;
                let mut moved = String::new();
                match result {
                    Ok(ws) => {
                        path = ws.path.tilde_format().bright_black().to_string();
                        if !ws.path.exists() {
                            moved = format!("{} path has moved", "warning:".bold().yellow());
                        }
                    }
                    Err(error) => {
                        path = format!("{} {}", "warning:".bold().yellow(), error);
                    }
                }
                let name =
                    name.unwrap_or_else(|| format!("{} invalid UTF-8", "warning:".bold().yellow()));

                grid.add(name.into());
                grid.add(path.into());
                grid.add(moved.into());
            }
            print!("{}", grid.fit_into_columns(3));
        }

        ("shell", Some(matches)) => {
            if matches.subcommand_matches("bash").is_some() {
                println!("{}", shell::BASH);
            } else if matches.subcommand_matches("fish").is_some() {
                println!("{}", shell::FISH);
            } else if matches.subcommand_matches("powershell").is_some() {
                println!("{}", shell::POWERSHELL)
            } else if let Some(matches) = matches.subcommand_matches("cmd") {
                let path: path::PathBuf = path_to_binary_or_arg(&matches);
                let mut file: fs::File = fs::OpenOptions::new()
                    .read(false)
                    .write(true)
                    .create(true)
                    .append(false)
                    .truncate(true)
                    .open(&path)
                    .unwrap_or_exit(&format!(
                        "Could not create batch file at {}",
                        path.tilde_format()
                    ));

                file.write_fmt(format_args!("{}", shell::CMD))
                    .unwrap_or_exit("Could not write to batch file");

                println!("Wrote {}", path.tilde_format());
            }
        }

        _ => {}
    }
}

fn path_to_binary_or_arg(matches: &ArgMatches) -> path::PathBuf {
    if let Some(path) = matches.value_of("PATH") {
        return path::Path::new(path)
            .with_file_name("ws")
            .with_extension("bat")
            .to_path_buf();
    } else {
        let mut path = env::current_exe().unwrap_or_exit("Could not determine path to binary");
        path.set_file_name("ws");
        path.set_extension("bat");
        return path;
    }
}