diff --git a/src/app/mod.rs b/src/app/mod.rs index 7690133a239..d15d91e2afc 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -8,7 +8,7 @@ mod help; // Std use std::borrow::Borrow; use std::env; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::fmt; use std::io::{self, BufRead, BufWriter, Write}; use std::path::Path; @@ -1525,6 +1525,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { fn num_vals(&self) -> Option { None } fn possible_vals(&self) -> Option<&[&'e str]> { None } fn validator(&self) -> Option<&Rc StdResult<(), String>>> { None } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { None } fn min_vals(&self) -> Option { None } fn short(&self) -> Option { None } fn long(&self) -> Option<&'e str> { None } diff --git a/src/app/parser.rs b/src/app/parser.rs index 4da3a0c9669..0a53d8ee7d3 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1531,6 +1531,11 @@ impl<'a, 'b> Parser<'a, 'b> return Err(Error::value_validation(Some(arg), e, self.color())); } } + if let Some(vtor) = arg.validator_os() { + if let Err(e) = vtor(val) { + return Err(Error::value_validation(Some(arg), (*e).to_string_lossy().to_string(), self.color())); + } + } if matcher.needs_more_vals(arg) { return Ok(Some(arg.name())); } diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 7446ea7f46d..0533fb450a8 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -1,6 +1,7 @@ // Std use std::rc::Rc; use std::fmt as std_fmt; +use std::ffi::{OsStr, OsString}; // Third Party use vec_map::VecMap; @@ -26,6 +27,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display { fn num_vals(&self) -> Option; fn possible_vals(&self) -> Option<&[&'e str]>; fn validator(&self) -> Option<&Rc Result<(), String>>>; + fn validator_os(&self) -> Option<&Rc Result<(), OsString>>>; fn short(&self) -> Option; fn long(&self) -> Option<&'e str>; fn val_delim(&self) -> Option; diff --git a/src/args/arg.rs b/src/args/arg.rs index 7f21ad7c174..6880296a514 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -1,6 +1,7 @@ #[cfg(feature = "yaml")] use std::collections::BTreeMap; use std::rc::Rc; +use std::ffi::{OsString, OsStr}; #[cfg(feature = "yaml")] use yaml_rust::Yaml; @@ -66,6 +67,8 @@ pub struct Arg<'a, 'b> #[doc(hidden)] pub validator: Option Result<(), String>>>, #[doc(hidden)] + pub validator_os: Option Result<(), OsString>>>, + #[doc(hidden)] pub overrides: Option>, #[doc(hidden)] pub settings: ArgFlags, @@ -97,6 +100,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> { max_vals: None, min_vals: None, validator: None, + validator_os: None, overrides: None, settings: ArgFlags::new(), val_delim: None, @@ -1908,6 +1912,37 @@ impl<'a, 'b> Arg<'a, 'b> { self } + ///Works identically to Validator but is intended to be used with non UTF-8 formatted strings. + /// # Examples + /// ```rust + /// # use clap::{App, Arg}; + ///fn has_ampersand(v: &OsStr) -> Result<(), String> { + /// if v.contains("&") { return Ok(()); } + /// Err(String::from("The value did not contain the required & sigil")) + /// } + /// let res = App::new("validators") + /// .arg(Arg::with_name("file") + /// .index(1) + /// .validator(has_ampersand)) + /// .get_matches_from_safe(vec![ + /// "validators", "Fish & chips" + /// ]); + /// assert!(res.is_ok()); + /// assert_eq!(res.unwrap().value_of("file"), Some("Fish & chips")); + /// ``` + /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + /// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html + /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html + /// [`Err(String)`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err + /// [`Rc`]: https://doc.rust-lang.org/std/rc/struct.Rc.html + pub fn validator_os(mut self, f: F) -> Self + where F: Fn(&OsStr) -> Result<(), OsString> + 'static + { + self.validator_os = Some(Rc::new(f)); + self + } + /// Specifies the *maximum* number of values are for this argument. For example, if you had a /// `-f ` argument where you wanted up to 3 'files' you would set `.max_values(3)`, and /// this argument would be satisfied if the user provided, 1, 2, or 3 values. @@ -2563,6 +2598,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> { val_names: a.val_names.clone(), groups: a.groups.clone(), validator: a.validator.clone(), + validator_os: a.validator_os.clone(), overrides: a.overrides.clone(), settings: a.settings, val_delim: a.val_delim, @@ -2591,6 +2627,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> { val_names: self.val_names.clone(), groups: self.groups.clone(), validator: self.validator.clone(), + validator_os: self.validator_os.clone(), overrides: self.overrides.clone(), settings: self.settings, val_delim: self.val_delim, diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs index fcec7aadcc5..5e6d66b738b 100644 --- a/src/args/arg_builder/flag.rs +++ b/src/args/arg_builder/flag.rs @@ -3,6 +3,7 @@ use std::convert::From; use std::fmt::{Display, Formatter, Result}; use std::rc::Rc; use std::result::Result as StdResult; +use std::ffi::{OsStr, OsString}; // Third Party use vec_map::VecMap; @@ -64,6 +65,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { fn num_vals(&self) -> Option { None } fn possible_vals(&self) -> Option<&[&'e str]> { None } fn validator(&self) -> Option<&Rc StdResult<(), String>>> { None } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { None } fn min_vals(&self) -> Option { None } fn short(&self) -> Option { self.s.short } fn long(&self) -> Option<&'e str> { self.s.long } diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs index 54c3f11f9ef..aa195658a1a 100644 --- a/src/args/arg_builder/option.rs +++ b/src/args/arg_builder/option.rs @@ -2,6 +2,7 @@ use std::fmt::{Display, Formatter, Result}; use std::rc::Rc; use std::result::Result as StdResult; +use std::ffi::{OsStr, OsString}; // Third Party use vec_map::VecMap; @@ -107,6 +108,9 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { fn validator(&self) -> Option<&Rc StdResult<(), String>>> { self.v.validator.as_ref() } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { + self.v.validator_os.as_ref() + } fn min_vals(&self) -> Option { self.v.min_vals } fn short(&self) -> Option { self.s.short } fn long(&self) -> Option<&'e str> { self.s.long } diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs index ec5af27b5e4..78a92f0eefb 100644 --- a/src/args/arg_builder/positional.rs +++ b/src/args/arg_builder/positional.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use std::fmt::{Display, Formatter, Result}; use std::rc::Rc; use std::result::Result as StdResult; +use std::ffi::{OsStr, OsString}; // Third Party use vec_map::VecMap; @@ -111,6 +112,9 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { fn validator(&self) -> Option<&Rc StdResult<(), String>>> { self.v.validator.as_ref() } + fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { + self.v.validator_os.as_ref() + } fn min_vals(&self) -> Option { self.v.min_vals } fn short(&self) -> Option { None } fn long(&self) -> Option<&'e str> { None } diff --git a/src/args/arg_builder/valued.rs b/src/args/arg_builder/valued.rs index eba045020fd..f4d7956298d 100644 --- a/src/args/arg_builder/valued.rs +++ b/src/args/arg_builder/valued.rs @@ -1,4 +1,5 @@ use std::rc::Rc; +use std::ffi::{OsStr, OsString}; use vec_map::VecMap; @@ -15,6 +16,7 @@ pub struct Valued<'a, 'b> pub max_vals: Option, pub min_vals: Option, pub validator: Option Result<(), String>>>, + pub validator_os: Option Result<(), OsString>>>, pub val_delim: Option, pub default_val: Option<&'a str>, } @@ -28,6 +30,7 @@ impl<'n, 'e> Default for Valued<'n, 'e> { max_vals: None, val_names: None, validator: None, + validator_os: None, val_delim: Some(','), default_val: None, } @@ -43,6 +46,7 @@ impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> { max_vals: a.max_vals, val_names: a.val_names.clone(), validator: a.validator.clone(), + validator_os: a.validator_os.clone(), val_delim: a.val_delim, default_val: a.default_val, };