sshc 1.0.0

A simple TUI-based connections/tunnels manager for SSH
#[macro_use] extern crate error_chain;
#[macro_use] extern crate serde_derive;
#[macro_use(crate_version, crate_authors)] extern crate clap;
extern crate toml;
extern crate serde;
extern crate shellexpand;
extern crate exec;
extern crate cursive;
extern crate either;
extern crate itertools;

use std::borrow::Cow;
use std::path::{Path, PathBuf};

use clap::{App, Arg, AppSettings};

use config::ConfigItem;
use execution::Execution;

mod config;
mod ui;
mod execution;

fn main() {
    let matches = App::new("sshc")
        .version(crate_version!())
        .author(crate_authors!())
        .about("A simple SSH connections manager.")
        .setting(AppSettings::ColoredHelp)
        .arg(
            Arg::from_usage("-c, --config=[FILE] 'Path to the configuration file'")
                .default_value("~/.config/sshc/config.toml")
        )
        .args_from_usage(
            "-p, --profile=[PROFILE] 'Run the specified profile immediately'
             -d, --dry-run 'Just print the executed command if the profile is specified'"
        )
        .get_matches();

    let config_path = matches.value_of("config").unwrap();
    let config_path: Cow<Path> = str_to_path(config_path);

    let config = match config::load(&config_path) {
        Ok(config) => config,
        Err(e) => {
            println!("Failed to load configuration from {}: {}", config_path.display(), e);
            std::process::exit(1)
        }
    };

    if let Some(profile) = matches.value_of("profile") {
        let parts: Vec<_> = profile.split(".").collect();

        let mut group = config.root;
        let mut definition = None;
        for (i, part) in parts.iter().cloned().enumerate() {
            match group.definitions.remove(part) {
                Some(ConfigItem::Definition(d)) =>
                    if i == parts.len() - 1 {
                        definition = Some(d);
                    } else {
                        break;
                    },
                Some(ConfigItem::Subgroup(g)) =>
                    if i < parts.len() - 1 {
                        group = g;
                    } else {
                        break;
                    },
                _ => {}
            }
        }

        if let Some(definition) = definition {
            let mut e = Execution::from(definition);
            if matches.is_present("dry-run") {
                println!("{}", e.command_line());
            } else {
                e.run();
            }

        } else {
            eprintln!("Invalid profile name: {}", profile);
            std::process::exit(1);
        }

    } else {
        ui::run(config);
    }
}

#[inline]
pub fn str_to_path(s: &str) -> Cow<Path> {
    match shellexpand::tilde(s) {
        Cow::Borrowed(s) => Path::new(s).into(),
        Cow::Owned(s) => PathBuf::from(s).into(),
    }
}

#[inline]
pub fn string_to_path(s: &String) -> Cow<Path> { str_to_path(s) }