diff --git a/src/confirm.rs b/src/confirm.rs index bad33f6..58a0496 100644 --- a/src/confirm.rs +++ b/src/confirm.rs @@ -20,7 +20,7 @@ impl DialogPlugin { } if call.has_flag("abortable") { - let result = confirm.prompt_opt()?; + let result = confirm.ask_opt(call.head)?; if let Some(val) = result { Ok(Value::Bool { @@ -31,7 +31,7 @@ impl DialogPlugin { Ok(Value::Nothing { span: call.head }) } } else { - let result = confirm.prompt()?; + let result = confirm.ask(call.head)?; Ok(Value::Bool { val: result, span: call.head, diff --git a/src/lib.rs b/src/lib.rs index 72b12ec..b231d4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use nu_plugin::{LabeledError, Plugin}; use nu_protocol::{PluginSignature, SyntaxShape}; mod confirm; +mod password; mod prompt; mod select; @@ -63,6 +64,25 @@ impl Plugin for DialogPlugin { None, ) .category(nu_protocol::Category::Misc), + PluginSignature::build("ask password") + .usage("Prompt the user with a password input.") + .named( + "prompt", + SyntaxShape::String, + "The prompt to this password input", + None, + ) + .switch( + "confirm", + "Prompts the user twice for matching password inputs", + None, + ) + .switch( + "allow-empty", + "Allows the user to input an empty password", + None, + ) + .category(nu_protocol::Category::Misc), ] } @@ -75,6 +95,7 @@ impl Plugin for DialogPlugin { match name { "ask confirm" => self.confirm(call, input), "ask select" => self.select(call, input), + "ask password" => self.password(call, input), "ask" => Err(LabeledError { label: "Missing subcommand".into(), diff --git a/src/password.rs b/src/password.rs new file mode 100644 index 0000000..edcb5fe --- /dev/null +++ b/src/password.rs @@ -0,0 +1,27 @@ +use nu_plugin::{EvaluatedCall, LabeledError}; +use nu_protocol::Value; + +use crate::{prompt::UserPrompt, DialogPlugin}; + +impl DialogPlugin { + pub(crate) fn password( + &self, + call: &EvaluatedCall, + _input: &Value, + ) -> Result { + let mut pw_input = dialoguer::Password::with_theme(&*self.theme); + + pw_input.allow_empty_password(call.has_flag("allow-empty")); + + if call.has_flag("confirm") { + pw_input.with_confirmation("Repeat your input", "Error: The inputs don't match"); + } + + let password = pw_input.ask(call.head)?; + + Ok(Value::String { + val: password, + span: call.head, + }) + } +} diff --git a/src/prompt/generic_select.rs b/src/prompt/generic_select.rs index 70c8a37..e076f49 100644 --- a/src/prompt/generic_select.rs +++ b/src/prompt/generic_select.rs @@ -1,4 +1,5 @@ use dialoguer::{theme::Theme, FuzzySelect, Select}; +use nu_protocol::Span; use super::{create_labeled_error, UserPrompt}; @@ -44,20 +45,20 @@ impl<'a> GenericSelect<'a> { impl<'a> UserPrompt for GenericSelect<'a> { type Output = usize; - fn prompt(&self) -> Result { + fn ask(&self, span: Span) -> Result { match self { GenericSelect::Fuzzy(f) => f.interact(), GenericSelect::Normal(n) => n.interact(), } - .map_err(create_labeled_error) + .map_err(|e| create_labeled_error(e, span)) } - fn prompt_opt(&self) -> Result, nu_plugin::LabeledError> { + fn ask_opt(&self, span: Span) -> Result, nu_plugin::LabeledError> { match self { GenericSelect::Fuzzy(f) => f.interact_opt(), GenericSelect::Normal(n) => n.interact_opt(), } - .map_err(create_labeled_error) + .map_err(|e| create_labeled_error(e, span)) } } diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs index e9349f3..3c7a366 100644 --- a/src/prompt/mod.rs +++ b/src/prompt/mod.rs @@ -1,33 +1,48 @@ use std::io; +use dialoguer::Password; use nu_plugin::LabeledError; mod generic_select; pub use generic_select::GenericSelect; +use nu_protocol::Span; pub trait UserPrompt { type Output; - fn prompt(&self) -> Result; + fn ask(&self, span: Span) -> Result; - fn prompt_opt(&self) -> Result, LabeledError>; + fn ask_opt(&self, span: Span) -> Result, LabeledError>; } impl<'a> UserPrompt for dialoguer::Confirm<'a> { type Output = bool; - fn prompt(&self) -> Result { - self.interact().map_err(create_labeled_error) + fn ask(&self, span: Span) -> Result { + self.interact().map_err(|e| create_labeled_error(e, span)) } - fn prompt_opt(&self) -> Result, LabeledError> { - self.interact_opt().map_err(create_labeled_error) + fn ask_opt(&self, span: Span) -> Result, LabeledError> { + self.interact_opt() + .map_err(|e| create_labeled_error(e, span)) } } -fn create_labeled_error(e: io::Error) -> LabeledError { +impl<'a> UserPrompt for Password<'a> { + type Output = String; + + fn ask(&self, span: Span) -> Result { + self.interact().map_err(|e| create_labeled_error(e, span)) + } + + fn ask_opt(&self, span: Span) -> Result, LabeledError> { + self.ask(span).map(Option::Some) + } +} + +fn create_labeled_error(e: io::Error, span: Span) -> LabeledError { LabeledError { label: "Failed to prompt user".into(), msg: e.to_string(), - span: None, + span: Some(span), } } diff --git a/src/select.rs b/src/select.rs index 3b9ff54..4a053df 100644 --- a/src/select.rs +++ b/src/select.rs @@ -29,7 +29,7 @@ impl DialogPlugin { } if call.has_flag("abortable") { - if let Some(selection) = select.prompt_opt()? { + if let Some(selection) = select.ask_opt(call.head)? { let selected_item = options.remove(selection); Ok(Value::String { @@ -40,7 +40,7 @@ impl DialogPlugin { Ok(Value::Nothing { span: call.head }) } } else { - let selection = select.prompt()?; + let selection = select.ask(call.head)?; let selected_item = options.remove(selection); Ok(Value::String {