rust-call 0.2.0

Make remote development more elegant
use anyhow::Result;
use console::Emoji;
use std::collections::HashMap;
use std::fmt;
use yaml_rust::Yaml;

pub static ROOT: &str = "call";
pub static CONFIG: &str = "config";
pub static EXCLUDE: &str = "exclude";
pub static MAPPING: &str = "mapping";
pub static SERVER: &str = "server";
pub static ACTIVE: &str = "active";
pub static RUNNER: &str = "runner";
pub static MODE: &str = "mode";
pub static USERNAME: &str = "username";
pub static HOST: &str = "host";
pub static PORT: &str = "port";
pub static OPENSSH: &str = "openssh";
pub static PASSWORD: &str = "password";
pub static KEYPAIR: &str = "keypair";
pub static AUTHENTICATION_TYPE: &str = "authentication_type";
pub static PRIVATE_KEY_FILE: &str = "private_key_file";
pub static PASS_PHRASE: &str = "pass_phrase";
pub static SRC: &str = "src";
pub static DEST: &str = "dest";


pub static LOOKING_GLASS: Emoji<'_, '_> = Emoji("🔍  ", "");
pub static TRUCK: Emoji<'_, '_> = Emoji("🚚  ", "");
pub static CLIP: Emoji<'_, '_> = Emoji("🔗  ", "");
pub static PAPER: Emoji<'_, '_> = Emoji("📃  ", "");
pub static SPARKLE: Emoji<'_, '_> = Emoji("", ":-)");

pub static INIT_CONFIG: &str = r#"
call:
  config:
    active:
      openssh:
        - dev
      password:
        - stage
      keypair:
        - prod
    runner: make
  mapping:
      src: .
      dest: ~/workspace/call
      exclude:
          - ./target
          - README.md
  server:
        openssh:
          dev:
              host:
                - 127.0.0.1
              port: 22
              authentication_type: openssh
              username: rust
        password:
          stage:
              host:
                - 127.0.0.1
              port: 22
              authentication_type: password
              username: rust
              password: "123456"
        keypair:
          prod:
              host:
                - 127.0.0.1
              port: 22
              authentication_type: keypair
              username: rust
              private_key_file: rust
              pass_phrase: rust
"#;

#[derive(Clone)]
pub struct Openssh {
    pub host: Vec<String>,
    pub port: i64,
    pub authentication_type: String,
    pub username: String,
}

#[derive(Clone)]
pub struct Password {
    pub host: Vec<String>,
    pub port: i64,
    pub authentication_type: String,
    pub username: String,
    pub password: String,
}

#[derive(Clone)]
pub struct Keypair {
    pub host: Vec<String>,
    pub port: i64,
    pub authentication_type: String,
    pub username: String,
    pub private_key_file: String,
    pub pass_phrase: String,
}


#[derive(Clone, Debug)]
pub enum ServerValue {
    Openssh {
        host: Vec<String>,
        port: i64,
        authentication_type: String,
        username: String,
    },
    Password {
        host: Vec<String>,
        port: i64,
        authentication_type: String,
        username: String,
        password: String,
    },
    Keypair {
        host: Vec<String>,
        port: i64,
        authentication_type: String,
        username: String,
        private_key_file: String,
        pass_phrase: String,
    },
}

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum ServerKey {
    Openssh,
    Password,
    Keypair,
}

impl fmt::Display for ServerKey {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let res = match self {
            ServerKey::Openssh => "openssh",
            ServerKey::Password => "password",
            ServerKey::Keypair => "keypair",
        };
        write!(fmt, "{}", res)
    }
}

impl ServerKey {
    fn as_str(&self) -> &'static str {
        match *self {
            ServerKey::Openssh => "openssh",
            ServerKey::Password => "password",
            ServerKey::Keypair => "keypair",
        }
    }
}


#[derive(Clone, Debug)]
pub struct Mapping {
    pub src: String,
    pub dest: String,
    pub exclude: Vec<String>,
}

#[derive(Clone, Debug)]
pub struct CallConfig {
    pub active: HashMap<ServerKey, Vec<ServerValue>>,
    pub runner: String,
    pub mapping: Mapping,

}

impl CallConfig {
    pub fn build(yaml_config: Yaml) -> Result<CallConfig> {
        let mut active_server = HashMap::new();

        let server_yaml = &yaml_config[ROOT][SERVER];
        let mapping_yaml = &yaml_config[ROOT][MAPPING];
        let config_yaml = &yaml_config[ROOT][CONFIG];


        for server_list in config_yaml[ACTIVE][OPENSSH].as_vec() {
            let mut openssh_server_list = Vec::new();

            for server in server_list {
                let server_name = server.as_str().unwrap_or_default();
                if !server_yaml[OPENSSH][server_name].is_badvalue() {
                    let openssh_server = ServerValue::Openssh {
                        host: call_vec!(server_yaml[OPENSSH][server_name][HOST]),
                        port: call_i64!(server_yaml[OPENSSH][server_name][PORT]),
                        authentication_type: call_string!(
                            server_yaml[OPENSSH][server_name][AUTHENTICATION_TYPE]
                        ),
                        username: call_string!(server_yaml[OPENSSH][server_name][USERNAME]),
                    };

                    openssh_server_list.push(openssh_server);
                }
            }
            active_server.insert(ServerKey::Openssh, openssh_server_list);
        }

        for server_list in config_yaml[ACTIVE][PASSWORD].as_vec() {
            let mut password_server_list = Vec::new();
            for server in server_list {
                let server_name = server.as_str().unwrap_or_default();
                if !server_yaml[PASSWORD][server_name].is_badvalue() {
                    let password_server = ServerValue::Password {
                        host: call_vec!(server_yaml[PASSWORD][server_name][HOST]),
                        port: call_i64!(server_yaml[PASSWORD][server_name][PORT]),
                        authentication_type: call_string!(
                            server_yaml[PASSWORD][server_name][AUTHENTICATION_TYPE]
                        ),
                        username: call_string!(server_yaml[PASSWORD][server_name][USERNAME]),
                        password: call_string!(server_yaml[PASSWORD][server_name][PASSWORD]),
                    };

                    password_server_list.push(password_server);
                }
            }
            active_server.insert(ServerKey::Password, password_server_list);
        }

        for server_list in config_yaml[ACTIVE][KEYPAIR].as_vec() {
            let mut keypair_server_list = Vec::new();
            for server in server_list {
                let server_name = server.as_str().unwrap_or_default();
                if !server_yaml[KEYPAIR][server_name].is_badvalue() {
                    let password_server = ServerValue::Keypair {
                        host: call_vec!(server_yaml[KEYPAIR][server_name][HOST]),
                        port: call_i64!(server_yaml[KEYPAIR][server_name][PORT]),
                        authentication_type: call_string!(
                            server_yaml[KEYPAIR][server_name][AUTHENTICATION_TYPE]
                        ),
                        username: call_string!(server_yaml[KEYPAIR][server_name][USERNAME]),
                        private_key_file: call_string!(
                            server_yaml[KEYPAIR][server_name][PRIVATE_KEY_FILE]
                        ),
                        pass_phrase: call_string!(server_yaml[KEYPAIR][server_name][PASS_PHRASE]),
                    };

                    keypair_server_list.push(password_server);
                }
            }
            active_server.insert(ServerKey::Keypair, keypair_server_list);
        }


        Ok(CallConfig {
            active: active_server,
            runner: call_string!(config_yaml[RUNNER]),
            mapping: Mapping {
                src: call_string!(mapping_yaml[SRC]),
                dest: call_string!(mapping_yaml[DEST]),
                exclude: call_vec!(mapping_yaml[EXCLUDE]),
            },
        })
    }
}