From 19319322c763dc1d85d07fc27e848caa552bdb7c Mon Sep 17 00:00:00 2001 From: GDGunnars Date: Mon, 17 Jun 2024 18:24:22 +0200 Subject: [PATCH 1/6] use expect rather than unwrap for better errors. --- src/commands/action.rs | 24 +++++++++-------- src/commands/server.rs | 61 +++++++++++++++++++++++++----------------- src/config.rs | 22 ++++++++------- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/src/commands/action.rs b/src/commands/action.rs index 7d6e0d8..8bf7897 100644 --- a/src/commands/action.rs +++ b/src/commands/action.rs @@ -12,10 +12,12 @@ fn create_and_auth_client(server: ServerConfig) -> Result read_timeout: Some(13), write_timeout: Some(37), }) - .unwrap(); + .expect("Could not create RCON client"); - // Auth request to RCON server (SERVERDATA_AUTH) - client.auth(AuthRequest::new(server.pass)).unwrap(); + // Auth request to RCON server using stored password + client + .auth(AuthRequest::new(server.pass)) + .expect("Could not authenticate with RCON server"); Ok(client) } @@ -27,11 +29,9 @@ fn determine_server(use_default_server: bool) -> ServerConfig { error!("No default server set, please add one with `rcon server set-default`",); std::process::exit(2); } - println!( - "▲ Default server is enabled: {}", - cfg.default_server.as_ref().unwrap() - ); - return cfg.default_server.unwrap(); + let default_server = cfg.default_server.expect("No default server set"); + println!("▲ Default server is enabled: {}", default_server); + return default_server; } else { return config::select_server_from_list(); } @@ -40,11 +40,13 @@ fn determine_server(use_default_server: bool) -> ServerConfig { pub fn shell(use_default_server: bool) -> Result<(), RCONError> { let server: ServerConfig = determine_server(use_default_server); // Create new RCON client & validate auth - let mut client = create_and_auth_client(server).unwrap(); + let mut client = create_and_auth_client(server).expect("Could not create RCON client"); let mut continue_loop = true; while continue_loop { - let command = Text::new("(q/quit) Enter RCON command:").prompt().unwrap(); + let command = Text::new("(q/quit) Enter RCON command:") + .prompt() + .expect("Could not get input"); if command == "q" || command == "quit" { continue_loop = false; @@ -65,7 +67,7 @@ pub fn shell(use_default_server: bool) -> Result<(), RCONError> { pub fn execute_command(use_default_server: bool, command: String) -> Result<(), RCONError> { let server: ServerConfig = determine_server(use_default_server); // Create new RCON client & validate auth - let mut client = create_and_auth_client(server).unwrap(); + let mut client = create_and_auth_client(server).expect("Could not create RCON client"); let mut table = Table::new(); diff --git a/src/commands/server.rs b/src/commands/server.rs index 7a80848..1d86450 100644 --- a/src/commands/server.rs +++ b/src/commands/server.rs @@ -7,14 +7,25 @@ use inquire::{Confirm, Text}; use log::{debug, info}; pub fn add_server() { - let name = Text::new("Name of server:").prompt().unwrap(); - let ip: String = Text::new("Address:").prompt().unwrap(); - let port: String = Text::new("Port:").with_default("27015").prompt().unwrap(); - let pass: String = Text::new("RCON password:").prompt().unwrap(); - let set_as_default: bool = Confirm::new("Set as default server?").prompt().unwrap(); + let name = Text::new("Name of server:") + .prompt() + .expect("Could not get server name"); + let ip: String = Text::new("Address:") + .prompt() + .expect("Could not get server address"); + let port: String = Text::new("Port:") + .with_default("27015") + .prompt() + .expect("Could not get server port"); + let pass: String = Text::new("RCON password:") + .prompt() + .expect("Could not get RCON password"); + let set_as_default: bool = Confirm::new("Set as default server?") + .prompt() + .expect("Could not get default server setting"); let correct_info = Confirm::new("Is the above information correct?") .prompt() - .unwrap(); + .expect("Could not get confirmation"); if !correct_info { info!("Server configuration not saved"); @@ -47,26 +58,26 @@ pub fn edit_server() { let name = Text::new("Name of server:") .with_default(&server.name) .prompt() - .unwrap(); + .expect("Could not get server name"); let ip = Text::new("IP address:") .with_default(&server.ip) .prompt() - .unwrap(); + .expect("Could not get server address"); let port = Text::new("Port:") .with_default(&server.port) .prompt() - .unwrap(); + .expect("Could not get server port"); let pass = Text::new("RCON password:") .with_default(&server.pass) .prompt() - .unwrap(); + .expect("Could not get RCON password"); let override_settings = Confirm::new(&format!( "You want to override {} with the above info?", server.name )) .prompt() - .unwrap(); + .expect("Could not get confirmation"); if override_settings { let mut new_config: RconCliConfig = config::get_config(); @@ -82,7 +93,7 @@ pub fn edit_server() { pub fn clear_servers() { let confirm_prompt = Confirm::new("Are you sure you want to clear all server configurations?") .prompt() - .unwrap(); + .expect("Could not get confirmation"); if confirm_prompt { let mut cfg: RconCliConfig = config::get_config(); cfg.clear(); @@ -101,7 +112,7 @@ pub fn remove_server() { server.name )) .prompt() - .unwrap(); + .expect("Could not get confirmation"); if confirm_prompt { let mut new_config = RconCliConfig::new(cfg.default_server, cfg.servers); new_config.remove_server(server.clone()); @@ -123,18 +134,20 @@ pub fn list_servers() { ]); let cfg: RconCliConfig = config::get_config(); - for server in cfg.servers { - if cfg.default_server.is_some() && cfg.default_server.as_ref().unwrap().name == server.name - { - table.add_row(vec![ - Cell::new(&server.name).fg(Color::Green), - Cell::new(&server.ip).fg(Color::Green), - Cell::new(&server.port).fg(Color::Green), - Cell::new(&server.pass).fg(Color::Green), - Cell::new("✓").fg(Color::Green), - ]); - continue; + for server in &cfg.servers { + if let Some(ref default) = cfg.default_server { + if server == default { + table.add_row(vec![ + Cell::new(&server.name).fg(Color::Green), + Cell::new(&server.ip).fg(Color::Green), + Cell::new(&server.port).fg(Color::Green), + Cell::new(&server.pass).fg(Color::Green), + Cell::new("✓").fg(Color::Green), + ]); + continue; + } } + table.add_row(vec![ Cell::new(&server.name), Cell::new(&server.ip), diff --git a/src/config.rs b/src/config.rs index ae087c6..936df27 100644 --- a/src/config.rs +++ b/src/config.rs @@ -110,19 +110,25 @@ impl ::std::default::Default for RconCliConfig { } pub fn get_path() -> String { - let path = confy::get_configuration_file_path("rcon_cli", None).unwrap(); - return path.to_str().unwrap().to_string(); + let path = confy::get_configuration_file_path("rcon_cli", None) + .expect("Could not get configuration file path"); + + return path + .to_str() + .expect("Could not convert path to string") + .to_string(); } pub fn get_config() -> RconCliConfig { - let cfg: RconCliConfig = confy::load("rcon_cli", None).unwrap(); + let cfg: RconCliConfig = + confy::load("rcon_cli", None).expect("Could not load configuration file"); return cfg; } pub fn save_config(cfg: RconCliConfig) { let mut config = cfg.clone(); config.order_servers(); - confy::store("rcon_cli", None, config).unwrap(); + confy::store("rcon_cli", None, config).expect("Error saving configuration file"); } pub fn set_and_save_default_server(server: ServerConfig) { @@ -133,11 +139,9 @@ pub fn set_and_save_default_server(server: ServerConfig) { pub fn select_server_from_list() -> ServerConfig { let cfg: RconCliConfig = get_config(); - let ans = Select::new("Selected server: ", cfg.servers.clone()).prompt(); - let selected_server = match ans { - Ok(choice) => choice, - Err(_) => panic!("There was an error, please try again"), - }; + let selected_server = Select::new("Selected server: ", cfg.servers.clone()) + .prompt() + .expect("Error selecting server"); return selected_server; } From 3a1b90b4c4e560d970b6a32e382be09ffa66c503 Mon Sep 17 00:00:00 2001 From: GDGunnars Date: Mon, 17 Jun 2024 22:12:09 +0200 Subject: [PATCH 2/6] add rustyline package for shell interactivity Also removed empty lib.rs file --- Cargo.lock | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 0 3 files changed, 131 insertions(+) delete mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8c968e8..b3ec16b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,6 +101,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "clap" version = "4.5.4" @@ -160,6 +166,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "clipboard-win" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" +dependencies = [ + "error-code", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -255,6 +270,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "env_filter" version = "0.1.0" @@ -284,6 +305,33 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "fd-lock" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -331,6 +379,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "humantime" version = "2.1.0" @@ -380,6 +437,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -423,6 +486,27 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -482,6 +566,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -526,6 +620,7 @@ dependencies = [ "inquire", "log", "rcon-client", + "rustyline", "serde", ] @@ -590,12 +685,47 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +[[package]] +name = "rustyline" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index efe817a..2e4c7ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,5 @@ env_logger = "0.11.3" inquire = "0.7.5" log = "0.4.21" rcon-client = "0.1.2" +rustyline = "14.0.0" serde = { version = "1.0.198", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index e69de29..0000000 From d1c01e568bea916171d19f60cab4fae1a787fcf4 Mon Sep 17 00:00:00 2001 From: GDGunnars Date: Mon, 17 Jun 2024 22:14:52 +0200 Subject: [PATCH 3/6] add history to shell action --- src/commands/action.rs | 66 ++++++++++++++++++++++++++++++------------ src/config.rs | 2 +- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/commands/action.rs b/src/commands/action.rs index 8bf7897..dd1fb34 100644 --- a/src/commands/action.rs +++ b/src/commands/action.rs @@ -1,9 +1,10 @@ use comfy_table::{Cell, Color, Table}; use log::error; use rcon_client::{AuthRequest, RCONClient, RCONConfig, RCONError, RCONRequest}; +use rustyline::error::ReadlineError; +use rustyline::DefaultEditor; use crate::config::{self, ServerConfig}; -use inquire::Text; fn create_and_auth_client(server: ServerConfig) -> Result { let mut client = RCONClient::new(RCONConfig { @@ -40,25 +41,52 @@ fn determine_server(use_default_server: bool) -> ServerConfig { pub fn shell(use_default_server: bool) -> Result<(), RCONError> { let server: ServerConfig = determine_server(use_default_server); // Create new RCON client & validate auth - let mut client = create_and_auth_client(server).expect("Could not create RCON client"); - - let mut continue_loop = true; - while continue_loop { - let command = Text::new("(q/quit) Enter RCON command:") - .prompt() - .expect("Could not get input"); - - if command == "q" || command == "quit" { - continue_loop = false; - continue; + let mut client = create_and_auth_client(&server).expect("Could not create RCON client"); + let mut rl = DefaultEditor::new().expect("Could not create shell editor"); + + loop { + println!( + "▲ (q/quit) Shell mode for server: {}: {}", + server.name, server.ip + ); + let readline = rl.readline("▶︎ "); + + match readline { + Ok(command) => { + if command == "q" || command == "quit" { + println!("▶︎ Exiting, goodbye!"); + break; + } + + if let Err(err) = rl.add_history_entry(command.as_str()) { + eprintln!("Failed to add history entry: {}", err); + } + + let mut table = Table::new(); + + let command_response = client.execute(RCONRequest::new(command))?; + + table.add_row(vec![Cell::new(command_response.body).fg(Color::Green)]); + println!("▲\n{}", table); + } + + Err(ReadlineError::Interrupted) => { + // CTRL-C" + println!("▶︎ Exiting, goodbye!"); + break; + } + + Err(ReadlineError::Eof) => { + // CTRL-D + println!("▶︎ Exiting, goodbye!"); + break; + } + + Err(err) => { + println!("▼ Error: {:?}", err); + break; + } } - - let mut table = Table::new(); - - let command_response = client.execute(RCONRequest::new(command))?; - - table.add_row(vec![Cell::new(command_response.body).fg(Color::Green)]); - println!("▲\n{}", table); } Ok(()) diff --git a/src/config.rs b/src/config.rs index 936df27..076c2ee 100644 --- a/src/config.rs +++ b/src/config.rs @@ -139,7 +139,7 @@ pub fn set_and_save_default_server(server: ServerConfig) { pub fn select_server_from_list() -> ServerConfig { let cfg: RconCliConfig = get_config(); - let selected_server = Select::new("Selected server: ", cfg.servers.clone()) + let selected_server = Select::new("Selected server: ", cfg.servers) .prompt() .expect("Error selecting server"); From 323f7800bceaeb27e9f3ea6fa5be68c1530f9292 Mon Sep 17 00:00:00 2001 From: GDGunnars Date: Mon, 17 Jun 2024 22:38:42 +0200 Subject: [PATCH 4/6] minor refactor to reduce cloning --- src/commands/action.rs | 6 +++--- src/shell_tools.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/action.rs b/src/commands/action.rs index dd1fb34..26cdae7 100644 --- a/src/commands/action.rs +++ b/src/commands/action.rs @@ -6,7 +6,7 @@ use rustyline::DefaultEditor; use crate::config::{self, ServerConfig}; -fn create_and_auth_client(server: ServerConfig) -> Result { +fn create_and_auth_client(server: &ServerConfig) -> Result { let mut client = RCONClient::new(RCONConfig { url: format!("{}:{}", server.ip, server.port), // Optional @@ -17,7 +17,7 @@ fn create_and_auth_client(server: ServerConfig) -> Result // Auth request to RCON server using stored password client - .auth(AuthRequest::new(server.pass)) + .auth(AuthRequest::new(server.pass.clone())) .expect("Could not authenticate with RCON server"); Ok(client) @@ -95,7 +95,7 @@ pub fn shell(use_default_server: bool) -> Result<(), RCONError> { pub fn execute_command(use_default_server: bool, command: String) -> Result<(), RCONError> { let server: ServerConfig = determine_server(use_default_server); // Create new RCON client & validate auth - let mut client = create_and_auth_client(server).expect("Could not create RCON client"); + let mut client = create_and_auth_client(&server).expect("Could not create RCON client"); let mut table = Table::new(); diff --git a/src/shell_tools.rs b/src/shell_tools.rs index 0fbc8f5..aa5ce87 100644 --- a/src/shell_tools.rs +++ b/src/shell_tools.rs @@ -13,7 +13,7 @@ pub fn completion(path: Option) -> Result<(), Error> { let name = cli.get_name().to_string(); if let Some(location) = path { - generate(shell, &mut cli, name, &mut File::create(location.clone())?); + generate(shell, &mut cli, name, &mut File::create(&location)?); println!("Shell autocomplete script generated at: {:?}", location) } else { error!("▲ Could not determine location to generate shell completion script"); From 4576af2e2602419a94b41c6a970720d7e3d20505 Mon Sep 17 00:00:00 2001 From: GDGunnars Date: Mon, 17 Jun 2024 22:45:14 +0200 Subject: [PATCH 5/6] add a PR gh-action workflow to validate it builds on all platforms --- .github/workflows/on-pull.yml | 39 +++++++++++++++++++++++++++++++ .github/workflows/on-release.yaml | 3 --- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/on-pull.yml diff --git a/.github/workflows/on-pull.yml b/.github/workflows/on-pull.yml new file mode 100644 index 0000000..0b845a0 --- /dev/null +++ b/.github/workflows/on-pull.yml @@ -0,0 +1,39 @@ +name: Pull request build + +on: + pull_request: + +jobs: + build-and-upload: + name: build and upload + runs-on: ${{ matrix.os }} + + strategy: + matrix: + include: + - build: linux + os: ubuntu-latest + target: x86_64-unknown-linux-musl + + - build: macos + os: macos-latest + target: aarch64-apple-darwin + + - build: windows-gnu + os: windows-latest + target: x86_64-pc-windows-gnu + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install Rust + # Or @nightly if you want + uses: dtolnay/rust-toolchain@stable + # Arguments to pass in + with: + # Make Rust compile to our target (defined in the matrix) + targets: ${{ matrix.target }} + + - name: Build project + run: cargo build --release --target ${{ matrix.target }} diff --git a/.github/workflows/on-release.yaml b/.github/workflows/on-release.yaml index 531e523..aa84eda 100644 --- a/.github/workflows/on-release.yaml +++ b/.github/workflows/on-release.yaml @@ -29,9 +29,7 @@ jobs: uses: actions/checkout@v4 - name: Install Rust - # Or @nightly if you want uses: dtolnay/rust-toolchain@stable - # Arguments to pass in with: # Make Rust compile to our target (defined in the matrix) targets: ${{ matrix.target }} @@ -40,7 +38,6 @@ jobs: shell: bash run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - # TODO: Build project here - name: Build project run: cargo build --release --target ${{ matrix.target }} From 5ac493007c3f6a6e607b58512d2d0131919fd019 Mon Sep 17 00:00:00 2001 From: GDGunnars Date: Mon, 17 Jun 2024 22:48:24 +0200 Subject: [PATCH 6/6] update package version to 1.0.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3ec16b..24703e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,7 +608,7 @@ dependencies = [ [[package]] name = "rcon" -version = "1.0.0" +version = "1.0.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 2e4c7ef..b93db1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rcon" -version = "1.0.0" +version = "1.0.1" edition = "2021" authors = ["GDGunnars ", "BLAST.tv "] license = "MIT OR Apache-2.0"