feat(Unicode): allows non-panicing on invalid unicode characters

This commit is contained in:
Kevin K 2015-09-21 21:58:25 -04:00
parent 3d0199da88
commit c5bf7ddc8c
2 changed files with 222 additions and 26 deletions

View file

@ -4,6 +4,8 @@ use std::io::{self, BufRead, BufWriter, Write};
use std::path::Path; use std::path::Path;
use std::process; use std::process;
use std::error::Error; use std::error::Error;
use std::ffi::OsStr;
use std::borrow::Borrow;
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
use yaml_rust::Yaml; use yaml_rust::Yaml;
@ -1879,6 +1881,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls /// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands /// the real parsing function for all subcommands
/// ///
/// # Panics
///
/// If any arguments contain invalid unicode characters. If this is not desired it is
/// recommended to use the `*_safe()` or `*_lossy()` versions of this method.
/// ///
/// # Example /// # Example
/// ///
@ -1893,6 +1899,23 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
self.get_matches_from(env::args()) self.get_matches_from(env::args())
} }
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands. Invalid unicode characters are replaced with
/// `U+FFFD REPLACEMENT CHARACTER`
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let matches = App::new("myprog")
/// // Args and options go here...
/// .get_matches();
/// ```
pub fn get_matches_lossy(self) -> ArgMatches<'ar, 'ar> {
// Start the parsing
self.get_matches_from_lossy(env::args_os())
}
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls /// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands /// the real parsing function for all subcommands
/// ///
@ -1911,7 +1934,29 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
/// ``` /// ```
pub fn get_matches_safe(self) -> Result<ArgMatches<'ar, 'ar>, ClapError> { pub fn get_matches_safe(self) -> Result<ArgMatches<'ar, 'ar>, ClapError> {
// Start the parsing // Start the parsing
self.get_matches_from_safe(env::args()) self.get_matches_from_safe(env::args_os())
}
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands. Invalid unicode characters are replaced with
/// `U+FFFD REPLACEMENT CHARACTER`
///
/// **NOTE:** This method should only be used when is absolutely necessary to handle errors
/// manually.
///
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// let matches = App::new("myprog")
/// // Args and options go here...
/// .get_matches_safe()
/// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) });
/// ```
pub fn get_matches_safe_lossy(self) -> Result<ArgMatches<'ar, 'ar>, ClapError> {
// Start the parsing
self.get_matches_from_safe_lossy(env::args_os())
} }
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls /// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
@ -1938,8 +1983,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
itr: I) itr: I)
-> ArgMatches<'ar, 'ar> -> ArgMatches<'ar, 'ar>
where I: IntoIterator<Item = T>, where I: IntoIterator<Item = T>,
T: AsRef<str> T: AsRef<OsStr> {
{
match self.get_matches_from_safe_borrow(itr) { match self.get_matches_from_safe_borrow(itr) {
Ok(m) => return m, Ok(m) => return m,
Err(e) => { Err(e) => {
@ -1955,6 +1999,47 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
} }
} }
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands. Invalid unicode characters are replaced with
/// `U+FFFD REPLACEMENT CHARACTER`
///
/// **NOTE:** The first argument will be parsed as the binary name.
///
/// **NOTE:** This method should only be used when absolutely necessary, such as needing to
/// parse arguments from something other than `std::env::args()`. If you are unsure, use
/// `App::get_matches()`
///
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"];
///
/// let matches = App::new("myprog")
/// // Args and options go here...
/// .get_matches_from(arg_vec);
/// ```
pub fn get_matches_from_lossy<I, T>(mut self,
itr: I)
-> ArgMatches<'ar, 'ar>
where I: IntoIterator<Item = T>,
T: AsRef<OsStr> {
match self.get_matches_from_safe_borrow_lossy(itr) {
Ok(m) => return m,
Err(e) => {
wlnerr!("{}", e.error);
if self.wait_on_error {
wlnerr!("\nPress [ENTER] / [RETURN] to continue...");
let mut s = String::new();
let i = io::stdin();
i.lock().read_line(&mut s).unwrap();
}
process::exit(1);
}
}
}
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls /// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands /// the real parsing function for all subcommands
/// ///
@ -1967,6 +2052,9 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
/// **NOTE:** This method should only be used when is absolutely necessary to handle errors /// **NOTE:** This method should only be used when is absolutely necessary to handle errors
/// manually. /// manually.
/// ///
/// **NOTE:** Invalid unicode characters will result in an `Err` with type
/// `ClapErrorType::InvalidUnicode`
///
/// ///
/// # Example /// # Example
/// ///
@ -1983,14 +2071,13 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
itr: I) itr: I)
-> Result<ArgMatches<'ar, 'ar>, ClapError> -> Result<ArgMatches<'ar, 'ar>, ClapError>
where I: IntoIterator<Item = T>, where I: IntoIterator<Item = T>,
T: AsRef<str> T: AsRef<OsStr> {
{
self.get_matches_from_safe_borrow(itr) self.get_matches_from_safe_borrow(itr)
} }
/// Starts the parsing process without consuming the `App` struct `self`. This is normally not /// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the desired functionality, instead prefer `App::get_matches_from_safe` which *does* /// the real parsing function for all subcommands. Invalid unicode characters are replaced with
/// consume `self`. /// `U+FFFD REPLACEMENT CHARACTER`
/// ///
/// **NOTE:** The first argument will be parsed as the binary name. /// **NOTE:** The first argument will be parsed as the binary name.
/// ///
@ -2001,23 +2088,32 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
/// **NOTE:** This method should only be used when is absolutely necessary to handle errors /// **NOTE:** This method should only be used when is absolutely necessary to handle errors
/// manually. /// manually.
/// ///
///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use clap::{App, Arg}; /// # use clap::{App, Arg};
/// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"]; /// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"];
/// ///
/// let mut app = App::new("myprog"); /// let matches = App::new("myprog")
/// // Args and options go here... /// // Args and options go here...
/// let matches = app.get_matches_from_safe_borrow(arg_vec) /// .get_matches_from_safe(arg_vec)
/// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) }); /// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) });
/// ``` /// ```
pub fn get_matches_from_safe_borrow<I, T>(&mut self, pub fn get_matches_from_safe_lossy<I, T>(mut self,
itr: I) itr: I)
-> Result<ArgMatches<'ar, 'ar>, ClapError>
where I: IntoIterator<Item = T>,
T: AsRef<OsStr> {
self._get_matches_from_safe_borrow(itr, true)
}
fn _get_matches_from_safe_borrow<I, T>(&mut self,
itr: I,
lossy: bool)
-> Result<ArgMatches<'ar, 'ar>, ClapError> -> Result<ArgMatches<'ar, 'ar>, ClapError>
where I: IntoIterator<Item = T>, where I: IntoIterator<Item = T>,
T: AsRef<str> T: AsRef<OsStr> {
{
// Verify all positional assertions pass // Verify all positional assertions pass
self.verify_positionals(); self.verify_positionals();
// If there are global arguments, we need to propgate them down to subcommands before // If there are global arguments, we need to propgate them down to subcommands before
@ -2044,13 +2140,83 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
} }
// do the real parsing // do the real parsing
if let Err(e) = self.get_matches_with(&mut matches, &mut it) { if let Err(e) = self.get_matches_with(&mut matches, &mut it, lossy) {
return Err(e); return Err(e);
} }
Ok(matches) Ok(matches)
} }
/// Starts the parsing process without consuming the `App` struct `self`. This is normally not
/// the desired functionality, instead prefer `App::get_matches_from_safe` which *does*
/// consume `self`.
///
/// **NOTE:** The first argument will be parsed as the binary name.
///
/// **NOTE:** This method should only be used when absolutely necessary, such as needing to
/// parse arguments from something other than `std::env::args()`. If you are unsure, use
/// `App::get_matches_safe()`
///
/// **NOTE:** This method should only be used when is absolutely necessary to handle errors
/// manually.
///
/// **NOTE:** Invalid unicode characters will result in an `Err` with type
/// `ClapErrorType::InvalidUnicode`
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"];
///
/// let mut app = App::new("myprog");
/// // Args and options go here...
/// let matches = app.get_matches_from_safe_borrow(arg_vec)
/// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) });
/// ```
pub fn get_matches_from_safe_borrow<I, T>(&mut self,
itr: I)
-> Result<ArgMatches<'ar, 'ar>, ClapError>
where I: IntoIterator<Item = T>,
T: AsRef<OsStr> {
self._get_matches_from_safe_borrow(itr, false)
}
/// Starts the parsing process without consuming the `App` struct `self`. This is normally not
/// the desired functionality, instead prefer `App::get_matches_from_safe` which *does*
/// consume `self`. Invalid unicode characters are replaced with `U+FFFD REPLACEMENT CHARACTER`
///
/// **NOTE:** The first argument will be parsed as the binary name.
///
/// **NOTE:** This method should only be used when absolutely necessary, such as needing to
/// parse arguments from something other than `std::env::args()`. If you are unsure, use
/// `App::get_matches_safe()`
///
/// **NOTE:** This method should only be used when is absolutely necessary to handle errors
/// manually.
///
/// **NOTE:** Invalid unicode characters will result in an `Err` with type
/// `ClapErrorType::InvalidUnicode`
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"];
///
/// let mut app = App::new("myprog");
/// // Args and options go here...
/// let matches = app.get_matches_from_safe_borrow(arg_vec)
/// .unwrap_or_else( |e| { panic!("An error occurs: {}", e) });
/// ```
pub fn get_matches_from_safe_borrow_lossy<I, T>(&mut self,
itr: I)
-> Result<ArgMatches<'ar, 'ar>, ClapError>
where I: IntoIterator<Item = T>,
T: AsRef<OsStr> {
self._get_matches_from_safe_borrow(itr, true)
}
fn verify_positionals(&mut self) { fn verify_positionals(&mut self) {
// Because you must wait until all arguments have been supplied, this is the first chance // Because you must wait until all arguments have been supplied, this is the first chance
@ -2137,11 +2303,11 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
// The actual parsing function // The actual parsing function
fn get_matches_with<I, T>(&mut self, fn get_matches_with<I, T>(&mut self,
matches: &mut ArgMatches<'ar, 'ar>, matches: &mut ArgMatches<'ar, 'ar>,
it: &mut I) it: &mut I,
lossy: bool)
-> Result<(), ClapError> -> Result<(), ClapError>
where I: Iterator<Item = T>, where I: Iterator<Item = T>,
T: AsRef<str> T: AsRef<OsStr> {
{
// First we create the `--help` and `--version` arguments and add them if necessary // First we create the `--help` and `--version` arguments and add them if necessary
self.create_help_and_version(); self.create_help_and_version();
@ -2151,7 +2317,20 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
let mut pos_counter = 1; let mut pos_counter = 1;
let mut val_counter = 0; let mut val_counter = 0;
while let Some(arg) = it.next() { while let Some(arg) = it.next() {
let arg_slice = arg.as_ref(); let arg_cow = match arg.as_ref().to_str() {
Some(s) => s.into(),
None => {
if !lossy {
return Err(ClapError{
error: format!("{} Invalid unicode character in one or more arguments",
Format::Error("error:")),
error_type: ClapErrorType::InvalidUnicode
});
}
arg.as_ref().to_string_lossy()
}
};
let arg_slice: &str = arg_cow.borrow();
let mut skip = false; let mut skip = false;
// we need to know if we're parsing a new argument, or the value of previous argument, // we need to know if we're parsing a new argument, or the value of previous argument,
@ -2186,7 +2365,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
if num == vals.len() as u8 && !opt.multiple { if num == vals.len() as u8 && !opt.multiple {
return Err(self.report_error(format!("The argument '{}' \ return Err(self.report_error(format!("The argument '{}' \
was found, but '{}' only expects {} values", was found, but '{}' only expects {} values",
Format::Warning(arg.as_ref()), Format::Warning(arg_slice),
Format::Warning(opt.to_string()), Format::Warning(opt.to_string()),
Format::Good(vals.len().to_string())), Format::Good(vals.len().to_string())),
ClapErrorType::InvalidValue, ClapErrorType::InvalidValue,
@ -2335,7 +2514,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
format!("The subcommand '{}' isn't valid\n\tDid you mean '{}' ?\n\n\ format!("The subcommand '{}' isn't valid\n\tDid you mean '{}' ?\n\n\
If you received this message in error, try \ If you received this message in error, try \
re-running with '{} {} {}'", re-running with '{} {} {}'",
Format::Warning(arg.as_ref()), Format::Warning(arg_slice),
Format::Good(candidate_subcommand), Format::Good(candidate_subcommand),
self.bin_name.clone().unwrap_or(self.name.clone()), self.bin_name.clone().unwrap_or(self.name.clone()),
Format::Good("--"), Format::Good("--"),
@ -2350,7 +2529,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
if self.positionals_idx.is_empty() { if self.positionals_idx.is_empty() {
return Err(self.report_error( return Err(self.report_error(
format!("Found argument '{}', but {} wasn't expecting any", format!("Found argument '{}', but {} wasn't expecting any",
Format::Warning(arg.as_ref()), Format::Warning(arg_slice),
self.bin_name.clone().unwrap_or(self.name.clone())), self.bin_name.clone().unwrap_or(self.name.clone())),
ClapErrorType::UnexpectedArgument, ClapErrorType::UnexpectedArgument,
App::get_args(matches))); App::get_args(matches)));
@ -2391,7 +2570,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
if vals.len() as u8 == num { if vals.len() as u8 == num {
return Err(self.report_error(format!("The argument '{}' \ return Err(self.report_error(format!("The argument '{}' \
was found, but '{}' wasn't expecting any more values", was found, but '{}' wasn't expecting any more values",
Format::Warning(arg.as_ref()), Format::Warning(arg_slice),
Format::Warning(p.to_string())), Format::Warning(p.to_string())),
ClapErrorType::TooMuchValues, ClapErrorType::TooMuchValues,
App::get_args(matches))); App::get_args(matches)));
@ -2481,7 +2660,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
} }
} else { } else {
return Err(self.report_error(format!("The argument '{}' was found, but '{}' \ return Err(self.report_error(format!("The argument '{}' was found, but '{}' \
wasn't expecting any", Format::Warning(arg.as_ref()), wasn't expecting any", Format::Warning(arg_slice),
self.bin_name.clone().unwrap_or(self.name.clone())), self.bin_name.clone().unwrap_or(self.name.clone())),
ClapErrorType::UnexpectedArgument, ClapErrorType::UnexpectedArgument,
App::get_args(matches))); App::get_args(matches)));
@ -2575,7 +2754,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
"" ""
}, },
sc.name.clone())); sc.name.clone()));
if let Err(e) = sc.get_matches_with(&mut new_matches, it) { if let Err(e) = sc.get_matches_with(&mut new_matches, it, lossy) {
e.exit(); e.exit();
} }
matches.subcommand = Some(Box::new(SubCommand { matches.subcommand = Some(Box::new(SubCommand {
@ -3554,5 +3733,4 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
None => (String::new(), None), None => (String::new(), None),
} }
} }
} }

View file

@ -178,6 +178,24 @@ pub enum ClapErrorType {
/// .get_matches_from_safe(vec!["", "--debug", "--debug"]); /// .get_matches_from_safe(vec!["", "--debug", "--debug"]);
/// ``` /// ```
UnexpectedMultipleUsage, UnexpectedMultipleUsage,
/// Error occurs when argument contains invalid unicode characters
///
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// # use std::os::unix::ffi::OsStringExt;
/// # use std::ffi::OsString;
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .short("u")
/// .takes_value(true))
/// .get_matches_from_safe(vec![OsString::from_vec(vec![0x20]),
/// OsString::from_vec(vec![0xE9])]);
/// assert!(result.is_err());
/// ```
InvalidUnicode
} }
/// Command line argument parser error /// Command line argument parser error