Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow FnMut for validate #96

Merged
merged 4 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 0.8.0

### Enhancements

* `Input::validate_with` can take a `FnMut` (allowing multiple references)

### Breaking

* `Input::interact*` methods take `&mut self` instead of `&self`

## 0.7.0

### Enhancements
Expand Down
14 changes: 9 additions & 5 deletions examples/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ fn main() {

let mail: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Your email")
.validate_with(|input: &String| -> Result<(), &str> {
if input.contains('@') {
Ok(())
} else {
Err("This is not a mail address")
.validate_with({
let mut force = None;
move |input: &String| -> Result<(), &str> {
if input.contains('@') || force.as_ref().map_or(false, |old| old == input) {
Ok(())
} else {
force = Some(input.clone());
Err("This is not a mail address; type the same value again to force use")
}
}
})
.interact_text()
Expand Down
20 changes: 10 additions & 10 deletions src/prompts/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub struct Input<'a, T> {
initial_text: Option<String>,
theme: &'a dyn Theme,
permit_empty: bool,
validator: Option<Box<dyn Fn(&T) -> Option<String> + 'a>>,
validator: Option<Box<dyn FnMut(&T) -> Option<String> + 'a>>,
}

impl<'a, T> Default for Input<'a, T>
Expand Down Expand Up @@ -139,15 +139,15 @@ where
/// .interact()
/// .unwrap();
/// ```
pub fn validate_with<V>(&mut self, validator: V) -> &mut Input<'a, T>
pub fn validate_with<V>(&mut self, mut validator: V) -> &mut Input<'a, T>
where
V: Validator<T> + 'a,
T: 'a,
{
let old_validator_func = self.validator.take();
let mut old_validator_func = self.validator.take();

self.validator = Some(Box::new(move |value: &T| -> Option<String> {
if let Some(old) = old_validator_func.as_ref() {
if let Some(old) = old_validator_func.as_mut() {
if let Some(err) = old(value) {
return Some(err);
}
Expand All @@ -168,12 +168,12 @@ where
/// while [`interact`](#method.interact) allows virtually any character to be used e.g arrow keys.
///
/// The dialog is rendered on stderr.
pub fn interact_text(&self) -> io::Result<T> {
pub fn interact_text(&mut self) -> io::Result<T> {
self.interact_text_on(&Term::stderr())
}

/// Like [`interact_text`](#method.interact_text) but allows a specific terminal to be set.
pub fn interact_text_on(&self, term: &Term) -> io::Result<T> {
pub fn interact_text_on(&mut self, term: &Term) -> io::Result<T> {
let mut render = TermThemeRenderer::new(term, self.theme);

loop {
Expand Down Expand Up @@ -265,7 +265,7 @@ where

match input.parse::<T>() {
Ok(value) => {
if let Some(ref validator) = self.validator {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(&value) {
render.error(&err)?;
continue;
Expand Down Expand Up @@ -293,12 +293,12 @@ where
///
/// If the user confirms the result is `true`, `false` otherwise.
/// The dialog is rendered on stderr.
pub fn interact(&self) -> io::Result<T> {
pub fn interact(&mut self) -> io::Result<T> {
self.interact_on(&Term::stderr())
}

/// Like [`interact`](#method.interact) but allows a specific terminal to be set.
pub fn interact_on(&self, term: &Term) -> io::Result<T> {
pub fn interact_on(&mut self, term: &Term) -> io::Result<T> {
let mut render = TermThemeRenderer::new(term, self.theme);

loop {
Expand Down Expand Up @@ -336,7 +336,7 @@ where

match input.parse::<T>() {
Ok(value) => {
if let Some(ref validator) = self.validator {
if let Some(ref mut validator) = self.validator {
if let Some(err) = validator(&value) {
render.error(&err)?;
continue;
Expand Down
6 changes: 3 additions & 3 deletions src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ pub trait Validator<T> {
///
/// If this produces `Ok(())` then the value is used and parsed, if
/// an error is returned validation fails with that error.
fn validate(&self, input: &T) -> Result<(), Self::Err>;
fn validate(&mut self, input: &T) -> Result<(), Self::Err>;
}

impl<T, F: Fn(&T) -> Result<(), E>, E: Debug + Display> Validator<T> for F {
impl<T, F: FnMut(&T) -> Result<(), E>, E: Debug + Display> Validator<T> for F {
type Err = E;

fn validate(&self, input: &T) -> Result<(), Self::Err> {
fn validate(&mut self, input: &T) -> Result<(), Self::Err> {
self(input)
}
}