Skip to content

Commit

Permalink
tail: skip clap for obsolete args
Browse files Browse the repository at this point in the history
  • Loading branch information
bbara committed Feb 16, 2023
1 parent bce55fb commit ef8087f
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 90 deletions.
115 changes: 80 additions & 35 deletions src/uu/tail/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
use fundu::DurationParser;
use same_file::Handle;
use std::collections::VecDeque;
use std::ffi::OsString;
use std::time::Duration;
use uucore::error::{UResult, USimpleError, UUsageError};
use uucore::parse_size::{parse_size, ParseSizeError};
Expand Down Expand Up @@ -65,6 +64,20 @@ pub enum FilterMode {
}

impl FilterMode {
fn from_obsolete_args(args: parse::ObsoleteArgs) -> UResult<Self> {
let signum = if args.plus {
Signum::Positive(args.num)
} else {
Signum::Negative(args.num)
};
let mode = if args.lines {
Self::Lines(signum, b'\n')
} else {
Self::Bytes(signum)
};
Ok(mode)
}

fn from(matches: &ArgMatches) -> UResult<Self> {
let zero_term = matches.get_flag(options::ZERO_TERM);
let mode = if let Some(arg) = matches.get_one::<String>(options::BYTES) {
Expand Down Expand Up @@ -138,6 +151,29 @@ pub struct Settings {
}

impl Settings {
pub fn from_obsolete_args(args: parse::ObsoleteArgs, name: Option<&str>) -> UResult<Self> {
let mut settings: Self = Self {
sleep_sec: Duration::from_secs_f32(1.0),
max_unchanged_stats: 5,
..Default::default()
};
if args.follow {
settings.follow = if name.is_some() {
Some(FollowMode::Name)
} else {
Some(FollowMode::Descriptor)
};
}
settings.mode = FilterMode::from_obsolete_args(args)?;
let input = if let Some(name) = name {
Input::from(name.to_string())
} else {
Input::default()
};
settings.inputs.push_back(input);
Ok(settings)
}

pub fn from(matches: &clap::ArgMatches) -> UResult<Self> {
let mut settings: Self = Self {
sleep_sec: Duration::from_secs_f32(1.0),
Expand Down Expand Up @@ -314,37 +350,23 @@ impl Settings {
}
}

pub fn arg_iterate<'a>(
mut args: impl uucore::Args + 'a,
) -> UResult<Box<dyn Iterator<Item = OsString> + 'a>> {
// argv[0] is always present
let first = args.next().unwrap();
if let Some(second) = args.next() {
if let Some(s) = second.to_str() {
match parse::parse_obsolete(s) {
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
Some(Err(e)) => Err(USimpleError::new(
1,
match e {
parse::ParseError::Overflow => format!(
"invalid argument: {} Value too large for defined datatype",
s.quote()
),
parse::ParseError::Context => {
format!(
"option used in invalid context -- {}",
s.chars().nth(1).unwrap_or_default()
)
}
},
)),
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
}
} else {
Err(UUsageError::new(1, "bad argument encoding".to_owned()))
}
} else {
Ok(Box::new(vec![first].into_iter()))
pub fn parse_obsolete(args: &str) -> UResult<Option<parse::ObsoleteArgs>> {
match parse::parse_obsolete(args) {
Some(Ok(args)) => Ok(Some(args)),
None => Ok(None),
Some(Err(e)) => Err(USimpleError::new(
1,
match e {
parse::ParseError::Overflow => format!(
"invalid argument: {} Value too large for defined datatype",
args.quote()
),
parse::ParseError::Context => format!(
"option used in invalid context -- {}",
args.chars().nth(1).unwrap_or_default()
),
},
)),
}
}

Expand Down Expand Up @@ -372,9 +394,32 @@ fn parse_num(src: &str) -> Result<Signum, ParseSizeError> {
})
}

pub fn parse_args(args: impl uucore::Args) -> UResult<Settings> {
let matches = uu_app().try_get_matches_from(arg_iterate(args)?)?;
Settings::from(&matches)
pub fn parse_args(mut args: impl uucore::Args) -> UResult<Settings> {
let first = args.next().unwrap();
let second = args.next();
if let Some(second) = second {
if let Some(second_str) = second.to_str() {
if let Some(obsolete_args) = parse_obsolete(second_str)? {
let name = args.next();
if let Some(name) = name {
if let Some(name_str) = name.to_str() {
Settings::from_obsolete_args(obsolete_args, Some(name_str))
} else {
Err(UUsageError::new(1, "bad argument encoding".to_owned()))
}
} else {
Settings::from_obsolete_args(obsolete_args, None)
}
} else {
let args = vec![first, second].into_iter().chain(args);
Settings::from(&uu_app().try_get_matches_from(args)?)
}
} else {
Err(UUsageError::new(1, "bad argument encoding".to_owned()))
}
} else {
Settings::from(&uu_app().try_get_matches_from(vec![first])?)
}
}

pub fn uu_app() -> Command {
Expand Down
132 changes: 77 additions & 55 deletions src/uu/tail/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
// * 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,
pub plus: bool,
pub lines: bool,
pub follow: bool,
}

#[derive(PartialEq, Eq, Debug)]
pub enum ParseError {
Expand All @@ -12,7 +18,7 @@ pub enum ParseError {
}
/// Parses obsolete syntax
/// tail -\[NUM\]\[bl\]\[f\] and tail +\[NUM\]\[bcl\]\[f\] // spell-checker:disable-line
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
pub fn parse_obsolete(src: &str) -> Option<Result<ObsoleteArgs, ParseError>> {
let mut chars = src.chars();
let sign = chars.next()?;
if sign != '+' && sign != '-' {
Expand All @@ -30,101 +36,117 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
} else {
10
};
let num = num as u64;

let mut follow = false;
let mut mode = None;
let mut mode = 'l';
let mut first_char = true;
for char in chars.skip_while(|&c| c.is_ascii_digit()) {
if sign == '-' && char == 'c' && !has_num {
// special case: -c should be handled by clap (is ambiguous)
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 = Some(char);
mode = char;
} else if has_num && sign == '-' {
return Some(Err(ParseError::Context));
} else {
return None;
}
first_char = false;
}
let multiplier = if mode == 'b' { 512 } else { 1 };
let num = match num.checked_mul(multiplier) {
Some(n) => n,
None => return Some(Err(ParseError::Overflow)),
};

let mut options = Vec::new();
if follow {
options.push(OsString::from("-f"));
}
let mode = mode.unwrap_or('l');
if mode == 'b' || mode == 'c' {
options.push(OsString::from("-c"));
let n = if mode == 'b' { 512 } else { 1 };
let num = match num.checked_mul(n) {
Some(n) => n,
None => return Some(Err(ParseError::Overflow)),
};
options.push(OsString::from(format!("{sign}{num}")));
} else {
options.push(OsString::from("-n"));
options.push(OsString::from(format!("{sign}{num}")));
}
Some(Ok(options.into_iter()))
Some(Ok(ObsoleteArgs {
num,
plus: sign == '+',
lines: mode == 'l',
follow,
}))
}

#[cfg(test)]
mod tests {
use super::*;
fn obsolete(src: &str) -> Option<Result<Vec<String>, ParseError>> {
let r = parse_obsolete(src);
match r {
Some(s) => match s {
Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())),
Err(e) => Some(Err(e)),
},
None => None,
}
}
fn obsolete_result(src: &[&str]) -> Option<Result<Vec<String>, ParseError>> {
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
}
#[test]
fn test_parse_numbers_obsolete() {
assert_eq!(obsolete("+2c"), obsolete_result(&["-c", "+2"]));
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "-5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "-100"]));
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "-1024"]));
assert_eq!(
parse_obsolete("+2c"),
Some(Ok(ObsoleteArgs {
num: 2,
plus: true,
lines: false,
follow: false,
}))
);
assert_eq!(
parse_obsolete("-5"),
Some(Ok(ObsoleteArgs {
num: 5,
plus: false,
lines: true,
follow: false,
}))
);
assert_eq!(
parse_obsolete("+100f"),
Some(Ok(ObsoleteArgs {
num: 100,
plus: true,
lines: true,
follow: true,
}))
);
assert_eq!(
parse_obsolete("-2b"),
Some(Ok(ObsoleteArgs {
num: 1024,
plus: false,
lines: false,
follow: false,
}))
);
}
#[test]
fn test_parse_errors_obsolete() {
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-1vzc"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-5m"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-1k"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-1mmk"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-105kzm"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-1vz"), Some(Err(ParseError::Context)));
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!(
obsolete("-1vzqvq"), // spell-checker:disable-line
parse_obsolete("-1vzqvq"), // spell-checker:disable-line
Some(Err(ParseError::Context))
);
}
#[test]
fn test_parse_obsolete_no_match() {
assert_eq!(obsolete("-k"), None);
assert_eq!(obsolete("asd"), None);
assert_eq!(obsolete("-cc"), None);
assert_eq!(parse_obsolete("-k"), None);
assert_eq!(parse_obsolete("asd"), None);
assert_eq!(parse_obsolete("-cc"), None);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_parse_obsolete_overflow_x64() {
assert_eq!(
obsolete("-10000000000000000000000"),
parse_obsolete("-10000000000000000000000"),
Some(Err(ParseError::Overflow))
);
}
#[test]
#[cfg(target_pointer_width = "32")]
fn test_parse_obsolete_overflow_x32() {
assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow)));
assert_eq!(
parse_obsolete("-42949672960"),
Some(Err(ParseError::Overflow))
);
}
}

0 comments on commit ef8087f

Please sign in to comment.