diff --git a/Cargo.toml b/Cargo.toml index 68d81d37..d986d6f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ clap_derive = { path = "./clap_derive", version = "=3.0.0-beta.4", optional = tr bitflags = "1.2" textwrap = { version = "0.14.0", default-features = false, features = [] } indexmap = "1.0" -os_str_bytes = { version = "3.0", features = ["raw"] } +os_str_bytes = "4.1" vec_map = "0.8" strsim = { version = "0.10", optional = true } yaml-rust = { version = "0.4.1", optional = true } diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index cbd7159a..e4d331a2 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -18,6 +18,7 @@ use std::{ }; // Third Party +use os_str_bytes::RawOsStr; #[cfg(feature = "yaml")] use yaml_rust::Yaml; @@ -27,7 +28,7 @@ use crate::{ mkeymap::MKeyMap, output::{fmt::Colorizer, Help, HelpWriter, Usage}, parse::{ArgMatcher, ArgMatches, Input, Parser}, - util::{safe_exit, termcolor::ColorChoice, ArgStr, Id, Key, USAGE_CODE}, + util::{safe_exit, termcolor::ColorChoice, Id, Key, USAGE_CODE}, Result as ClapResult, INTERNAL_ERROR_MSG, }; @@ -2791,7 +2792,7 @@ impl<'help> App<'help> { } /// Find a flag subcommand name by long flag or an alias - pub(crate) fn find_long_subcmd(&self, long: &ArgStr) -> Option<&str> { + pub(crate) fn find_long_subcmd(&self, long: &RawOsStr) -> Option<&str> { self.get_subcommands() .find(|sc| sc.long_flag_aliases_to(long)) .map(|sc| sc.get_name()) diff --git a/src/mkeymap.rs b/src/mkeymap.rs index b32b5df9..b810265f 100644 --- a/src/mkeymap.rs +++ b/src/mkeymap.rs @@ -1,6 +1,6 @@ use crate::{build::Arg, util::Id, INTERNAL_ERROR_MSG}; -use std::{ffi::OsString, iter::Iterator, ops::Index}; +use std::{ffi::OsStr, ffi::OsString, iter::Iterator, ops::Index}; #[derive(PartialEq, Eq, Debug, Clone)] pub(crate) struct Key { @@ -49,8 +49,8 @@ impl PartialEq<&str> for KeyType { } } -impl PartialEq for KeyType { - fn eq(&self, rhs: &OsString) -> bool { +impl PartialEq for KeyType { + fn eq(&self, rhs: &OsStr) -> bool { match self { KeyType::Long(l) => l == rhs, _ => false, @@ -86,7 +86,7 @@ impl<'help> MKeyMap<'help> { /// Find the arg have corresponding key in this map, we can search the key /// with u64(for positional argument), char(for short flag), &str and /// OsString (for long flag) - pub(crate) fn get(&self, key: &K) -> Option<&Arg<'help>> + pub(crate) fn get(&self, key: &K) -> Option<&Arg<'help>> where KeyType: PartialEq, { diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 19f69b61..1e896013 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -4,6 +4,9 @@ use std::{ ffi::{OsStr, OsString}, }; +// Third Party +use os_str_bytes::RawOsStr; + // Internal use crate::{ build::AppSettings as AS, @@ -16,7 +19,7 @@ use crate::{ parse::features::suggestions, parse::{ArgMatcher, SubCommand}, parse::{Validator, ValueType}, - util::{termcolor::ColorChoice, ArgStr, ChildGraph, Id}, + util::{termcolor::ColorChoice, ChildGraph, Id}, INTERNAL_ERROR_MSG, INVALID_UTF8, }; @@ -374,7 +377,7 @@ impl<'help, 'app> Parser<'help, 'app> { continue; } - let arg_os = ArgStr::new(arg_os); + let arg_os = RawOsStr::new(arg_os); debug!( "Parser::get_matches_with: Begin parsing '{:?}' ({:?})", arg_os, @@ -423,7 +426,7 @@ impl<'help, 'app> Parser<'help, 'app> { // pos_counter(which means current value cannot be a // positional argument with a value next to it), assume // current value matches the next arg. - let n = ArgStr::new(n); + let n = RawOsStr::new(n); self.is_new_arg(&n, p) || self.possible_subcommand(&n, valid_arg_found).is_some() } else { @@ -468,10 +471,10 @@ impl<'help, 'app> Parser<'help, 'app> { } } - if arg_os.starts_with("--") { + if let Some(long_arg) = arg_os.strip_prefix("--") { let parse_result = self.parse_long_arg( matcher, - &arg_os, + long_arg, &parse_state, &mut valid_arg_found, trailing_values, @@ -537,7 +540,7 @@ impl<'help, 'app> Parser<'help, 'app> { unreachable!() } } - } else if arg_os.starts_with("-") { + } else if let Some(short_arg) = arg_os.strip_prefix("-") { // Arg looks like a short flag, and not a possible number // Try to parse short args like normal, if AllowLeadingHyphen or @@ -545,7 +548,7 @@ impl<'help, 'app> Parser<'help, 'app> { // an error, and instead return Ok(None) let parse_result = self.parse_short_arg( matcher, - &arg_os, + short_arg, &parse_state, pos_counter, &mut valid_arg_found, @@ -626,7 +629,7 @@ impl<'help, 'app> Parser<'help, 'app> { // get the option so we can check the settings let parse_result = self.add_val_to_arg( &self.app[id], - arg_os, + &arg_os, matcher, ValueType::CommandLine, true, @@ -645,7 +648,7 @@ impl<'help, 'app> Parser<'help, 'app> { if let Some(p) = self.app.args.get(&pos_counter) { if p.is_set(ArgSettings::Last) && !trailing_values { return Err(ClapError::unknown_argument( - arg_os.to_string_lossy().to_string(), + arg_os.to_str_lossy().into_owned(), None, Usage::new(self).create_usage_with_title(&[]), self.app.color(), @@ -663,7 +666,7 @@ impl<'help, 'app> Parser<'help, 'app> { let append = self.arg_have_val(matcher, p); self.add_val_to_arg( p, - arg_os, + &arg_os, matcher, ValueType::CommandLine, append, @@ -768,7 +771,7 @@ impl<'help, 'app> Parser<'help, 'app> { fn match_arg_error( &self, - arg_os: &ArgStr, + arg_os: &RawOsStr, valid_arg_found: bool, trailing_values: bool, ) -> ClapError { @@ -777,14 +780,14 @@ impl<'help, 'app> Parser<'help, 'app> { // If the arg matches a subcommand name, or any of its aliases (if defined) if self.possible_subcommand(arg_os, valid_arg_found).is_some() { return ClapError::unnecessary_double_dash( - arg_os.to_string_lossy().to_string(), + arg_os.to_str_lossy().into_owned(), Usage::new(self).create_usage_with_title(&[]), self.app.color(), ); } } let candidates = - suggestions::did_you_mean(&arg_os.to_string_lossy(), self.app.all_subcommand_names()); + suggestions::did_you_mean(&arg_os.to_str_lossy(), self.app.all_subcommand_names()); // If the argument looks like a subcommand. if !candidates.is_empty() { let candidates: Vec<_> = candidates @@ -792,7 +795,7 @@ impl<'help, 'app> Parser<'help, 'app> { .map(|candidate| format!("'{}'", candidate)) .collect(); return ClapError::invalid_subcommand( - arg_os.to_string_lossy().to_string(), + arg_os.to_str_lossy().into_owned(), candidates.join(" or "), self.app .bin_name @@ -806,7 +809,7 @@ impl<'help, 'app> Parser<'help, 'app> { // If the argument must be a subcommand. if !self.app.has_args() || self.is_set(AS::InferSubcommands) && self.app.has_subcommands() { return ClapError::unrecognized_subcommand( - arg_os.to_string_lossy().to_string(), + arg_os.to_str_lossy().into_owned(), self.app .bin_name .as_ref() @@ -816,7 +819,7 @@ impl<'help, 'app> Parser<'help, 'app> { ); } ClapError::unknown_argument( - arg_os.to_string_lossy().to_string(), + arg_os.to_str_lossy().into_owned(), None, Usage::new(self).create_usage_with_title(&[]), self.app.color(), @@ -835,7 +838,7 @@ impl<'help, 'app> Parser<'help, 'app> { } // Checks if the arg matches a subcommand name, or any of its aliases (if defined) - fn possible_subcommand(&self, arg_os: &ArgStr, valid_arg_found: bool) -> Option<&str> { + fn possible_subcommand(&self, arg_os: &RawOsStr, valid_arg_found: bool) -> Option<&str> { debug!("Parser::possible_subcommand: arg={:?}", arg_os); if !(self.is_set(AS::ArgsNegateSubcommands) && valid_arg_found) { @@ -845,7 +848,7 @@ impl<'help, 'app> Parser<'help, 'app> { let v = self .app .all_subcommand_names() - .filter(|s| arg_os.is_prefix_of(s)) + .filter(|s| RawOsStr::from_str(s).starts_with_os(arg_os)) .collect::>(); if v.len() == 1 { @@ -863,7 +866,7 @@ impl<'help, 'app> Parser<'help, 'app> { } // Checks if the arg matches a long flag subcommand name, or any of its aliases (if defined) - fn possible_long_flag_subcommand(&self, arg_os: &ArgStr) -> Option<&str> { + fn possible_long_flag_subcommand(&self, arg_os: &RawOsStr) -> Option<&str> { debug!("Parser::possible_long_flag_subcommand: arg={:?}", arg_os); if self.is_set(AS::InferSubcommands) { let options = self @@ -871,12 +874,12 @@ impl<'help, 'app> Parser<'help, 'app> { .get_subcommands() .fold(Vec::new(), |mut options, sc| { if let Some(long) = sc.long_flag { - if arg_os.is_prefix_of(long) { + if RawOsStr::from_str(long).starts_with_os(arg_os) { options.push(long); } options.extend( sc.get_all_aliases() - .filter(|alias| arg_os.is_prefix_of(alias)), + .filter(|alias| RawOsStr::from_str(alias).starts_with_os(arg_os)), ) } options @@ -885,7 +888,7 @@ impl<'help, 'app> Parser<'help, 'app> { return Some(options[0]); } - for sc in &options { + for sc in options { if sc == arg_os { return Some(sc); } @@ -930,7 +933,7 @@ impl<'help, 'app> Parser<'help, 'app> { } } else { return Err(ClapError::unrecognized_subcommand( - cmd.to_string_lossy().to_string(), + cmd.to_string_lossy().into_owned(), self.app .bin_name .as_ref() @@ -968,7 +971,7 @@ impl<'help, 'app> Parser<'help, 'app> { Err(parser.help_err(self.app.is_set(AS::UseLongFormatForHelpSubcommand))) } - fn is_new_arg(&self, next: &ArgStr, current_positional: &Arg) -> bool { + fn is_new_arg(&self, next: &RawOsStr, current_positional: &Arg) -> bool { debug!( "Parser::is_new_arg: {:?}:{:?}", next, current_positional.name @@ -977,7 +980,7 @@ impl<'help, 'app> Parser<'help, 'app> { if self.is_set(AS::AllowLeadingHyphen) || self.app[¤t_positional.id].is_set(ArgSettings::AllowHyphenValues) || (self.is_set(AS::AllowNegativeNumbers) - && next.to_string_lossy().parse::().is_ok()) + && next.to_str_lossy().parse::().is_ok()) { // If allow hyphen, this isn't a new arg. debug!("Parser::is_new_arg: Allow hyphen"); @@ -990,7 +993,7 @@ impl<'help, 'app> Parser<'help, 'app> { debug!("Parser::is_new_arg: - found"); // If this is a short flag, this is a new arg. But a singe '-' by // itself is a value and typically means "stdin" on unix systems. - next.len() != 1 + next.raw_len() != 1 } else { debug!("Parser::is_new_arg: value"); // Nothing special, this is a value. @@ -1092,7 +1095,7 @@ impl<'help, 'app> Parser<'help, 'app> { // Retrieves the names of all args the user has supplied thus far, except required ones // because those will be listed in self.required - fn check_for_help_and_version_str(&self, arg: &ArgStr) -> Option { + fn check_for_help_and_version_str(&self, arg: &RawOsStr) -> Option { debug!("Parser::check_for_help_and_version_str"); debug!( "Parser::check_for_help_and_version_str: Checking if --{:?} is help or version...", @@ -1172,7 +1175,7 @@ impl<'help, 'app> Parser<'help, 'app> { fn parse_long_arg( &mut self, matcher: &mut ArgMatcher, - full_arg: &ArgStr, + long_arg: &RawOsStr, parse_state: &ParseState, valid_arg_found: &mut bool, trailing_values: bool, @@ -1191,12 +1194,11 @@ impl<'help, 'app> Parser<'help, 'app> { debug!("Parser::parse_long_arg: cur_idx:={}", self.cur_idx.get()); debug!("Parser::parse_long_arg: Does it contain '='..."); - let long_arg = full_arg.trim_start_n_matches(2, b'-'); if long_arg.is_empty() { return ParseResult::NoArg; } - let (arg, val) = if full_arg.contains_byte(b'=') { - let (p0, p1) = long_arg.split_at_byte(b'='); + let (arg, val) = if let Some(index) = long_arg.find("=") { + let (p0, p1) = long_arg.split_at(index); debug!("Yes '{:?}'", p1); (p0, Some(p1)) } else { @@ -1204,20 +1206,20 @@ impl<'help, 'app> Parser<'help, 'app> { (long_arg, None) }; - let opt = if let Some(opt) = self.app.args.get(&arg.to_os_string()) { + let opt = if let Some(opt) = self.app.args.get(&*arg.to_os_str()) { debug!( "Parser::parse_long_arg: Found valid opt or flag '{}'", opt.to_string() ); Some(opt) } else if self.is_set(AS::InferLongArgs) { - let arg_str = arg.to_string_lossy(); + let arg_str = arg.to_str_lossy(); self.app.args.args().find(|a| { a.long - .map_or(false, |long| long.starts_with(arg_str.as_ref())) + .map_or(false, |long| long.starts_with(&*arg_str)) || a.aliases .iter() - .any(|(alias, _)| alias.starts_with(arg_str.as_ref())) + .any(|(alias, _)| alias.starts_with(&*arg_str)) }) } else { None @@ -1231,7 +1233,7 @@ impl<'help, 'app> Parser<'help, 'app> { "Parser::parse_long_arg: Found an opt with value '{:?}'", &val ); - self.parse_opt(&val, opt, matcher, trailing_values) + self.parse_opt(val, opt, matcher, trailing_values) } else if let Some(rest) = val { debug!( "Parser::parse_long_arg: Got invalid literal `{:?}`", @@ -1248,7 +1250,7 @@ impl<'help, 'app> Parser<'help, 'app> { .collect(); ParseResult::UnneededAttachedValue { - rest: rest.to_string_lossy().to_string(), + rest: rest.to_str_lossy().into_owned(), used, arg: opt.to_string(), } @@ -1264,7 +1266,7 @@ impl<'help, 'app> Parser<'help, 'app> { ParseResult::MaybeHyphenValue } else { ParseResult::NoMatchingArg { - arg: arg.to_string_lossy().to_string(), + arg: arg.to_str_lossy().into_owned(), } } } @@ -1272,16 +1274,15 @@ impl<'help, 'app> Parser<'help, 'app> { fn parse_short_arg( &mut self, matcher: &mut ArgMatcher, - full_arg: &ArgStr, + short_arg: &RawOsStr, parse_state: &ParseState, // change this to possible pos_arg when removing the usage of &mut Parser. pos_counter: usize, valid_arg_found: &mut bool, trailing_values: bool, ) -> ParseResult { - debug!("Parser::parse_short_arg: full_arg={:?}", full_arg); - let arg_os = full_arg.trim_start_matches(b'-'); - let arg = arg_os.to_string_lossy(); + debug!("Parser::parse_short_arg: short_arg={:?}", short_arg); + let arg = short_arg.to_str_lossy(); if (self.is_set(AS::AllowNegativeNumbers) && arg.parse::().is_ok()) || (self.is_set(AS::AllowLeadingHyphen) @@ -1332,24 +1333,12 @@ impl<'help, 'app> Parser<'help, 'app> { } // Check for trailing concatenated value - let i = arg_os.split(c).next().expect(INTERNAL_ERROR_MSG).len() + c.len_utf8(); + let val = short_arg.split_once(c).expect(INTERNAL_ERROR_MSG).1; debug!( - "Parser::parse_short_arg:iter:{}: i={}, arg_os={:?}", - c, i, arg_os + "Parser::parse_short_arg:iter:{}: val={:?} (bytes), val={:?} (ascii), rest={:?}, arg_os={:?}", + c, val, val.as_raw_bytes(), arg_os ); - let val = if i != arg_os.len() { - // This is always a valid place to split, because the separator is UTF-8. - let val = arg_os.split_at_unchecked(i).1; - debug!( - "Parser::parse_short_arg:iter:{}: val={:?} (bytes), val={:?} (ascii)", - c, - val.as_raw_bytes(), - val - ); - Some(val) - } else { - None - }; + let val = Some(val).filter(|v| !v.is_empty()); // Default to "we're expecting a value later". // @@ -1358,7 +1347,7 @@ impl<'help, 'app> Parser<'help, 'app> { // // e.g. `-xvf`, when RequireEquals && x.min_vals == 0, we don't // consume the `vf`, even if it's provided as value. - match self.parse_opt(&val, opt, matcher, trailing_values) { + match self.parse_opt(val, opt, matcher, trailing_values) { ParseResult::AttachedValueNotConsumed => continue, x => return x, } @@ -1393,7 +1382,7 @@ impl<'help, 'app> Parser<'help, 'app> { fn parse_opt( &self, - attached_value: &Option, + attached_value: Option<&RawOsStr>, opt: &Arg<'help>, matcher: &mut ArgMatcher, trailing_values: bool, @@ -1435,8 +1424,8 @@ impl<'help, 'app> Parser<'help, 'app> { } } } else if let Some(fv) = attached_value { - let v = fv.trim_start_n_matches(1, b'='); - debug!("Found - {:?}, len: {}", v, v.len()); + let v = fv.strip_prefix("=").unwrap_or(fv); + debug!("Found - {:?}, len: {}", v, v.raw_len()); debug!( "Parser::parse_opt: {:?} contains '='...{:?}", fv, @@ -1466,7 +1455,7 @@ impl<'help, 'app> Parser<'help, 'app> { fn add_val_to_arg( &self, arg: &Arg<'help>, - val: ArgStr, + val: &RawOsStr, matcher: &mut ArgMatcher, ty: ValueType, append: bool, @@ -1495,7 +1484,7 @@ impl<'help, 'app> Parser<'help, 'app> { }; self.add_multiple_vals_to_arg( arg, - vals.into_iter().map(|x| x.into_os_string()), + vals.into_iter().map(|x| x.to_os_str().into_owned()), matcher, ty, append, @@ -1503,7 +1492,7 @@ impl<'help, 'app> Parser<'help, 'app> { // If there was a delimiter used or we must use the delimiter to // separate the values or no more vals is needed, we're not // looking for more values. - return if val.contains_char(delim) + return if val.contains(delim) || arg.is_set(ArgSettings::RequireDelimiter) || !matcher.needs_more_vals(arg) { @@ -1518,7 +1507,7 @@ impl<'help, 'app> Parser<'help, 'app> { return ParseResult::ValuesDone; } } - self.add_single_val_to_arg(arg, val.to_os_string(), matcher, ty, append); + self.add_single_val_to_arg(arg, val.to_os_str().into_owned(), matcher, ty, append); if matcher.needs_more_vals(arg) { ParseResult::Opt(arg.id.clone()) } else { @@ -1686,7 +1675,7 @@ impl<'help, 'app> Parser<'help, 'app> { if let Some(default) = default { self.add_val_to_arg( arg, - ArgStr::new(default), + &RawOsStr::new(default), matcher, ty, false, @@ -1782,7 +1771,7 @@ impl<'help, 'app> Parser<'help, 'app> { debug!("Parser::add_env: Checking arg `{}`", a); if let Some((_, Some(ref val))) = a.env { - let val = ArgStr::new(val); + let val = RawOsStr::new(val); if a.is_set(ArgSettings::TakesValue) { debug!( @@ -1791,7 +1780,7 @@ impl<'help, 'app> Parser<'help, 'app> { ); self.add_val_to_arg( a, - val, + &val, matcher, ValueType::EnvVariable, false, @@ -1813,7 +1802,7 @@ impl<'help, 'app> Parser<'help, 'app> { } debug!("Parser::add_env: Found a flag with value `{:?}`", val); - let predicate = str_to_bool(val.to_string_lossy()); + let predicate = str_to_bool(val.to_str_lossy()); debug!("Parser::add_env: Found boolean literal `{}`", predicate); if predicate { matcher.add_index_to(&a.id, self.cur_idx.get(), ValueType::EnvVariable); diff --git a/src/parse/validator.rs b/src/parse/validator.rs index 7dac71dc..294a3911 100644 --- a/src/parse/validator.rs +++ b/src/parse/validator.rs @@ -122,7 +122,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> { .cloned() .collect(); return Err(Error::invalid_value( - val_str.to_string(), + val_str.into_owned(), &arg.possible_vals, arg, Usage::new(self.p).create_usage_with_title(&used), @@ -149,7 +149,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> { debug!("error"); return Err(Error::value_validation( arg.to_string(), - val.to_string_lossy().to_string(), + val.to_string_lossy().into_owned(), e, self.p.app.color(), )); diff --git a/src/util/argstr.rs b/src/util/argstr.rs deleted file mode 100644 index e1fcb540..00000000 --- a/src/util/argstr.rs +++ /dev/null @@ -1,260 +0,0 @@ -use std::{ - borrow::Cow, - ffi::{OsStr, OsString}, - fmt::{self, Debug}, - str, -}; - -use os_str_bytes::{raw, OsStrBytes}; - -#[derive(PartialEq, Eq)] -pub(crate) struct ArgStr<'a>(Cow<'a, [u8]>); - -impl<'a> ArgStr<'a> { - pub(crate) fn new(s: &'a OsStr) -> Self { - Self(s.to_raw_bytes()) - } - - pub(crate) fn starts_with(&self, s: &str) -> bool { - self.0.starts_with(s.as_bytes()) - } - - pub(crate) fn is_prefix_of(&self, s: &str) -> bool { - raw::starts_with(s, &self.0) - } - - fn to_borrowed(&'a self) -> Self { - Self(Cow::Borrowed(&self.0)) - } - - pub(crate) fn contains_byte(&self, byte: u8) -> bool { - assert!(byte.is_ascii()); - - self.0.contains(&byte) - } - - pub(crate) fn contains_char(&self, ch: char) -> bool { - let mut bytes = [0; 4]; - let bytes = ch.encode_utf8(&mut bytes).as_bytes(); - for i in 0..self.0.len().saturating_sub(bytes.len() - 1) { - if self.0[i..].starts_with(bytes) { - return true; - } - } - false - } - - pub(crate) fn split_at_byte(&self, byte: u8) -> (ArgStr, ArgStr) { - assert!(byte.is_ascii()); - - if let Some(i) = self.0.iter().position(|&x| x == byte) { - self.split_at_unchecked(i) - } else { - (self.to_borrowed(), Self(Cow::Borrowed(&[]))) - } - } - - pub(crate) fn trim_start_matches(&'a self, byte: u8) -> ArgStr { - assert!(byte.is_ascii()); - - if let Some(i) = self.0.iter().position(|x| x != &byte) { - Self(Cow::Borrowed(&self.0[i..])) - } else { - Self(Cow::Borrowed(&[])) - } - } - - // Like `trim_start_matches`, but trims no more than `n` matches - pub(crate) fn trim_start_n_matches(&self, n: usize, ch: u8) -> ArgStr { - assert!(ch.is_ascii()); - - let i = self.0.iter().take(n).take_while(|c| **c == ch).count(); - - self.split_at_unchecked(i).1 - } - - pub(crate) fn split_at_unchecked(&'a self, i: usize) -> (ArgStr, ArgStr) { - ( - Self(Cow::Borrowed(&self.0[..i])), - Self(Cow::Borrowed(&self.0[i..])), - ) - } - - pub(crate) fn split(&self, ch: char) -> ArgSplit<'_> { - let mut sep = [0; 4]; - ArgSplit { - sep_len: ch.encode_utf8(&mut sep).as_bytes().len(), - sep, - val: &self.0, - pos: 0, - } - } - - #[allow(dead_code)] - pub(crate) fn as_raw_bytes(&self) -> &[u8] { - &self.0 - } - - pub(crate) fn len(&self) -> usize { - self.0.len() - } - - #[allow(dead_code)] - pub(crate) fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub(crate) fn to_str(&self) -> Option<&str> { - str::from_utf8(&self.0).ok() - } - - pub(crate) fn to_string_lossy(&self) -> Cow<'_, str> { - String::from_utf8_lossy(&self.0) - } - - pub(crate) fn to_os_string(&self) -> OsString { - self.to_borrowed().into_os_string() - } - - pub(crate) fn into_os_string(self) -> OsString { - OsStr::from_raw_bytes(self.0).unwrap().into_owned() - } -} - -impl<'a> Debug for ArgStr<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.to_string_lossy()) - } -} - -impl<'a> PartialEq for ArgStr<'a> { - fn eq(&self, other: &str) -> bool { - self.0 == other.as_bytes() - } -} - -impl<'a> PartialEq<&str> for ArgStr<'a> { - fn eq(&self, other: &&str) -> bool { - self.eq(*other) - } -} - -impl<'a> PartialEq> for str { - fn eq(&self, other: &ArgStr<'a>) -> bool { - other.eq(self) - } -} - -impl<'a> PartialEq> for &str { - fn eq(&self, other: &ArgStr<'a>) -> bool { - other.eq(self) - } -} - -#[derive(Clone, Debug)] -pub(crate) struct ArgSplit<'a> { - sep: [u8; 4], - sep_len: usize, - val: &'a [u8], - pos: usize, -} - -impl<'a> Iterator for ArgSplit<'a> { - type Item = ArgStr<'a>; - - fn next(&mut self) -> Option> { - debug!("ArgSplit::next: self={:?}", self); - - if self.pos == self.val.len() { - return None; - } - let start = self.pos; - while self.pos < self.val.len() { - if self.val[self.pos..].starts_with(&self.sep[..self.sep_len]) { - let arg = ArgStr(Cow::Borrowed(&self.val[start..self.pos])); - self.pos += self.sep_len; - return Some(arg); - } - self.pos += 1; - } - Some(ArgStr(Cow::Borrowed(&self.val[start..]))) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - #[rustfmt::skip] - fn test_trim_start_matches() { - let raw = OsString::from("hello? world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_matches(b'-'); - assert_eq!(trimmed, a); - - let raw = OsString::from("------------hello? world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_matches(b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("hello? world"))); - - let raw = OsString::from("------------hel-lo? -world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_matches(b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world"))); - - let raw = OsString::from("hel-lo? -world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_matches(b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world"))); - - let raw = OsString::from(""); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_matches(b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from(""))); - } - - #[test] - #[rustfmt::skip] - fn test_trim_start_n_matches() { - let raw = OsString::from("hello? world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(2, b'-'); - assert_eq!(trimmed, a); - - let raw = OsString::from("------------hello? world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(2, b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("----------hello? world"))); - - let raw = OsString::from("------------hello? world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(1000, b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("hello? world"))); - - let raw = OsString::from("------------hel-lo? -world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(2, b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("----------hel-lo? -world"))); - - let raw = OsString::from("-hel-lo? -world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(5, b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world"))); - - let raw = OsString::from("hel-lo? -world"); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(10, b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from("hel-lo? -world"))); - - let raw = OsString::from(""); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(10, b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from(""))); - - let raw = OsString::from(""); - let a = ArgStr::new(&raw); - let trimmed = a.trim_start_n_matches(0, b'-'); - assert_eq!(trimmed, ArgStr::new(&OsString::from(""))); - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs index a3e9544e..020f57d1 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,6 +1,5 @@ #![allow(clippy::single_component_path_imports)] -mod argstr; mod fnv; mod graph; mod id; @@ -11,7 +10,7 @@ pub use self::fnv::Key; #[cfg(feature = "env")] pub(crate) use self::str_to_bool::str_to_bool; -pub(crate) use self::{argstr::ArgStr, graph::ChildGraph, id::Id}; +pub(crate) use self::{graph::ChildGraph, id::Id}; pub(crate) use vec_map::VecMap; #[cfg(feature = "color")]