Skip to content

Commit

Permalink
tail: simplify obsolete parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
bbara committed Feb 24, 2023
1 parent 1cb710e commit 394d415
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 98 deletions.
102 changes: 43 additions & 59 deletions src/uu/tail/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,39 +354,32 @@ impl Settings {

VerificationResult::Ok
}

pub fn is_default(&self) -> bool {
let default = Self::default();
self.max_unchanged_stats == default.max_unchanged_stats
&& self.sleep_sec == default.sleep_sec
&& self.follow == default.follow
&& self.mode == default.mode
&& self.pid == default.pid
&& self.retry == default.retry
&& self.use_polling == default.use_polling
&& (self.verbose == default.verbose || self.inputs.len() > 1)
&& self.presume_input_pipe == default.presume_input_pipe
}
}

pub fn parse_obsolete(args: &str) -> UResult<Option<parse::ObsoleteArgs>> {
match parse::parse_obsolete(args) {
Some(Ok(args)) => Ok(Some(args)),
pub fn parse_obsolete(arg: &OsString, input: Option<&OsString>) -> UResult<Option<Settings>> {
match parse::parse_obsolete(arg) {
Some(Ok(args)) => Ok(Some(Settings::from_obsolete_args(&args, input))),
None => Ok(None),
Some(Err(e)) => Err(USimpleError::new(
1,
match e {
parse::ParseError::OutOfRange => format!(
"invalid number: {}: Numerical result out of range",
args.quote()
),
parse::ParseError::Overflow => format!("invalid number: {}", args.quote()),
parse::ParseError::Context => format!(
"option used in invalid context -- {}",
args.chars().nth(1).unwrap_or_default()
),
},
)),
Some(Err(e)) => {
let arg_str = arg.to_string_lossy();
Err(USimpleError::new(
1,
match e {
parse::ParseError::OutOfRange => format!(
"invalid number: {}: Numerical result out of range",
arg_str.quote()
),
parse::ParseError::Overflow => format!("invalid number: {}", arg_str.quote()),
parse::ParseError::Context => format!(
"option used in invalid context -- {}",
arg_str.chars().nth(1).unwrap_or_default()
),
parse::ParseError::InvalidEncoding => {
format!("bad argument encoding: '{arg_str}'")
}
},
))
}
}
}

Expand Down Expand Up @@ -416,39 +409,29 @@ fn parse_num(src: &str) -> Result<Signum, ParseSizeError> {

pub fn parse_args(args: impl uucore::Args) -> UResult<Settings> {
let args_vec: Vec<OsString> = args.collect();
let clap_result = match uu_app().try_get_matches_from(args_vec.clone()) {
Ok(matches) => {
let settings = Settings::from(&matches)?;
if !settings.is_default() {
// non-default settings can't have obsolete arguments
return Ok(settings);
}
Ok(settings)
}
let clap_args = uu_app().try_get_matches_from(args_vec.clone());
let clap_result = match clap_args {
Ok(matches) => Ok(Settings::from(&matches)?),
Err(err) => Err(err.into()),
};

// clap parsing failed or resulted to default -> check for obsolete/deprecated args
// argv[0] is always present
let second = match args_vec.get(1) {
Some(second) => second,
None => return clap_result,
};
let second_str = match second.to_str() {
Some(second_str) => second_str,
None => {
let invalid_string = second.to_string_lossy();
return Err(USimpleError::new(
1,
format!("bad argument encoding: '{invalid_string}'"),
));
// clap misinterprets obsolete args as first input (starting with +) or Err
if let Ok(settings) = clap_result.as_ref() {
// Settings::from() ensures we always have at least one input (STDIN as fallback)
let input = settings.inputs.get(0).unwrap();
if !input.display_name.starts_with('+') {
return clap_result;
}
};
match parse_obsolete(second_str)? {
Some(obsolete_args) => Ok(Settings::from_obsolete_args(
&obsolete_args,
args_vec.get(2),
)),
}

// argv[0] is always present, argv[1] might be obsolete arguments
// argv[2] might contain file, argv[3] isn't allowed in obsolete mode
if args_vec.len() != 2 && args_vec.len() != 3 {
return clap_result;
}
let second = args_vec.get(1).unwrap();
match parse_obsolete(second, args_vec.get(2))? {
Some(settings) => Ok(settings),
None => clap_result,
}
}
Expand Down Expand Up @@ -483,6 +466,7 @@ pub fn uu_app() -> Command {
.num_args(0..=1)
.require_equals(true)
.value_parser(["descriptor", "name"])
.overrides_with(options::FOLLOW)
.help("Print the file as it grows"),
)
.arg(
Expand Down
120 changes: 81 additions & 39 deletions src/uu/tail/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.

use std::ffi::OsString;

#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub struct ObsoleteArgs {
pub num: u64,
Expand All @@ -27,46 +29,62 @@ pub enum ParseError {
OutOfRange,
Overflow,
Context,
InvalidEncoding,
}
/// Parses obsolete syntax
/// tail -\[NUM\]\[bl\]\[f\] and tail +\[NUM\]\[bcl\]\[f\] // spell-checker:disable-line
pub fn parse_obsolete(src: &str) -> Option<Result<ObsoleteArgs, ParseError>> {
let mut chars = src.chars();
let sign = chars.next()?;
if sign != '+' && sign != '-' {
pub fn parse_obsolete(src: &OsString) -> Option<Result<ObsoleteArgs, ParseError>> {
let mut rest = match src.to_str() {
Some(src) => src,
None => return Some(Err(ParseError::InvalidEncoding)),
};
let sign = if let Some(r) = rest.strip_prefix('-') {
rest = r;
'-'
} else if let Some(r) = rest.strip_prefix('+') {
rest = r;
'+'
} else {
return None;
}
};

let numbers: String = chars.clone().take_while(|&c| c.is_ascii_digit()).collect();
let has_num = !numbers.is_empty();
let end_num = rest
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(rest.len());
let has_num = !rest[..end_num].is_empty();
let num: u64 = if has_num {
if let Ok(num) = numbers.parse() {
if let Ok(num) = rest[..end_num].parse() {
num
} else {
return Some(Err(ParseError::OutOfRange));
}
} else {
10
};
rest = &rest[end_num..];

let mut follow = false;
let mut mode = 'l';
let mut first_char = true;
for char in chars.skip_while(|&c| c.is_ascii_digit()) {
if !has_num && first_char && sign == '-' && (char == 'c' || char == 'f') {
// special cases: -c, -f should be handled by clap (are ambiguous)
return None;
} else if char == 'f' {
follow = true;
} else if first_char && (char == 'b' || char == 'c' || char == 'l') {
mode = char;
} else if has_num && sign == '-' {
let mode = if let Some(r) = rest.strip_prefix('l') {
rest = r;
'l'
} else if let Some(r) = rest.strip_prefix('c') {
rest = r;
'c'
} else if let Some(r) = rest.strip_prefix('b') {
rest = r;
'b'
} else {
'l'
};

let follow = rest.contains('f');
if !rest.chars().all(|f| f == 'f') {
// GNU allows an arbitrary amount of following fs, but nothing else
if sign == '-' && has_num {
return Some(Err(ParseError::Context));
} else {
return None;
}
first_char = false;
return None;
}

let multiplier = if mode == 'b' { 512 } else { 1 };
let num = match num.checked_mul(multiplier) {
Some(n) => n,
Expand All @@ -87,7 +105,7 @@ mod tests {
#[test]
fn test_parse_numbers_obsolete() {
assert_eq!(
parse_obsolete("+2c"),
parse_obsolete(&OsString::from("+2c")),
Some(Ok(ObsoleteArgs {
num: 2,
plus: true,
Expand All @@ -96,7 +114,7 @@ mod tests {
}))
);
assert_eq!(
parse_obsolete("-5"),
parse_obsolete(&OsString::from("-5")),
Some(Ok(ObsoleteArgs {
num: 5,
plus: false,
Expand All @@ -105,7 +123,7 @@ mod tests {
}))
);
assert_eq!(
parse_obsolete("+100f"),
parse_obsolete(&OsString::from("+100f")),
Some(Ok(ObsoleteArgs {
num: 100,
plus: true,
Expand All @@ -114,7 +132,7 @@ mod tests {
}))
);
assert_eq!(
parse_obsolete("-2b"),
parse_obsolete(&OsString::from("-2b")),
Some(Ok(ObsoleteArgs {
num: 1024,
plus: false,
Expand All @@ -125,23 +143,47 @@ mod tests {
}
#[test]
fn test_parse_errors_obsolete() {
assert_eq!(parse_obsolete("-5n"), Some(Err(ParseError::Context)));
assert_eq!(parse_obsolete("-5c5"), Some(Err(ParseError::Context)));
assert_eq!(parse_obsolete("-1vzc"), Some(Err(ParseError::Context)));
assert_eq!(parse_obsolete("-5m"), Some(Err(ParseError::Context)));
assert_eq!(parse_obsolete("-1k"), Some(Err(ParseError::Context)));
assert_eq!(parse_obsolete("-1mmk"), Some(Err(ParseError::Context)));
assert_eq!(parse_obsolete("-105kzm"), Some(Err(ParseError::Context)));
assert_eq!(parse_obsolete("-1vz"), Some(Err(ParseError::Context)));
assert_eq!(
parse_obsolete("-1vzqvq"), // spell-checker:disable-line
parse_obsolete(&OsString::from("-5n")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-5c5")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-1vzc")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-5m")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-1k")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-1mmk")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-105kzm")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-1vz")),
Some(Err(ParseError::Context))
);
assert_eq!(
parse_obsolete(&OsString::from("-1vzqvq")), // spell-checker:disable-line
Some(Err(ParseError::Context))
);
}
#[test]
fn test_parse_obsolete_no_match() {
assert_eq!(parse_obsolete("-k"), None);
assert_eq!(parse_obsolete("asd"), None);
assert_eq!(parse_obsolete("-cc"), None);
assert_eq!(parse_obsolete(&OsString::from("-k")), None);
assert_eq!(parse_obsolete(&OsString::from("asd")), None);
assert_eq!(parse_obsolete(&OsString::from("-cc")), None);
}
}

0 comments on commit 394d415

Please sign in to comment.