dmux 0.6.1

Dmux is a fast and easy tmux workspace opener
#[macro_use]
extern crate serde_derive;
extern crate clap;
extern crate config;
extern crate dirs;
extern crate grep_cli;
extern crate tmux_interface;
extern crate url;
extern crate walkdir;
#[macro_use]
extern crate anyhow;

mod app;
mod select;
mod tmux;

use anyhow::Result;
use app::CommandType;
use colored::*;
use select::Selector;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use tmux::{Layout, WorkSpace};
use url::Url;

fn main() {
    if let Err(err) = do_main() {
        eprintln!("{}: {}", "Error".red(), err);
        err.chain()
            .skip(1)
            .for_each(|cause| eprintln!("because: {}", cause));
        std::process::exit(1);
    }
}

fn do_main() -> Result<()> {
    run_command_from_args(app::build_app()?)
}

fn run_command_from_args(command: CommandType) -> Result<()> {
    if !tmux::has_tmux() {
        return Err(anyhow!("Tmux is not installed."));
    }
    match command {
        CommandType::Open(open_config) => {
            open_selected_dir(open_config)?;
            Ok(())
        }
        CommandType::Select(select_config) => {
            if let Some(dir) = Selector::new(&select_config.workspace.search_dir).select_dir()? {
                open_selected_dir(app::OpenArgs {
                    selected_dir: dir,
                    workspace: select_config.workspace,
                })?;
            };
            Ok(())
        }
        CommandType::Pull(pull_config) => match clone_from(&pull_config) {
            Ok(dir) => {
                open_selected_dir(app::OpenArgs {
                    selected_dir: dir,
                    workspace: pull_config.workspace,
                })?;

                Ok(())
            }
            Err(err) => Err(err),
        },
        CommandType::Layout => {
            if !tmux::in_tmux() {
                return Err(anyhow!("Not inside a tmux session. Run `tmux a` and select the window you want the layout of."));
            };
            tmux::generate_layout()?;
            Ok(())
        }
    }
}

fn open_selected_dir(config: app::OpenArgs) -> Result<()> {
    if !config.selected_dir.exists() {
        return Err(anyhow!("{:?} isn't a valid path", config.selected_dir));
    }
    let layout = Layout {
        layout_string: config.workspace.layout,
        window_count: config.workspace.number_of_panes,
    };
    let workspaces = WorkSpace {
        commands: config.workspace.commands,
        dir: path_to_string(&config.selected_dir)?,
        layout,
        session_name: config.workspace.session_name,
        window_name: path_to_window_name(&config.selected_dir)?,
    };
    tmux::setup_workspace(workspaces)?;
    Ok(())
}

fn git_url_to_dir_name(git_url: &str) -> Result<String> {
    if let Ok(url) = Url::parse(git_url) {
        Ok(url
            .path_segments()
            .ok_or_else(|| anyhow!("cannot be base"))?
            .last()
            .ok_or_else(|| anyhow!("no segments"))?
            .replace(".git", ""))
    } else {
        Ok(git_url
            .split('/')
            .last()
            .ok_or_else(|| anyhow!("I don't know how to parse a dir from {:?}", git_url))?
            .replace(".git", ""))
    }
}

fn clone_from(config: &app::PullArgs) -> Result<PathBuf> {
    let dir_name = git_url_to_dir_name(&config.repo_url)?;
    let target = config.target_dir.join(dir_name);
    let output = Command::new("git")
        .arg("clone")
        .arg(config.repo_url.as_str())
        .arg(
            target
                .to_str()
                .ok_or_else(|| anyhow!("Specified target couldn't be used {:?}", target))?,
        )
        .stdout(Stdio::inherit())
        .output()?;
    if output.status.success() {
        Ok(target)
    } else {
        Err(anyhow!("{}", String::from_utf8(output.stderr)?))
    }
}

fn path_to_string(path: &Path) -> Result<String> {
    Ok(path
        .to_str()
        .ok_or_else(|| anyhow!("Invalid file"))?
        .to_string())
}

fn path_to_window_name(path: &Path) -> Result<String> {
    let file_str = path
        .file_name()
        .ok_or_else(|| anyhow!("No file name found"))?
        .to_str()
        .ok_or_else(|| anyhow!("Invalid file"));

    Ok(String::from(file_str?))
}

#[test]
fn git_url_to_dir_name_test() {
    assert_eq!(
        "dmux".to_string(),
        git_url_to_dir_name("https://github.com/zdcthomas/dmux").unwrap()
    );
    assert_eq!(
        "dmux".to_string(),
        git_url_to_dir_name("git@github.com:zdcthomas/dmux.git").unwrap()
    );
}