From a2e903472c64b6024d5739459431614049aafe8b Mon Sep 17 00:00:00 2001 From: rajdip-b Date: Fri, 21 Jun 2024 23:00:20 +0530 Subject: [PATCH 1/4] refactor command arrangement --- src/command.rs | 9 ++ src/command/configure.rs | 90 ++++++++++++++++ src/command/run.rs | 29 ++++++ src/commands/add.rs | 89 ---------------- src/commands/configure.rs | 78 -------------- src/commands/mod.rs | 123 ---------------------- src/commands/run_command_with_env.rs | 51 --------- src/macros/command_line_macros.rs | 4 - src/macros/generate_toml_macros.rs | 148 --------------------------- src/macros/mod.rs | 2 - src/main.rs | 89 ++++++++++++++-- src/util.rs | 19 ++++ 12 files changed, 226 insertions(+), 505 deletions(-) create mode 100644 src/command.rs create mode 100644 src/command/configure.rs create mode 100644 src/command/run.rs delete mode 100644 src/commands/add.rs delete mode 100644 src/commands/configure.rs delete mode 100644 src/commands/mod.rs delete mode 100644 src/commands/run_command_with_env.rs delete mode 100644 src/macros/command_line_macros.rs delete mode 100644 src/macros/generate_toml_macros.rs delete mode 100644 src/macros/mod.rs create mode 100644 src/util.rs diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..5724771 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,9 @@ +use std::io; + +pub mod configure; +pub mod run; + +pub trait AbstractCommandInterface { + fn parse_args(&mut self) -> Result<(), io::Error>; + fn execute(&self) -> Result<(), io::Error>; +} diff --git a/src/command/configure.rs b/src/command/configure.rs new file mode 100644 index 0000000..d37a0c3 --- /dev/null +++ b/src/command/configure.rs @@ -0,0 +1,90 @@ +use clap::ArgMatches; +use std::io; + +use crate::util::{read_from_terminal, read_securely_from_terminal}; + +use super::AbstractCommandInterface; + +#[derive(Debug)] +struct ConfigureCommandParsedData { + workspace: String, + project: String, + environment: String, + api_key: String, + private_key: String, +} + +pub struct ConfigureCommand<'a> { + parsed_data: ConfigureCommandParsedData, + args: &'a ArgMatches, +} + +impl<'a> ConfigureCommand<'a> { + pub fn new(args: &'a ArgMatches) -> ConfigureCommand<'a> { + ConfigureCommand { + parsed_data: ConfigureCommandParsedData { + workspace: String::new(), + project: String::new(), + environment: String::new(), + api_key: String::new(), + private_key: String::new(), + }, + args, + } + } +} + +impl<'a> AbstractCommandInterface for ConfigureCommand<'a> { + fn parse_args(&mut self) -> Result<(), io::Error> { + let args = self.args; + + let workspace = if let Some(w) = args.get_one::("WORKSPACE") { + w.to_string() + } else { + read_from_terminal("Enter the workspace name: ")? + }; + + // Read project name + let project = if let Some(p) = args.get_one::("PROJECT") { + p.to_string() + } else { + read_from_terminal("Enter the project name: ")? + }; + + // Read environment name + let environment = if let Some(e) = args.get_one::("ENVIRONMENT") { + e.to_string() + } else { + read_from_terminal("Enter the environment name: ")? + }; + + // Read API Key + let api_key = if let Some(a) = args.get_one::("API_KEY") { + a.to_string() + } else { + read_securely_from_terminal("Enter your API Key:")? + }; + + // Read Private Key + let private_key = if let Some(p) = args.get_one::("PRIVATE_KEY") { + p.to_string() + } else { + read_securely_from_terminal("Enter your Private Key:")? + }; + + self.parsed_data = ConfigureCommandParsedData { + workspace, + project, + environment, + api_key, + private_key, + }; + + Ok(()) + } + + fn execute(&self) -> Result<(), io::Error> { + println!("{:?}\n", self.parsed_data); + Ok(()) + } +} diff --git a/src/command/run.rs b/src/command/run.rs new file mode 100644 index 0000000..8a8ce3d --- /dev/null +++ b/src/command/run.rs @@ -0,0 +1,29 @@ +use std::io; + +use super::AbstractCommandInterface; + +struct RunCommandParsedData {} + +pub struct RunCommand<'a> { + parsed_data: RunCommandParsedData, + args: &'a clap::ArgMatches, +} + +impl RunCommand<'_> { + pub fn new(args: &clap::ArgMatches) -> RunCommand { + RunCommand { + parsed_data: RunCommandParsedData {}, + args, + } + } +} + +impl<'a> AbstractCommandInterface for RunCommand<'a> { + fn parse_args(&mut self) -> Result<(), io::Error> { + Ok(()) + } + + fn execute(&self) -> Result<(), io::Error> { + Ok(()) + } +} diff --git a/src/commands/add.rs b/src/commands/add.rs deleted file mode 100644 index 28ffa0e..0000000 --- a/src/commands/add.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::{ - fs::{self, OpenOptions}, - path::Path, -}; - -use colored::Colorize; -use directories::UserDirs; -use inquire::Password; -use spinners::{Spinner, Spinners}; -use std::io::Write; - -use crate::{constants::CONFIG_FILE_NAME, generate_workspace_toml}; -/// Adds a new workspace and project to the keyshades-cli configuration file. -/// -/// # Arguments -/// -/// * `wrkspc` - A reference to a `String` representing the workspace name. -/// * `prjct` - An optional reference to a `String` representing the project name. -/// -/// # Example -/// -/// ``` -/// let workspace = "my_workspace".to_string(); -/// let project = Some("my_project".to_string()); -/// add(&workspace, project.as_ref()); -/// ``` -pub fn add(wrkspc: &String, prjct: Option<&String>) { - let mut api_key_input: String = String::new(); - let mut private_key_input: String = String::new(); - - if let Some(user_dirs) = UserDirs::new() { - let config_dir: &Path = user_dirs.home_dir(); - let config_file_path = config_dir.join(CONFIG_FILE_NAME); - match fs::read_to_string(config_file_path.clone()) { - Ok(_config_file) => { - let mut file = OpenOptions::new() - .write(true) - .append(true) - .open(config_file_path.clone()) - .unwrap(); - if prjct.is_some() { - api_key_input = Password::new("Enter your API Key:") - .without_confirmation() - .prompt() - .unwrap(); - private_key_input = Password::new("Enter your Private Key:") - .without_confirmation() - .prompt() - .unwrap(); - } - let mut sp = Spinner::new(Spinners::Dots9, "Creating config file...".into()); - if let Some(project) = prjct { - let config_str: String = generate_workspace_toml!( - wrkspc, - Some(project.to_string()), - api_key_input, - private_key_input - ); - // fs::write(config_dir.join(CONFIG_FILE_NAME), config_str).unwrap(); - if let Err(e) = writeln!(file, "{}", config_str) { - eprintln!("Couldn't write to file: {}", e); - } - } else { - let config_str: String = generate_workspace_toml!(wrkspc, None, "", ""); - // fs::write(config_dir.join(CONFIG_FILE_NAME), config_str).unwrap(); - if let Err(e) = writeln!(file, "{}", config_str) { - eprintln!("Couldn't write to file: {}", e); - } - } - - sp.stop(); - println!("\n{}", "Config file created 🎉".bright_green()); - } - Err(_e) => { - println!("{}", "Config file does not exist".bright_yellow().bold()); - println!( - "{}", - "Please run the configure command first".bright_yellow() - ); - println!("{}", "Usage: ks configure -h".bright_yellow()); - } - } - } else { - eprintln!( - "{}", - "Error: Could not find the user's home directory".bright_red() - ); - } -} diff --git a/src/commands/configure.rs b/src/commands/configure.rs deleted file mode 100644 index f09e653..0000000 --- a/src/commands/configure.rs +++ /dev/null @@ -1,78 +0,0 @@ -use colored::Colorize; -use inquire::Password; -use spinners::{Spinner, Spinners}; -use std::{fs, path::Path}; - -use directories::UserDirs; - -use crate::{constants::CONFIG_FILE_NAME, generate_config_toml}; - -/// Configures the keyshades-cli by creating a configuration file in the user's home directory. -/// -/// # Arguments -/// -/// * `wrkspc` - A reference to a `String` representing the workspace name. -/// * `prjct` - An optional reference to a `String` representing the project name. -/// -/// # Example -/// -/// ``` -/// let workspace = "my_workspace".to_string(); -/// let project = Some("my_project".to_string()); -/// configure(&workspace, project.as_ref()); -/// ``` -pub fn configure(wrkspc: &String, prjct: Option<&String>) { - let mut api_key_input: String = String::new(); - let mut private_key_input: String = String::new(); - - println!("\n{}\n", "* Configuring the keyshades-cli".on_cyan().bold()); - - if let Some(user_dirs) = UserDirs::new() { - let config_dir: &Path = user_dirs.home_dir(); - // Linux: /home/JohnDoe - // Windows: C:\Users\JohnDoe - // macOS: /Users/JohnDoe - - match fs::read_to_string(config_dir.join(CONFIG_FILE_NAME)) { - Ok(_config_file) => { - println!("{}", "Config file exists 🙌".bright_green()); - } - Err(_e) => { - // add a new workspace and project if the file does not exist - - if prjct.is_some() { - api_key_input = Password::new("Enter your API Key:") - .without_confirmation() - .prompt() - .unwrap(); - private_key_input = Password::new("Enter your Private Key:") - .without_confirmation() - .prompt() - .unwrap(); - } - - let mut sp = Spinner::new(Spinners::Dots9, "Creating config file...".into()); - if let Some(project) = prjct { - let config_str: String = generate_config_toml!( - wrkspc, - Some(project.to_string()), - api_key_input, - private_key_input - ); - fs::write(config_dir.join(CONFIG_FILE_NAME), config_str).unwrap(); - } else { - let config_str: String = generate_config_toml!(wrkspc, None, "", ""); - fs::write(config_dir.join(CONFIG_FILE_NAME), config_str).unwrap(); - } - - sp.stop(); - println!("\n{}", "Config file created 🎉".bright_green()); - } - } - } else { - eprintln!( - "{}", - "Error: Could not find the user's home directory".bright_red() - ); - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs deleted file mode 100644 index 25ac6c7..0000000 --- a/src/commands/mod.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::{ - commands::add::add, - commands::configure::configure, - constants::{ABOUT, VERSION}, -}; -mod add; -mod configure; -mod run_command_with_env; -use clap::{arg, Arg, ArgAction, ArgMatches, Command}; - -use self::run_command_with_env::run; - -/// Constructs the command line interface for the keyshades CLI. -pub fn cli() -> Command { - Command::new("keyshades-cli") - .alias("ks") - .version(VERSION) - .about(ABOUT) - .max_term_width(100) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand( - Command::new("configure") - .alias("conf") - .about("Configure the keyshades CLI, alias to `conf`") - .arg( - Arg::new("WORKSPACE") - .long("workspace") - .short('w') - .action(ArgAction::Set) - .help("Configure the workspace") - .required(true), - ) - .arg( - Arg::new("PROJECT") - .long("project") - .short('p') - .action(ArgAction::Set) - .help("Configure the project"), - ), - ) - .subcommand( - Command::new("add") - .about("Add a new project to the workspace or a new workspace") - .arg( - Arg::new("WORKSPACE") - .long("workspace") - .short('w') - .action(ArgAction::Set) - .help("Add the workspace") - .required(true), - ) - .arg( - Arg::new("PROJECT") - .long("project") - .short('p') - .action(ArgAction::Set) - .help("Add the project, to add a project you must specify the workspace"), - ), - ) - .subcommand( - Command::new("remove") - .alias("rm") - .about("Remove project(s) or workspace(s), alias to `rm`") - .arg(arg!(-w --workspace "Configure the workspace ").required(true)) - .arg(arg!(-p --project "Configure the project")), - ) - .subcommand( - Command::new("list") - .alias("li") - .about("List project(s) or workspace(s), alias to `li`") - .arg(arg!(-w --workspace "list all workspace").required(true)) - .arg(arg!(-p --project "list all projects")), - ) - .subcommand( - Command::new("run") - .alias("r") - .about("run a command, alias to `r`") - .arg( - Arg::new("COMMAND") - .help("The command to run") - .value_name("COMMAND") - .allow_hyphen_values(true) - .last(true) - .num_args(1..) - .action(ArgAction::Set) - .required(true), - ), - ) -} - -pub fn execution() { - let matches: ArgMatches = cli().get_matches(); - match matches.subcommand() { - Some(("configure", sub_m)) => { - let workspace: &String = sub_m.get_one::("WORKSPACE").unwrap(); - let project: Option<&String> = sub_m.get_one::("PROJECT"); - configure(workspace, project); - } - Some(("add", sub_m)) => { - let workspace: &String = sub_m.get_one::("WORKSPACE").unwrap(); - let project: Option<&String> = sub_m.get_one::("PROJECT"); - add(workspace, project); - } - Some(("remove", sub_m)) => { - dbg!(sub_m); - } - Some(("list", sub_m)) => { - dbg!(sub_m); - } - Some(("run", sub_m)) => { - run(sub_m); - } - _ => { - println!("No subcommand was used"); - } - } -} - -/// Executes the command line application. -pub fn main() { - execution(); -} diff --git a/src/commands/run_command_with_env.rs b/src/commands/run_command_with_env.rs deleted file mode 100644 index ae99bdb..0000000 --- a/src/commands/run_command_with_env.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::{collections::HashMap, process::Command}; - -use clap::ArgMatches; - -/// Runs a command with environment variables. -/// -/// # Arguments -/// -/// * `sub_m` - A reference to a `ArgMatches` struct. -/// -/// # Remarks -/// -/// The `run` function takes a reference to a `ArgMatches` struct as an argument. The `ArgMatches` struct is used to parse the command line arguments. The `run` function then creates a `HashMap` of environment variables and inserts some key-value pairs. The `run` function then extracts the command from the `ArgMatches` struct and executes it using the `Command` struct from the `std::process` module. The output of the command is then printed to the console. -/// -/// # Example of command: -/// ```bash -/// keyshades-cli run -- node index.js -/// `````` -pub fn run(sub_m: &ArgMatches) { - let mut env_vars: HashMap = HashMap::new(); // ! this will be replaced with the env vars from the backend - env_vars.insert("NAME".to_string(), "Sawan".to_string()); - env_vars.insert("AGE".to_string(), "21".to_string()); - env_vars.insert("COUNTRY".to_string(), "India".to_string()); - - let command: Vec<&str> = sub_m - .get_many::("COMMAND") - .unwrap_or_default() - .map(|v| v.as_str()) - .collect::>(); - if let Some((program, args)) = command.split_first() { - let output = Command::new(program).args(args).envs(env_vars).output(); - - match output { - Ok(output) => { - // Print the output - if !output.stdout.is_empty() { - println!("{}", String::from_utf8_lossy(&output.stdout)); - } - // Print the error - if !output.stderr.is_empty() { - eprintln!("{}", String::from_utf8_lossy(&output.stderr)); - } - } - Err(e) => { - eprintln!("Failed to execute command: {}", e); - } - } - } else { - println!("No command provided"); - } -} diff --git a/src/macros/command_line_macros.rs b/src/macros/command_line_macros.rs deleted file mode 100644 index b70b006..0000000 --- a/src/macros/command_line_macros.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[macro_export] -macro_rules! create_arg { - () => {} -} \ No newline at end of file diff --git a/src/macros/generate_toml_macros.rs b/src/macros/generate_toml_macros.rs deleted file mode 100644 index 781ccc9..0000000 --- a/src/macros/generate_toml_macros.rs +++ /dev/null @@ -1,148 +0,0 @@ -#[macro_export] -/// Macro to generate a TOML string representation of a project. -/// -/// # Arguments -/// -/// * `$project` - The project name. -/// * `$api_key` - The API key for the project. -/// * `$private_key` - The private key for the project. -/// -/// # Returns -/// -/// A string representation of the project in TOML format. -/// -/// # Example -/// -/// ```rust -/// let project_toml: String = generate_project_toml!("MyProject", "api_key", "private_key"); -/// println!("{}", project_toml); -/// ``` -macro_rules! generate_project_toml { - ($project:expr, $api_key:expr, $private_key:expr) => {{ - let mut project_map = ::std::collections::HashMap::new(); - project_map.insert( - $project.to_string(), - $crate::models::toml_model::Project { - api_key: $api_key.to_string(), - private_key: $private_key.to_string(), - }, - ); - ::toml::to_string_pretty(&project_map).unwrap() - }}; -} - -#[macro_export] -/// Generates a TOML string representation of a workspace with the given parameters. -/// -/// # Arguments -/// -/// * `$wrkspc` - The workspace name. -/// * `$prjct` - An optional project name. -/// * `$api_key_input` - The API key input. -/// * `$private_key_input` - The private key input. -/// -/// # Returns -/// -/// A TOML string representation of the workspace. -/// -/// # Example -/// -/// ## Workspace with a project -/// -/// ```rust -/// let workspace_toml: String = generate_workspace_toml!("MyWorkspace", Some("MyProject").to_string(), "api_key", "private_key"); -/// println!("{}", workspace_toml); -/// ``` -/// -/// ## Workspace without a project -/// -/// ```rust -/// let workspace_toml: String = generate_workspace_toml!("MyWorkspace", None, "", ""); -/// println!("{}", workspace_toml); -/// ``` -macro_rules! generate_workspace_toml { - ($wrkspc:expr, $prjct:expr, $api_key_input:expr, $private_key_input:expr) => {{ - let mut workspace_map = ::std::collections::HashMap::new(); - workspace_map.insert( - $wrkspc.to_string(), - $crate::models::toml_model::Workspace { - projects: match $prjct { - Some(prjct) => { - let mut project_map = ::std::collections::HashMap::new(); - project_map.insert( - prjct, - $crate::models::toml_model::Project { - api_key: $api_key_input.to_string(), - private_key: $private_key_input.to_string(), - }, - ); - Some(project_map) - } - None => None, - }, - }, - ); - ::toml::to_string_pretty(&workspace_map).unwrap() - }}; -} - -#[macro_export] -/// Generates a TOML string representation of a configuration with the given parameters. -/// -/// # Arguments -/// -/// * `$wrkspc` - The workspace name. -/// * `$prjct` - An optional project name. -/// * `$api_key_input` - The API key input. -/// * `$private_key_input` - The private key input. -/// -/// # Returns -/// -/// A TOML string representation of the configuration. -/// -/// # Example -/// -/// ## Configuration with a project -/// -/// ```rust -/// let configuration_toml: String = generate_config_toml!("MyWorkspace", Some("MyProject").to_string(), "api_key", "private_key"); -/// println!("{}", configuration_toml); -/// ``` -/// -/// ## Configuration without a project -/// -/// ```rust -/// let configuration_toml: String = generate_config_toml!("MyWorkspace", None, "", ""); -/// println!("{}", configuration_toml); -/// ``` -macro_rules! generate_config_toml { - ($wrkspc:expr, $prjct:expr, $api_key_input:expr, $private_key_input:expr) => {{ - let mut workspace_map = ::std::collections::HashMap::new(); - workspace_map.insert( - $wrkspc.to_string(), - $crate::models::toml_model::Workspace { - projects: match $prjct { - Some(prjct) => { - let mut project_map = ::std::collections::HashMap::new(); - project_map.insert( - prjct, - $crate::models::toml_model::Project { - api_key: $api_key_input.to_string(), - private_key: $private_key_input.to_string(), - }, - ); - Some(project_map) - } - None => None, - }, - }, - ); - - let config = $crate::models::toml_model::Configure { - base_url: $crate::constants::BASE_URL.to_string(), - workspaces: workspace_map, - }; - - ::toml::to_string_pretty(&config).unwrap() - }}; -} diff --git a/src/macros/mod.rs b/src/macros/mod.rs deleted file mode 100644 index e7a8824..0000000 --- a/src/macros/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod command_line_macros; -pub mod generate_toml_macros; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index be0af14..4373c9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,85 @@ -mod commands; +mod command; mod constants; -mod macros; mod models; +mod util; + +use crate::constants::{ABOUT, VERSION}; +use clap::{Arg, ArgAction, ArgMatches, Command}; + +use command::{configure::ConfigureCommand, run::RunCommand, AbstractCommandInterface}; + +fn cli() -> Command { + Command::new("keyshades-cli") + .alias("ks") + .version(VERSION) + .about(ABOUT) + .max_term_width(100) + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("configure") + .alias("conf") + .about("Configure the keyshades CLI, alias to `conf`") + .arg( + Arg::new("WORKSPACE") + .long("workspace") + .short('w') + .action(ArgAction::Set) + .help("Configure the workspace"), + ) + .arg( + Arg::new("PROJECT") + .long("project") + .short('p') + .action(ArgAction::Set) + .help("Configure the project"), + ) + .arg( + Arg::new("ENVIRONMENT") + .long("environment") + .short('e') + .action(ArgAction::Set) + .help("Configure the environment"), + ) + .arg( + Arg::new("API_KEY") + .long("api-key") + .short('a') + .action(ArgAction::Set) + .help("Configure the API key"), + ) + .arg( + Arg::new("PRIVATE_KEY") + .long("private-key") + .short('k') + .action(ArgAction::Set) + .help("Configure the private key"), + ), + ) +} + fn main() { - commands::main(); - // let workspace_str = - // generate_workspace_toml!("my_workspace", None, "", ""); - // println!("{}", workspace_str); + let matches: ArgMatches = cli().get_matches(); + let mut command: Option> = None; - // let project_str = generate_project_toml!("my_project", "api_key", "private_key"); - // println!("{}", project_str); + // Get the subcommand implementation based on the user input + match matches.subcommand() { + Some(("configure", args)) => { + command = Some(Box::new(ConfigureCommand::new(args))); + } + Some(("run", args)) => { + command = Some(Box::new(RunCommand::new(args))); + } + _ => { + println!("Error: No subcommand provided. Usage: ks [SUBCOMMAND] [OPTIONS]"); + } + } - // let config_str: String = generate_config_toml!("my_workspace", None, "", ""); - // println!("{}", config_str); + // Execute the subcommand + if let Some(mut c) = command { + c.parse_args().unwrap(); + c.execute().unwrap(); + } else { + panic!("Error: No subcommand provided. Usage: ks [SUBCOMMAND] [OPTIONS]"); + } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..429d138 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,19 @@ +use std::io::{self, Write}; + +use inquire::Password; + +pub fn read_from_terminal(prompt: &str) -> Result { + let mut input = String::new(); + print!("{}", prompt); + io::stdout().flush()?; + io::stdin().read_line(&mut input)?; + Ok(input.trim().to_string()) +} + +pub fn read_securely_from_terminal(prompt: &str) -> Result { + let input = Password::new("Enter your API Key:") + .without_confirmation() + .prompt() + .unwrap(); + Ok(input.trim().to_string()) +} From 98e6d5061874299fef578df541b70ff3d8c91fa0 Mon Sep 17 00:00:00 2001 From: rajdip-b Date: Sat, 22 Jun 2024 12:44:56 +0530 Subject: [PATCH 2/4] finished configure command --- .gitignore | 2 + Cargo.toml | 1 + src/command/configure.rs | 90 --------------- src/commands/configure.rs | 171 ++++++++++++++++++++++++++++ src/{command.rs => commands/mod.rs} | 0 src/{command => commands}/run.rs | 0 src/constants.rs | 6 +- src/macros/command_line.rs | 53 +++++++++ src/macros/file_io.rs | 32 ++++++ src/macros/mod.rs | 3 + src/macros/toml.rs | 41 +++++++ src/main.rs | 6 +- src/models/mod.rs | 2 +- src/models/toml_model.rs | 18 +-- src/util.rs | 19 ---- 15 files changed, 315 insertions(+), 129 deletions(-) delete mode 100644 src/command/configure.rs create mode 100644 src/commands/configure.rs rename src/{command.rs => commands/mod.rs} (100%) rename src/{command => commands}/run.rs (100%) create mode 100644 src/macros/command_line.rs create mode 100644 src/macros/file_io.rs create mode 100644 src/macros/mod.rs create mode 100644 src/macros/toml.rs delete mode 100644 src/util.rs diff --git a/.gitignore b/.gitignore index 4022e20..d71e71b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ Cargo.lock # Added by cargo /target + +keyshade.toml diff --git a/Cargo.toml b/Cargo.toml index bc55384..3fddb10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ inquire = "0.7.4" serde = {version = "1.0.197", features = ["derive"] } spinners = "4.1.1" toml = "0.8.12" +whoami = "1.5.1" # We optimize the release build for size. (https://doc.rust-lang.org/cargo/reference/profiles.html) [profile.release] diff --git a/src/command/configure.rs b/src/command/configure.rs deleted file mode 100644 index d37a0c3..0000000 --- a/src/command/configure.rs +++ /dev/null @@ -1,90 +0,0 @@ -use clap::ArgMatches; -use std::io; - -use crate::util::{read_from_terminal, read_securely_from_terminal}; - -use super::AbstractCommandInterface; - -#[derive(Debug)] -struct ConfigureCommandParsedData { - workspace: String, - project: String, - environment: String, - api_key: String, - private_key: String, -} - -pub struct ConfigureCommand<'a> { - parsed_data: ConfigureCommandParsedData, - args: &'a ArgMatches, -} - -impl<'a> ConfigureCommand<'a> { - pub fn new(args: &'a ArgMatches) -> ConfigureCommand<'a> { - ConfigureCommand { - parsed_data: ConfigureCommandParsedData { - workspace: String::new(), - project: String::new(), - environment: String::new(), - api_key: String::new(), - private_key: String::new(), - }, - args, - } - } -} - -impl<'a> AbstractCommandInterface for ConfigureCommand<'a> { - fn parse_args(&mut self) -> Result<(), io::Error> { - let args = self.args; - - let workspace = if let Some(w) = args.get_one::("WORKSPACE") { - w.to_string() - } else { - read_from_terminal("Enter the workspace name: ")? - }; - - // Read project name - let project = if let Some(p) = args.get_one::("PROJECT") { - p.to_string() - } else { - read_from_terminal("Enter the project name: ")? - }; - - // Read environment name - let environment = if let Some(e) = args.get_one::("ENVIRONMENT") { - e.to_string() - } else { - read_from_terminal("Enter the environment name: ")? - }; - - // Read API Key - let api_key = if let Some(a) = args.get_one::("API_KEY") { - a.to_string() - } else { - read_securely_from_terminal("Enter your API Key:")? - }; - - // Read Private Key - let private_key = if let Some(p) = args.get_one::("PRIVATE_KEY") { - p.to_string() - } else { - read_securely_from_terminal("Enter your Private Key:")? - }; - - self.parsed_data = ConfigureCommandParsedData { - workspace, - project, - environment, - api_key, - private_key, - }; - - Ok(()) - } - - fn execute(&self) -> Result<(), io::Error> { - println!("{:?}\n", self.parsed_data); - Ok(()) - } -} diff --git a/src/commands/configure.rs b/src/commands/configure.rs new file mode 100644 index 0000000..2f78da8 --- /dev/null +++ b/src/commands/configure.rs @@ -0,0 +1,171 @@ +use clap::ArgMatches; +use std::io; +use std::io::Write; + +use crate::{ + file_exists, generate_project_toml, generate_user_root_toml, + get_os_specific_user_root_config_path, read_from_terminal, read_securely_from_terminal, + write_file, +}; + +use super::AbstractCommandInterface; + +#[derive(Debug)] +struct ConfigureCommandParsedData { + workspace: String, + project: String, + environment: String, + api_key: String, + private_key: String, +} + +pub struct ConfigureCommand<'a> { + parsed_data: ConfigureCommandParsedData, + args: &'a ArgMatches, +} + +impl<'a> ConfigureCommand<'a> { + pub fn new(args: &'a ArgMatches) -> ConfigureCommand<'a> { + ConfigureCommand { + parsed_data: ConfigureCommandParsedData { + workspace: String::new(), + project: String::new(), + environment: String::new(), + api_key: String::new(), + private_key: String::new(), + }, + args, + } + } + + fn create_keyshade_toml(&self) -> Result<(), io::Error> { + println!("Creating keyshade.toml..."); + + // Get the parsed toml content + let toml_content = generate_project_toml!( + self.parsed_data.workspace, + self.parsed_data.project, + self.parsed_data.environment + ); + + // Write the toml content to the file + write_file!("keyshade.toml", toml_content); + + println!("keyshade.toml created successfully!"); + + Ok(()) + } + + fn create_user_root_toml(&self) -> Result<(), io::Error> { + println!("Creating user root toml..."); + + // Get the user root toml path + let user_root_toml_path = get_os_specific_user_root_config_path!(self.parsed_data.project); + + // Get the parsed toml content + let toml_content = generate_user_root_toml!( + self.parsed_data.api_key, + self.parsed_data.private_key, + self.parsed_data.project + ); + + // Write the toml content to the file + write_file!(&user_root_toml_path, toml_content); + + println!("User root toml created successfully!"); + + Ok(()) + } +} + +impl<'a> AbstractCommandInterface for ConfigureCommand<'a> { + fn parse_args(&mut self) -> Result<(), io::Error> { + let args = self.args; + + let workspace = if let Some(w) = args.get_one::("WORKSPACE") { + w.to_string() + } else { + read_from_terminal!("Enter the workspace name: ") + }; + + // Read project name + let project = if let Some(p) = args.get_one::("PROJECT") { + p.to_string() + } else { + read_from_terminal!("Enter the project name:") + }; + + // Read environment name + let environment = if let Some(e) = args.get_one::("ENVIRONMENT") { + e.to_string() + } else { + read_from_terminal!("Enter the environment name: ") + }; + + // Read API Key + let api_key = if let Some(a) = args.get_one::("API_KEY") { + a.to_string() + } else { + read_securely_from_terminal!("Enter your API Key:") + }; + + // Read Private Key + let private_key = if let Some(p) = args.get_one::("PRIVATE_KEY") { + p.to_string() + } else { + read_securely_from_terminal!("Enter your Private Key:") + }; + + self.parsed_data = ConfigureCommandParsedData { + workspace, + project, + environment, + api_key, + private_key, + }; + + Ok(()) + } + + fn execute(&self) -> Result<(), io::Error> { + let mut should_upsert_keyshade_toml = true; + let mut should_upsert_user_root_toml = true; + + // Check if keyshade.toml exists in the current directory + if file_exists!("keyshade.toml") { + // If it does, ask if the users want to overwrite it + let choice = read_from_terminal!( + "keyshade.toml already exists. Do you want to overwrite it? (y/n): " + ); + + if choice.to_lowercase() != "y" { + println!("Skipping keyshade.toml creation..."); + should_upsert_keyshade_toml = false; + } + } + + // Check if user root toml exists + let user_root_toml_path = get_os_specific_user_root_config_path!(self.parsed_data.project); + if file_exists!(&user_root_toml_path) { + // If it does, ask if the users want to overwrite it + let choice = read_from_terminal!(format!( + "{} already exists. Do you want to overwrite it? (y/n): ", + user_root_toml_path + )); + + if choice.to_lowercase() != "y" { + println!("Skipping user root toml creation..."); + should_upsert_user_root_toml = false; + } + } + + if should_upsert_keyshade_toml { + self.create_keyshade_toml()?; + } + + if should_upsert_user_root_toml { + self.create_user_root_toml()?; + } + Ok(()) + } +} diff --git a/src/command.rs b/src/commands/mod.rs similarity index 100% rename from src/command.rs rename to src/commands/mod.rs diff --git a/src/command/run.rs b/src/commands/run.rs similarity index 100% rename from src/command/run.rs rename to src/commands/run.rs diff --git a/src/constants.rs b/src/constants.rs index 0940aa0..d0334b2 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,11 +1,11 @@ /// The version of the application. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const ABOUT: &str = "A command line utility for keyshades. +pub const ABOUT: &str = "A command line utility for keyshade. -This tools helps you to populate your secrects to your application, and manage them in a secure way. +This tools helps you to populate your secrets and configurations to your application, and manage them in a secure way. Use `--help` on the subcommands to learn more about them."; pub const CONFIG_FILE_NAME: &str = ".keyshade.toml"; -pub const BASE_URL: &str = "https://api.keyshades.com"; \ No newline at end of file +pub const BASE_URL: &str = "https://api.keyshade.xyz"; diff --git a/src/macros/command_line.rs b/src/macros/command_line.rs new file mode 100644 index 0000000..218e9ef --- /dev/null +++ b/src/macros/command_line.rs @@ -0,0 +1,53 @@ +#[macro_export] +/// Reads a line of input from the terminal. +/// +/// # Arguments +/// +/// * `$prompt` - The prompt to display to the user. +/// +/// # Returns +/// +/// A string representation of the user input. +/// +/// # Example +/// +/// ```rust +/// let input: String = read_from_terminal!("Enter your name:").unwrap(); +/// println!("{}", input); +/// ``` +macro_rules! read_from_terminal { + ($prompt:expr) => {{ + let mut input = String::new(); + print!("{}", $prompt); + ::std::io::stdout().flush().unwrap(); + ::std::io::stdin().read_line(&mut input).unwrap(); + input.trim().to_string() + }}; +} + +#[macro_export] +/// Reads a line of input securely from the terminal. +/// +/// # Arguments +/// +/// * `$prompt` - The prompt to display to the user. +/// +/// # Returns +/// +/// A string representation of the user input. +/// +/// # Example +/// +/// ```rust +/// let input: String = read_securely_from_terminal!("Enter your password:").unwrap(); +/// println!("{}", input); +/// ``` +macro_rules! read_securely_from_terminal { + ($prompt:expr) => {{ + let input = ::inquire::Password::new($prompt) + .without_confirmation() + .prompt() + .unwrap(); + input.trim().to_string() + }}; +} diff --git a/src/macros/file_io.rs b/src/macros/file_io.rs new file mode 100644 index 0000000..dc61abf --- /dev/null +++ b/src/macros/file_io.rs @@ -0,0 +1,32 @@ +#[macro_export] +macro_rules! file_exists { + ($file_name:expr) => {{ + ::std::fs::metadata($file_name).is_ok() + }}; +} + +#[macro_export] +macro_rules! read_file { + ($file_name:expr) => {{ + let mut file = ::std::fs::File::open($file_name).unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + contents + }}; +} + +#[macro_export] +macro_rules! write_file { + ($file_name:expr, $contents:expr) => {{ + let path = ::std::path::Path::new($file_name); + + // Create the parent directory if it doesn't exist + if let Some(parent) = path.parent() { + ::std::fs::create_dir_all(parent).unwrap(); + } + + // Write the contents to the file + let mut file = ::std::fs::File::create(&path).unwrap(); + file.write_all($contents.as_bytes()).unwrap(); + }}; +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs new file mode 100644 index 0000000..df0cddc --- /dev/null +++ b/src/macros/mod.rs @@ -0,0 +1,3 @@ +pub mod command_line; +pub mod file_io; +pub mod toml; diff --git a/src/macros/toml.rs b/src/macros/toml.rs new file mode 100644 index 0000000..9e20456 --- /dev/null +++ b/src/macros/toml.rs @@ -0,0 +1,41 @@ +#[macro_export] +macro_rules! get_os_specific_user_root_config_path { + ($project: expr) => {{ + if cfg!(windows) { + format!( + "C:\\Users\\{}\\.keyshade\\{}.toml", + ::whoami::username(), + $project + ) + } else { + format!("/home/{}/.keyshade/{}.toml", ::whoami::username(), $project) + } + }}; +} + +#[macro_export] +macro_rules! generate_user_root_toml { + ($api_key: expr, $private_key: expr, $project: expr) => {{ + let mut user_root_map = ::std::collections::HashMap::new(); + let config = crate::models::toml_model::UserRootConfig { + api_key: $api_key.to_string(), + private_key: $private_key.to_string(), + }; + user_root_map.insert($project.to_string(), &config); + ::toml::to_string_pretty(&user_root_map).unwrap() + }}; +} + +#[macro_export] +macro_rules! generate_project_toml { + ($workspace: expr, $project: expr, $environment: expr) => {{ + let mut project_map = ::std::collections::HashMap::new(); + let config = crate::models::toml_model::ProjectRootConfig { + workspace: $workspace.to_string(), + project: $project.to_string(), + environment: $environment.to_string(), + }; + project_map.insert($project.to_string(), &config); + ::toml::to_string_pretty(&project_map).unwrap() + }}; +} diff --git a/src/main.rs b/src/main.rs index 4373c9c..ec4a69a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ -mod command; +mod commands; mod constants; +mod macros; mod models; -mod util; use crate::constants::{ABOUT, VERSION}; use clap::{Arg, ArgAction, ArgMatches, Command}; -use command::{configure::ConfigureCommand, run::RunCommand, AbstractCommandInterface}; +use commands::{configure::ConfigureCommand, run::RunCommand, AbstractCommandInterface}; fn cli() -> Command { Command::new("keyshades-cli") diff --git a/src/models/mod.rs b/src/models/mod.rs index 2057c97..9039f5b 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1 +1 @@ -pub mod toml_model; \ No newline at end of file +pub mod toml_model; diff --git a/src/models/toml_model.rs b/src/models/toml_model.rs index fd7de5a..6943543 100644 --- a/src/models/toml_model.rs +++ b/src/models/toml_model.rs @@ -1,22 +1,14 @@ -use std::collections::HashMap; - use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] -pub struct Project { +pub struct UserRootConfig { pub api_key: String, pub private_key: String, } #[derive(Debug, Serialize, Deserialize)] -pub struct Workspace { - #[serde(flatten)] - pub projects: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Configure { - pub base_url: String, - #[serde(flatten)] - pub workspaces: HashMap, +pub struct ProjectRootConfig { + pub workspace: String, + pub project: String, + pub environment: String, } diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 429d138..0000000 --- a/src/util.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::io::{self, Write}; - -use inquire::Password; - -pub fn read_from_terminal(prompt: &str) -> Result { - let mut input = String::new(); - print!("{}", prompt); - io::stdout().flush()?; - io::stdin().read_line(&mut input)?; - Ok(input.trim().to_string()) -} - -pub fn read_securely_from_terminal(prompt: &str) -> Result { - let input = Password::new("Enter your API Key:") - .without_confirmation() - .prompt() - .unwrap(); - Ok(input.trim().to_string()) -} From 1ce7880fa3339b1b6ac29b85483d9c722fa20b81 Mon Sep 17 00:00:00 2001 From: rajdip-b Date: Sat, 22 Jun 2024 15:11:45 +0530 Subject: [PATCH 3/4] add run command (incomplete) --- Cargo.toml | 5 ++ src/commands/configure.rs | 14 ++--- src/commands/mod.rs | 2 +- src/commands/run.rs | 129 ++++++++++++++++++++++++++++++++++---- src/macros/toml.rs | 16 ++--- src/main.rs | 41 ++++++++++-- 6 files changed, 171 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3fddb10..8b63b81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,14 @@ edition = "2021" clap = { version = "4.5.4", features = ["derive"] } colored = "2.1.0" directories = "5.0.1" +futures-util = "0.3.30" inquire = "0.7.4" +net = "0.0.2" serde = {version = "1.0.197", features = ["derive"] } +serde_json = "1.0.117" spinners = "4.1.1" +tokio = { version = "1.38.0", features = ["rt-multi-thread"] } +tokio-tungstenite = "0.23.1" toml = "0.8.12" whoami = "1.5.1" diff --git a/src/commands/configure.rs b/src/commands/configure.rs index 2f78da8..aa32423 100644 --- a/src/commands/configure.rs +++ b/src/commands/configure.rs @@ -43,9 +43,9 @@ impl<'a> ConfigureCommand<'a> { // Get the parsed toml content let toml_content = generate_project_toml!( - self.parsed_data.workspace, - self.parsed_data.project, - self.parsed_data.environment + &self.parsed_data.workspace, + &self.parsed_data.project, + &self.parsed_data.environment ); // Write the toml content to the file @@ -64,9 +64,9 @@ impl<'a> ConfigureCommand<'a> { // Get the parsed toml content let toml_content = generate_user_root_toml!( - self.parsed_data.api_key, - self.parsed_data.private_key, - self.parsed_data.project + &self.parsed_data.api_key, + &self.parsed_data.private_key, + &self.parsed_data.project ); // Write the toml content to the file @@ -127,7 +127,7 @@ impl<'a> AbstractCommandInterface for ConfigureCommand<'a> { Ok(()) } - fn execute(&self) -> Result<(), io::Error> { + async fn execute(&self) -> Result<(), io::Error> { let mut should_upsert_keyshade_toml = true; let mut should_upsert_user_root_toml = true; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 5724771..9ae2de2 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,5 +5,5 @@ pub mod run; pub trait AbstractCommandInterface { fn parse_args(&mut self) -> Result<(), io::Error>; - fn execute(&self) -> Result<(), io::Error>; + async fn execute(&self) -> Result<(), io::Error>; } diff --git a/src/commands/run.rs b/src/commands/run.rs index 8a8ce3d..b5ba6ed 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,29 +1,136 @@ +use futures_util::{SinkExt, StreamExt}; +use serde_json::json; use std::io; +use std::io::Read; +use tokio_tungstenite::{ + connect_async, + tungstenite::{handshake::client::Request, Message}, +}; -use super::AbstractCommandInterface; +use crate::{ + file_exists, get_os_specific_user_root_config_path, + models::toml_model::{ProjectRootConfig, UserRootConfig}, + read_file, +}; -struct RunCommandParsedData {} +use super::AbstractCommandInterface; -pub struct RunCommand<'a> { - parsed_data: RunCommandParsedData, - args: &'a clap::ArgMatches, +pub struct RunCommand { + workspace: String, + project: String, + environment: String, + api_key: String, + private_key: String, } -impl RunCommand<'_> { - pub fn new(args: &clap::ArgMatches) -> RunCommand { +impl RunCommand { + pub fn new() -> RunCommand { RunCommand { - parsed_data: RunCommandParsedData {}, - args, + workspace: String::new(), + project: String::new(), + environment: String::new(), + api_key: String::new(), + private_key: String::new(), } } + + fn read_keyshade_toml(&self) -> Result { + // Check if the keyshade.toml exists + if !file_exists!("keyshade.toml") { + panic!("keyshade.toml not found. Please run `keyshade configure` first."); + } + + // Read the keyshade.toml + let keyshade_toml_content = read_file!("keyshade.toml"); + + // Parse the keyshade.toml + let keyshade_toml: ProjectRootConfig = toml::from_str(&keyshade_toml_content).unwrap(); + + Ok(keyshade_toml) + } + + fn read_user_root_toml(&self, project: &String) -> Result { + // Check if the user root toml exists + let user_root_toml_path = get_os_specific_user_root_config_path!(project); + if !file_exists!(&user_root_toml_path) { + panic!("User root toml not found. Please run `keyshade configure` first."); + } + + // Read the user root toml + let user_root_toml_content = read_file!(&user_root_toml_path); + + // Parse the user root toml + let user_root_toml: UserRootConfig = toml::from_str(&user_root_toml_content).unwrap(); + + Ok(user_root_toml) + } } -impl<'a> AbstractCommandInterface for RunCommand<'a> { +impl AbstractCommandInterface for RunCommand { fn parse_args(&mut self) -> Result<(), io::Error> { + let keyshade_toml = self.read_keyshade_toml().unwrap(); + let user_root_toml = self.read_user_root_toml(&keyshade_toml.project).unwrap(); + + self.workspace = keyshade_toml.workspace; + self.project = keyshade_toml.project; + self.environment = keyshade_toml.environment; + self.api_key = user_root_toml.api_key; + self.private_key = user_root_toml.private_key; + Ok(()) } - fn execute(&self) -> Result<(), io::Error> { + async fn execute(&self) -> Result<(), io::Error> { + print!("Running the project: "); + println!( + "{} {} {} {}", + self.workspace, self.project, self.environment, self.api_key + ); + + // Create a request with the URL and headers + let request = Request::builder() + .uri("ws://localhost:4200/change-notifier") + .header("x-keyshade-token", &self.api_key) + .header("sec-websocket-key", &self.private_key) + .header("host", "localhost:4200") + .body(()) + .expect("Failed to build request"); + + // Connect to the WebSocket server with the custom headers + let (ws_stream, _) = connect_async(request) + .await + .expect("Failed to connect to WebSocket server"); + + println!("Connected"); + + let (mut write, mut read) = ws_stream.split(); + + // Register client app + let register_message = json!({ + "workspaceName": self.workspace, + "projectName": self.project, + "environmentName": self.environment + }); + + write + .send(Message::Text(register_message.to_string())) + .await + .expect("Failed to send register message"); + + // Listen for configuration-updated messages + while let Some(message) = read.next().await { + match message { + Ok(msg) => match msg { + Message::Text(text) => println!("Change received: {}", text), + _ => (), + }, + Err(e) => { + println!("Error: {}", e); + break; + } + } + } + Ok(()) } } diff --git a/src/macros/toml.rs b/src/macros/toml.rs index 9e20456..332d354 100644 --- a/src/macros/toml.rs +++ b/src/macros/toml.rs @@ -17,11 +17,8 @@ macro_rules! get_os_specific_user_root_config_path { macro_rules! generate_user_root_toml { ($api_key: expr, $private_key: expr, $project: expr) => {{ let mut user_root_map = ::std::collections::HashMap::new(); - let config = crate::models::toml_model::UserRootConfig { - api_key: $api_key.to_string(), - private_key: $private_key.to_string(), - }; - user_root_map.insert($project.to_string(), &config); + user_root_map.insert("api_key".to_string(), $api_key); + user_root_map.insert("private_key".to_string(), $private_key); ::toml::to_string_pretty(&user_root_map).unwrap() }}; } @@ -30,12 +27,9 @@ macro_rules! generate_user_root_toml { macro_rules! generate_project_toml { ($workspace: expr, $project: expr, $environment: expr) => {{ let mut project_map = ::std::collections::HashMap::new(); - let config = crate::models::toml_model::ProjectRootConfig { - workspace: $workspace.to_string(), - project: $project.to_string(), - environment: $environment.to_string(), - }; - project_map.insert($project.to_string(), &config); + project_map.insert("workspace".to_string(), $workspace); + project_map.insert("project".to_string(), $project); + project_map.insert("environment".to_string(), $environment); ::toml::to_string_pretty(&project_map).unwrap() }}; } diff --git a/src/main.rs b/src/main.rs index ec4a69a..90eab45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ mod constants; mod macros; mod models; +use std::io; + use crate::constants::{ABOUT, VERSION}; use clap::{Arg, ArgAction, ArgMatches, Command}; @@ -56,19 +58,46 @@ fn cli() -> Command { .help("Configure the private key"), ), ) + .subcommand( + Command::new("run") + .alias("r") + .about("Run the keyshades CLI, alias to `r`"), + ) +} + +pub enum CommandEnum<'a> { + Configure(ConfigureCommand<'a>), + Run(RunCommand), +} + +impl<'a> AbstractCommandInterface for CommandEnum<'a> { + fn parse_args(&mut self) -> Result<(), io::Error> { + match self { + CommandEnum::Configure(command) => command.parse_args(), + CommandEnum::Run(command) => command.parse_args(), + } + } + + async fn execute(&self) -> Result<(), io::Error> { + match self { + CommandEnum::Configure(command) => command.execute().await, + CommandEnum::Run(command) => command.execute().await, + } + } } -fn main() { +#[tokio::main] +async fn main() { let matches: ArgMatches = cli().get_matches(); - let mut command: Option> = None; + let mut command: Option = None; // Get the subcommand implementation based on the user input match matches.subcommand() { Some(("configure", args)) => { - command = Some(Box::new(ConfigureCommand::new(args))); + command = Some(CommandEnum::Configure(ConfigureCommand::new(args))); } - Some(("run", args)) => { - command = Some(Box::new(RunCommand::new(args))); + Some(("run", _args)) => { + command = Some(CommandEnum::Run(RunCommand::new())); } _ => { println!("Error: No subcommand provided. Usage: ks [SUBCOMMAND] [OPTIONS]"); @@ -78,7 +107,7 @@ fn main() { // Execute the subcommand if let Some(mut c) = command { c.parse_args().unwrap(); - c.execute().unwrap(); + let _ = c.execute().await; } else { panic!("Error: No subcommand provided. Usage: ks [SUBCOMMAND] [OPTIONS]"); } From ad0de107c54843739bd47f7d9c99799e2411e9ad Mon Sep 17 00:00:00 2001 From: Sambit Chakraborty Date: Sun, 23 Jun 2024 19:13:29 +0530 Subject: [PATCH 4/4] Refactor: ws is rewritten with websocket crate --- Cargo.toml | 1 + src/commands/run.rs | 72 +++++++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b63b81..c405484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ spinners = "4.1.1" tokio = { version = "1.38.0", features = ["rt-multi-thread"] } tokio-tungstenite = "0.23.1" toml = "0.8.12" +websocket = "0.27.1" whoami = "1.5.1" # We optimize the release build for size. (https://doc.rust-lang.org/cargo/reference/profiles.html) diff --git a/src/commands/run.rs b/src/commands/run.rs index b5ba6ed..8102721 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,11 +1,8 @@ -use futures_util::{SinkExt, StreamExt}; -use serde_json::json; use std::io; -use std::io::Read; -use tokio_tungstenite::{ - connect_async, - tungstenite::{handshake::client::Request, Message}, -}; +use std::thread; +use websocket::client::ClientBuilder; +use websocket::OwnedMessage; +use serde_json::json; use crate::{ file_exists, get_os_specific_user_root_config_path, @@ -80,31 +77,27 @@ impl AbstractCommandInterface for RunCommand { Ok(()) } - async fn execute(&self) -> Result<(), io::Error> { + fn execute(&self) -> Result<(), io::Error> { print!("Running the project: "); println!( "{} {} {} {}", self.workspace, self.project, self.environment, self.api_key ); - // Create a request with the URL and headers - let request = Request::builder() - .uri("ws://localhost:4200/change-notifier") - .header("x-keyshade-token", &self.api_key) - .header("sec-websocket-key", &self.private_key) - .header("host", "localhost:4200") - .body(()) - .expect("Failed to build request"); - - // Connect to the WebSocket server with the custom headers - let (ws_stream, _) = connect_async(request) - .await + // Create a ClientBuilder + let mut client = ClientBuilder::new("ws://localhost:4200/change-notifier") + .unwrap() + .add_protocol("rust-websocket") + .custom_headers(&vec![ + ("x-keyshade-token".to_string(), self.api_key.clone()), + ("sec-websocket-key".to_string(), self.private_key.clone()), + ("host".to_string(), "localhost:4200".to_string()), + ]) + .connect_insecure() .expect("Failed to connect to WebSocket server"); println!("Connected"); - let (mut write, mut read) = ws_stream.split(); - // Register client app let register_message = json!({ "workspaceName": self.workspace, @@ -112,25 +105,28 @@ impl AbstractCommandInterface for RunCommand { "environmentName": self.environment }); - write - .send(Message::Text(register_message.to_string())) - .await + client + .send_message(&OwnedMessage::Text(register_message.to_string())) .expect("Failed to send register message"); - // Listen for configuration-updated messages - while let Some(message) = read.next().await { - match message { - Ok(msg) => match msg { - Message::Text(text) => println!("Change received: {}", text), - _ => (), - }, - Err(e) => { - println!("Error: {}", e); - break; + // Create a thread to receive messages + let (mut receiver, _) = client.split().unwrap(); + thread::spawn(move || { + for message in receiver.incoming_messages() { + match message { + Ok(OwnedMessage::Text(text)) => println!("Change received: {}", text), + Ok(_) => (), + Err(e) => { + println!("Error: {:?}", e); + break; + } } } - } + }); - Ok(()) + // Keep the main thread running + loop { + std::thread::sleep(std::time::Duration::from_secs(1)); + } } -} +} \ No newline at end of file