diff --git a/Cargo.lock b/Cargo.lock index d6b0c4c..462f375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "backtrace" version = "0.3.75" @@ -288,6 +294,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -469,6 +484,7 @@ dependencies = [ "clap", "httptest", "lazy_static", + "predicates", "regex", "reqwest", "rstest", @@ -873,6 +889,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -979,7 +1010,10 @@ checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", + "float-cmp", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 73c5179..dacd702 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ lazy_static = "1.5.0" rstest = "0.26.1" assert_cmd = "2.0.17" uuid = { version = "1.18.1", features = ["v4"]} -httptest = "0.16.3" \ No newline at end of file +httptest = "0.16.3" +predicates = "3.1.3" \ No newline at end of file diff --git a/src/commands.rs b/src/commands.rs index e415ccd..b5909c2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -5,6 +5,7 @@ mod delete_command; mod list_command; mod set_command; mod bindings; +mod view_command; use crate::commands::call_command::CallServiceEndpointCommand; use crate::commands::delete_command::DeleteCommand; @@ -12,7 +13,8 @@ use crate::commands::edit_command::EditCommand; use crate::commands::list_command::ListCommand; use crate::commands::new_command::NewCommand; use crate::commands::set_command::SetCommand; -use crate::commands::RootCommand::{Call, Delete, Edit, List, New, Set}; +use crate::commands::view_command::ViewCommand; +use crate::commands::RootCommand::{Call, Delete, Edit, List, New, Set, View}; use crate::config::current_config::HtrsConfig; use crate::htrs_binding_error::HtrsBindingError; use crate::outcomes::{HtrsAction, HtrsError}; @@ -26,6 +28,7 @@ pub enum RootCommand { Delete(DeleteCommand), List(ListCommand), Set(SetCommand), + View(ViewCommand) } impl RootCommand { @@ -40,6 +43,7 @@ impl RootCommand { .subcommand(DeleteCommand::get_command()) .subcommand(ListCommand::get_command()) .subcommand(SetCommand::get_command()) + .subcommand(ViewCommand::get_command()) } pub fn bind_from_matches(args: &ArgMatches, config: &HtrsConfig) -> Result { @@ -74,6 +78,11 @@ impl RootCommand { Ok(Set( SetCommand::bind_from_matches(set_matches) )) + }, + Some(("view", view_matches)) => { + Ok(View( + ViewCommand::bind_from_matches(view_matches) + )) } _ => unreachable!() } @@ -89,6 +98,7 @@ impl RootCommand { Delete(delete_command) => delete_command.execute(config), List(list_command) => list_command.execute(config), Set(set_command) => set_command.execute(config), + View(view_command) => view_command.execute(config), } } } \ No newline at end of file diff --git a/src/commands/view_command.rs b/src/commands/view_command.rs new file mode 100644 index 0000000..ee68329 --- /dev/null +++ b/src/commands/view_command.rs @@ -0,0 +1,33 @@ +use crate::commands::view_command::view_preset_command::ViewPresetCommand; +use crate::commands::view_command::ViewCommand::Preset; +use crate::config::current_config::HtrsConfig; +use crate::outcomes::{HtrsAction, HtrsError}; +use clap::{ArgMatches, Command}; + +mod view_preset_command; + +pub enum ViewCommand { + Preset(ViewPresetCommand), +} + +impl ViewCommand { + pub fn get_command() -> Command { + Command::new("view") + .about("View an item") + .arg_required_else_help(true) + .subcommand(ViewPresetCommand::get_command()) + } + + pub fn bind_from_matches(args: &ArgMatches) -> ViewCommand { + match args.subcommand() { + Some(("preset", preset_matches)) => Preset(ViewPresetCommand::bind_from_matches(preset_matches)), + _ => unreachable!(), + } + } + + pub fn execute(&self, config: &HtrsConfig) -> Result { + match self { + Preset(command) => command.execute(config), + } + } +} diff --git a/src/commands/view_command/view_preset_command.rs b/src/commands/view_command/view_preset_command.rs new file mode 100644 index 0000000..8aea2f9 --- /dev/null +++ b/src/commands/view_command/view_preset_command.rs @@ -0,0 +1,45 @@ +use crate::commands::bindings::MatchBinding; +use crate::config::current_config::HtrsConfig; +use crate::outcomes::{HtrsAction, HtrsError}; +use clap::{Arg, ArgMatches, Command}; + +pub struct ViewPresetCommand { + pub name: String, +} + +impl ViewPresetCommand { + pub fn get_command() -> Command { + Command::new("preset") + .about("View a preset") + .arg( + Arg::new("name") + .help("Name or alias of the preset") + .value_name("name") + .required(true) + ) + } + + pub fn bind_from_matches(args: &ArgMatches) -> ViewPresetCommand { + ViewPresetCommand { + name: args.bind_field("name") + } + } + + pub fn execute(&self, config: &HtrsConfig) -> Result { + let Some(preset) = config.get_preset(self.name.as_str()) else { + return Err(HtrsError::new(format!("No preset could be found with name or alias `{}`", self.name).as_str())); + }; + + let name = match preset.alias { + Some(ref alias) => format!("{} ({}):", preset.name, alias), + None => format!("{}:", preset.name), + }; + + let values = preset.values.iter() + .map(|(key, value)| format!(" - {}: {}", key, value)) + .collect::>() + .join("\n"); + + Ok(HtrsAction::PrintDialogue(format!("{}\n{}\n", name, values))) + } +} \ No newline at end of file diff --git a/tests/common/builders.rs b/tests/common/builders.rs index efc98f2..8fb0c67 100644 --- a/tests/common/builders.rs +++ b/tests/common/builders.rs @@ -18,6 +18,7 @@ pub struct ServiceBuilder { pub struct PresetBuilder { pub name: Option, + pub alias: Option, pub values: HashMap, } @@ -121,6 +122,7 @@ impl PresetBuilder { pub fn new() -> Self { Self { name: None, + alias: None, values: HashMap::new(), } } @@ -130,6 +132,11 @@ impl PresetBuilder { self } + pub fn with_alias(mut self, alias: &str) -> Self { + self.alias = Some(alias.to_string()); + self + } + pub fn with_value(mut self, key: &str, value: &str) -> Self { self.values.insert(key.to_string(), value.to_string()); self @@ -138,7 +145,7 @@ impl PresetBuilder { pub fn build(self) -> Preset { Preset { name: self.name.unwrap(), - alias: None, + alias: self.alias, values: self.values, } } diff --git a/tests/presets/mod.rs b/tests/presets/mod.rs index 409c0ae..a780807 100644 --- a/tests/presets/mod.rs +++ b/tests/presets/mod.rs @@ -1,3 +1,4 @@ mod create_new_preset_tests; mod delete_preset_tests; -mod edit_preset_tests; \ No newline at end of file +mod edit_preset_tests; +mod view_preset_tests; \ No newline at end of file diff --git a/tests/presets/view_preset_tests.rs b/tests/presets/view_preset_tests.rs new file mode 100644 index 0000000..8a2c0ad --- /dev/null +++ b/tests/presets/view_preset_tests.rs @@ -0,0 +1,80 @@ + +mod view_preset_tests { + use crate::common::builders::{HtrsConfigBuilder, PresetBuilder}; + use crate::common::test_helpers::{clear_config, setup}; + use assert_cmd::Command; + use predicates::prelude::*; + use std::error::Error; + + #[test] + pub fn given_unknown_preset_when_view_then_should_fail() -> Result<(), Box> { + let path = setup(None); + + Command::cargo_bin("htrs")? + .env("HTRS_CONFIG_PATH", &path) + .arg("view") + .arg("preset") + .arg("unknown_preset") + .assert() + .failure() + .stdout("No preset could be found with name or alias `unknown_preset`\n"); + + clear_config(&path); + Ok(()) + } + + #[test] + pub fn given_known_preset_name_when_view_should_succeed() -> Result<(), Box> { + let config = HtrsConfigBuilder::new() + .with_preset( + PresetBuilder::new() + .with_name("foo_name") + .with_value("key", "value") + ) + .build(); + let path = setup(Some(config)); + + Command::cargo_bin("htrs")? + .env("HTRS_CONFIG_PATH", &path) + .arg("view") + .arg("preset") + .arg("foo_name") + .assert() + .success() + .stdout( + predicate::str::contains("foo_name") + .and(predicate::str::contains(" - key: value")) + ); + + clear_config(&path); + Ok(()) + } + + #[test] + pub fn given_known_preset_alias_when_view_should_succeed() -> Result<(), Box> { + let config = HtrsConfigBuilder::new() + .with_preset( + PresetBuilder::new() + .with_name("foo_name") + .with_alias("foo_alias") + .with_value("key", "value") + ) + .build(); + let path = setup(Some(config)); + + Command::cargo_bin("htrs")? + .env("HTRS_CONFIG_PATH", &path) + .arg("view") + .arg("preset") + .arg("foo_alias") + .assert() + .success() + .stdout( + predicate::str::contains("foo_name (foo_alias)") + .and(predicate::str::contains(" - key: value")) + ); + + clear_config(&path); + Ok(()) + } +}