Skip to content

Commit

Permalink
tail: improve GNU compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
bbara committed Feb 12, 2023
1 parent d2c047c commit 6564741
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 20 deletions.
22 changes: 19 additions & 3 deletions src/uu/tail/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,25 @@ impl FilterMode {
let mode = if let Some(arg) = matches.get_one::<String>(options::BYTES) {
match parse_num(arg) {
Ok(signum) => Self::Bytes(signum),
Err(e) => return Err(UUsageError::new(1, format!("invalid number of bytes: {e}"))),
Err(e) => {
return Err(USimpleError::new(
1,
format!("invalid number of bytes: {e}"),
))
}
}
} else if let Some(arg) = matches.get_one::<String>(options::LINES) {
match parse_num(arg) {
Ok(signum) => {
let delimiter = if zero_term { 0 } else { b'\n' };
Self::Lines(signum, delimiter)
}
Err(e) => return Err(UUsageError::new(1, format!("invalid number of lines: {e}"))),
Err(e) => {
return Err(USimpleError::new(
1,
format!("invalid number of lines: {e}"),
))
}
}
} else if zero_term {
Self::default_zero()
Expand Down Expand Up @@ -308,14 +318,20 @@ pub fn arg_iterate<'a>(
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(UUsageError::new(
Some(Err(e)) => Err(USimpleError::new(
1,
match e {
parse::ParseError::Syntax => format!("bad argument format: {}", s.quote()),
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))),
Expand Down
72 changes: 55 additions & 17 deletions src/uu/tail/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ use std::ffi::OsString;
pub enum ParseError {
Syntax,
Overflow,
Context,
}
/// Parses obsolete syntax
/// tail -NUM\[kmzv\] // spell-checker:disable-line
/// tail -NUM\[kmzv\], tail -\[bl\] and tail +\[bcl\] // spell-checker:disable-line
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
let mut chars = src.char_indices();
if let Some((_, '-')) = chars.next() {
let (_, sign) = chars.next()?;
if sign == '-' || sign == '+' {
let mut num_end = 0usize;
let mut has_num = false;
let mut last_char = 0 as char;
Expand All @@ -35,8 +37,9 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
let mut zero_terminated = false;
let mut multiplier = None;
let mut c = last_char;
let mut has_multiple_chars = false;
let mut has_standalone_suffix = false;
loop {
// not that here, we only match lower case 'k', 'c', and 'm'
match c {
// we want to preserve order
// this also saves us 1 heap allocation
Expand All @@ -53,15 +56,32 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
'b' => multiplier = Some(512),
'k' => multiplier = Some(1024),
'm' => multiplier = Some(1024 * 1024),
'l' => {}
'\0' => {}
_ => return Some(Err(ParseError::Syntax)),
_ => return Some(Err(ParseError::Context)),
}
if c == 'b' || c == 'c' || c == 'l' {
if has_standalone_suffix {
// avoid second standalone suffix, like -cl
return Some(Err(ParseError::Syntax));
}
has_standalone_suffix = true;
}
if let Some((_, next)) = chars.next() {
has_multiple_chars = true;
c = next;
} else {
break;
}
}
if has_standalone_suffix && has_multiple_chars {
// avoid option in invalid context, like -5zc
if sign == '-' && has_num {
return Some(Err(ParseError::Context));
}
// avoid standalone suffix with another option, like +2cz
return None;
}
let mut options = Vec::new();
if quiet {
options.push(OsString::from("-q"));
Expand All @@ -78,15 +98,33 @@ pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>
Some(n) => n,
None => return Some(Err(ParseError::Overflow)),
};
options.push(OsString::from(format!("{num}")));
options.push(OsString::from(format!("{sign}{num}")));
} else {
options.push(OsString::from("-n"));
options.push(OsString::from(format!("{num}")));
options.push(OsString::from(format!("{sign}{num}")));
}
Some(Ok(options.into_iter()))
}
Err(_) => Some(Err(ParseError::Overflow)),
}
} else if src.len() == 2 {
if let Some(char) = src.chars().nth(1) {
// enable corner cases: -l, +l, -b, +b, +c. -c is handled by clap
if char == 'l' {
let ret = vec![OsString::from(format!("-n {sign}10"))].into_iter();
return Some(Ok(ret));
} else if char == 'b' {
let ret = vec![OsString::from(format!("-c {sign}5120"))].into_iter();
return Some(Ok(ret));
} else if sign == '+' && char == 'c' {
let ret = vec![OsString::from("-c +10")].into_iter();
return Some(Ok(ret));
} else {
None
}
} else {
None
}
} else {
None
}
Expand All @@ -113,27 +151,27 @@ mod tests {
}
#[test]
fn test_parse_numbers_obsolete() {
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"]));
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"]));
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "-5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "-100"]));
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "-5242880"]));
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "-1024"]));
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "-1024"]));
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "-1024"]));
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "-1"]));
assert_eq!(
obsolete("-1vzqvq"), // spell-checker:disable-line
obsolete_result(&["-q", "-z", "-n", "1"])
);
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"]));
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "-1"]));
assert_eq!(
obsolete("-105kzm"),
obsolete_result(&["-z", "-c", "110100480"])
obsolete_result(&["-z", "-c", "-110100480"])
);
}
#[test]
fn test_parse_errors_obsolete() {
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax)));
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax)));
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Context)));
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Context)));
}
#[test]
fn test_parse_obsolete_no_match() {
Expand Down
Loading

0 comments on commit 6564741

Please sign in to comment.