Merge pull request #7760 from Qelxiros/7670-tail-hex-formatting

tail hex parsing, remove fundu dependency
This commit is contained in:
Jeremy Smart 2025-04-23 12:34:00 -04:00 committed by GitHub
parent 0125bbc2b4
commit 044b33d8cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 125 additions and 91 deletions

View file

@ -20,7 +20,6 @@ exacl
filetime
formatteriteminfo
fsext
fundu
getopts
getrandom
globset

16
Cargo.lock generated
View file

@ -1030,21 +1030,6 @@ dependencies = [
"libc",
]
[[package]]
name = "fundu"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce12752fc64f35be3d53e0a57017cd30970f0cffd73f62c791837d8845badbd"
dependencies = [
"fundu-core",
]
[[package]]
name = "fundu-core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e463452e2d8b7600d38dcea1ed819773a57f0d710691bfc78db3961bd3f4c3ba"
[[package]]
name = "funty"
version = "2.0.0"
@ -3372,7 +3357,6 @@ name = "uu_tail"
version = "0.0.30"
dependencies = [
"clap",
"fundu",
"libc",
"memchr",
"notify",

View file

@ -1,7 +1,7 @@
# coreutils (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
# spell-checker:ignore (libs) bigdecimal datetime serde bincode fundu gethostid kqueue libselinux mangen memmap procfs uuhelp startswith constness expl
# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap procfs uuhelp startswith constness expl
[package]
name = "coreutils"
@ -295,7 +295,6 @@ filetime = "0.2.23"
fnv = "1.0.7"
fs_extra = "1.3.0"
fts-sys = "0.2.16"
fundu = "2.0.0"
gcd = "2.3"
glob = "0.3.1"
half = "2.4.1"

View file

@ -5,6 +5,7 @@ use uucore::parser::parse_time;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
_ = parse_time::from_str(s);
_ = parse_time::from_str(s, true);
_ = parse_time::from_str(s, false);
}
});

View file

@ -64,7 +64,7 @@ fn sleep(args: &[&str]) -> UResult<()> {
let sleep_dur = args
.iter()
.filter_map(|input| match parse_time::from_str(input) {
.filter_map(|input| match parse_time::from_str(input, true) {
Ok(duration) => Some(duration),
Err(error) => {
arg_error = true;

View file

@ -1,4 +1,4 @@
# spell-checker:ignore (libs) kqueue fundu
# spell-checker:ignore (libs) kqueue
[package]
name = "uu_tail"
description = "tail ~ (uutils) display the last lines of input"
@ -25,7 +25,6 @@ memchr = { workspace = true }
notify = { workspace = true }
uucore = { workspace = true, features = ["parser"] }
same-file = { workspace = true }
fundu = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows-sys = { workspace = true, features = [

View file

@ -3,18 +3,18 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) kqueue Signum fundu
// spell-checker:ignore (ToDO) kqueue Signum
use crate::paths::Input;
use crate::{Quotable, parse, platform};
use clap::{Arg, ArgAction, ArgMatches, Command, value_parser};
use fundu::{DurationParser, SaturatingInto};
use same_file::Handle;
use std::ffi::OsString;
use std::io::IsTerminal;
use std::time::Duration;
use uucore::error::{UResult, USimpleError, UUsageError};
use uucore::parser::parse_size::{ParseSizeError, parse_size_u64};
use uucore::parser::parse_time;
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
use uucore::{format_usage, help_about, help_usage, show_warning};
@ -228,22 +228,9 @@ impl Settings {
};
if let Some(source) = matches.get_one::<String>(options::SLEEP_INT) {
// Advantage of `fundu` over `Duration::(try_)from_secs_f64(source.parse().unwrap())`:
// * doesn't panic on errors like `Duration::from_secs_f64` would.
// * no precision loss, rounding errors or other floating point problems.
// * evaluates to `Duration::MAX` if the parsed number would have exceeded
// `DURATION::MAX` or `infinity` was given
// * not applied here but it supports customizable time units and provides better error
// messages
settings.sleep_sec = match DurationParser::without_time_units().parse(source) {
Ok(duration) => SaturatingInto::<Duration>::saturating_into(duration),
Err(_) => {
return Err(UUsageError::new(
1,
format!("invalid number of seconds: '{source}'"),
));
}
}
settings.sleep_sec = parse_time::from_str(source, false).map_err(|_| {
UUsageError::new(1, format!("invalid number of seconds: '{source}'"))
})?;
}
if let Some(s) = matches.get_one::<String>(options::MAX_UNCHANGED_STATS) {

View file

@ -71,17 +71,15 @@ impl Config {
let kill_after = match options.get_one::<String>(options::KILL_AFTER) {
None => None,
Some(kill_after) => match parse_time::from_str(kill_after) {
Some(kill_after) => match parse_time::from_str(kill_after, true) {
Ok(k) => Some(k),
Err(err) => return Err(UUsageError::new(ExitStatus::TimeoutFailed.into(), err)),
},
};
let duration =
match parse_time::from_str(options.get_one::<String>(options::DURATION).unwrap()) {
Ok(duration) => duration,
Err(err) => return Err(UUsageError::new(ExitStatus::TimeoutFailed.into(), err)),
};
parse_time::from_str(options.get_one::<String>(options::DURATION).unwrap(), true)
.map_err(|err| UUsageError::new(ExitStatus::TimeoutFailed.into(), err))?;
let preserve_status: bool = options.get_flag(options::PRESERVE_STATUS);
let foreground = options.get_flag(options::FOREGROUND);

View file

@ -25,7 +25,7 @@ use std::time::Duration;
/// one hundred twenty three seconds or "4.5d" meaning four and a half
/// days. If no unit is specified, the unit is assumed to be seconds.
///
/// The only allowed suffixes are
/// If `allow_suffixes` is true, the allowed suffixes are
///
/// * "s" for seconds,
/// * "m" for minutes,
@ -48,10 +48,12 @@ use std::time::Duration;
/// ```rust
/// use std::time::Duration;
/// use uucore::parser::parse_time::from_str;
/// assert_eq!(from_str("123"), Ok(Duration::from_secs(123)));
/// assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2)));
/// assert_eq!(from_str("123", true), Ok(Duration::from_secs(123)));
/// assert_eq!(from_str("123", false), Ok(Duration::from_secs(123)));
/// assert_eq!(from_str("2d", true), Ok(Duration::from_secs(60 * 60 * 24 * 2)));
/// assert!(from_str("2d", false).is_err());
/// ```
pub fn from_str(string: &str) -> Result<Duration, String> {
pub fn from_str(string: &str, allow_suffixes: bool) -> Result<Duration, String> {
// TODO: Switch to Duration::NANOSECOND if that ever becomes stable
// https://github.com/rust-lang/rust/issues/57391
const NANOSECOND_DURATION: Duration = Duration::from_nanos(1);
@ -63,7 +65,11 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
let num = match num_parser::parse(
string,
ParseTarget::Duration,
&[('s', 1), ('m', 60), ('h', 60 * 60), ('d', 60 * 60 * 24)],
if allow_suffixes {
&[('s', 1), ('m', 60), ('h', 60 * 60), ('d', 60 * 60 * 24)]
} else {
&[]
},
) {
Ok(ebd) | Err(ExtendedParserError::Overflow(ebd)) => ebd,
Err(ExtendedParserError::Underflow(_)) => return Ok(NANOSECOND_DURATION),
@ -105,20 +111,26 @@ mod tests {
#[test]
fn test_no_units() {
assert_eq!(from_str("123"), Ok(Duration::from_secs(123)));
assert_eq!(from_str("123", true), Ok(Duration::from_secs(123)));
assert_eq!(from_str("123", false), Ok(Duration::from_secs(123)));
}
#[test]
fn test_units() {
assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2)));
assert_eq!(
from_str("2d", true),
Ok(Duration::from_secs(60 * 60 * 24 * 2))
);
assert!(from_str("2d", false).is_err());
}
#[test]
fn test_overflow() {
// u64 seconds overflow (in Duration)
assert_eq!(from_str("9223372036854775808d"), Ok(Duration::MAX));
assert_eq!(from_str("9223372036854775808d", true), Ok(Duration::MAX));
// ExtendedBigDecimal overflow
assert_eq!(from_str("1e92233720368547758080"), Ok(Duration::MAX));
assert_eq!(from_str("1e92233720368547758080", false), Ok(Duration::MAX));
assert_eq!(from_str("1e92233720368547758080", false), Ok(Duration::MAX));
}
#[test]
@ -128,87 +140,143 @@ mod tests {
const NANOSECOND_DURATION: Duration = Duration::from_nanos(1);
// ExtendedBigDecimal underflow
assert_eq!(from_str("1e-92233720368547758080"), Ok(NANOSECOND_DURATION));
// nanoseconds underflow (in Duration)
assert_eq!(from_str("0.0000000001"), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1e-10"), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("9e-10"), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1e-9"), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1.9e-9"), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("2e-9"), Ok(Duration::from_nanos(2)));
assert_eq!(
from_str("1e-92233720368547758080", true),
Ok(NANOSECOND_DURATION)
);
// nanoseconds underflow (in Duration, true)
assert_eq!(from_str("0.0000000001", true), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1e-10", true), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("9e-10", true), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1e-9", true), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1.9e-9", true), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("2e-9", true), Ok(Duration::from_nanos(2)));
// ExtendedBigDecimal underflow
assert_eq!(
from_str("1e-92233720368547758080", false),
Ok(NANOSECOND_DURATION)
);
// nanoseconds underflow (in Duration, false)
assert_eq!(from_str("0.0000000001", false), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1e-10", false), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("9e-10", false), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1e-9", false), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("1.9e-9", false), Ok(NANOSECOND_DURATION));
assert_eq!(from_str("2e-9", false), Ok(Duration::from_nanos(2)));
}
#[test]
fn test_zero() {
assert_eq!(from_str("0e-9"), Ok(Duration::ZERO));
assert_eq!(from_str("0e-100"), Ok(Duration::ZERO));
assert_eq!(from_str("0e-92233720368547758080"), Ok(Duration::ZERO));
assert_eq!(from_str("0.000000000000000000000"), Ok(Duration::ZERO));
assert_eq!(from_str("0e-9", true), Ok(Duration::ZERO));
assert_eq!(from_str("0e-100", true), Ok(Duration::ZERO));
assert_eq!(
from_str("0e-92233720368547758080", true),
Ok(Duration::ZERO)
);
assert_eq!(
from_str("0.000000000000000000000", true),
Ok(Duration::ZERO)
);
assert_eq!(from_str("0e-9", false), Ok(Duration::ZERO));
assert_eq!(from_str("0e-100", false), Ok(Duration::ZERO));
assert_eq!(
from_str("0e-92233720368547758080", false),
Ok(Duration::ZERO)
);
assert_eq!(
from_str("0.000000000000000000000", false),
Ok(Duration::ZERO)
);
}
#[test]
fn test_hex_float() {
assert_eq!(
from_str("0x1.1p-1"),
from_str("0x1.1p-1", true),
Ok(Duration::from_secs_f64(0.53125f64))
);
assert_eq!(
from_str("0x1.1p-1d"),
from_str("0x1.1p-1", false),
Ok(Duration::from_secs_f64(0.53125f64))
);
assert_eq!(
from_str("0x1.1p-1d", true),
Ok(Duration::from_secs_f64(0.53125f64 * 3600.0 * 24.0))
);
assert_eq!(from_str("0xfh"), Ok(Duration::from_secs(15 * 3600)));
assert_eq!(from_str("0xfh", true), Ok(Duration::from_secs(15 * 3600)));
}
#[test]
fn test_error_empty() {
assert!(from_str("").is_err());
assert!(from_str("", true).is_err());
assert!(from_str("", false).is_err());
}
#[test]
fn test_error_invalid_unit() {
assert!(from_str("123X").is_err());
assert!(from_str("123X", true).is_err());
assert!(from_str("123X", false).is_err());
}
#[test]
fn test_error_multi_bytes_characters() {
assert!(from_str("10€").is_err());
assert!(from_str("10€", true).is_err());
assert!(from_str("10€", false).is_err());
}
#[test]
fn test_error_invalid_magnitude() {
assert!(from_str("12abc3s").is_err());
assert!(from_str("12abc3s", true).is_err());
assert!(from_str("12abc3s", false).is_err());
}
#[test]
fn test_error_only_point() {
assert!(from_str(".", true).is_err());
assert!(from_str(".", false).is_err());
}
#[test]
fn test_negative() {
assert!(from_str("-1").is_err());
assert!(from_str("-1", true).is_err());
assert!(from_str("-1", false).is_err());
}
#[test]
fn test_infinity() {
assert_eq!(from_str("inf"), Ok(Duration::MAX));
assert_eq!(from_str("infinity"), Ok(Duration::MAX));
assert_eq!(from_str("infinityh"), Ok(Duration::MAX));
assert_eq!(from_str("INF"), Ok(Duration::MAX));
assert_eq!(from_str("INFs"), Ok(Duration::MAX));
assert_eq!(from_str("inf", true), Ok(Duration::MAX));
assert_eq!(from_str("infinity", true), Ok(Duration::MAX));
assert_eq!(from_str("infinityh", true), Ok(Duration::MAX));
assert_eq!(from_str("INF", true), Ok(Duration::MAX));
assert_eq!(from_str("INFs", true), Ok(Duration::MAX));
assert_eq!(from_str("inf", false), Ok(Duration::MAX));
assert_eq!(from_str("infinity", false), Ok(Duration::MAX));
assert_eq!(from_str("INF", false), Ok(Duration::MAX));
}
#[test]
fn test_nan() {
assert!(from_str("nan").is_err());
assert!(from_str("nans").is_err());
assert!(from_str("-nanh").is_err());
assert!(from_str("NAN").is_err());
assert!(from_str("-NAN").is_err());
assert!(from_str("nan", true).is_err());
assert!(from_str("nans", true).is_err());
assert!(from_str("-nanh", true).is_err());
assert!(from_str("NAN", true).is_err());
assert!(from_str("-NAN", true).is_err());
assert!(from_str("nan", false).is_err());
assert!(from_str("NAN", false).is_err());
assert!(from_str("-NAN", false).is_err());
}
/// Test that capital letters are not allowed in suffixes.
#[test]
fn test_no_capital_letters() {
assert!(from_str("1S").is_err());
assert!(from_str("1M").is_err());
assert!(from_str("1H").is_err());
assert!(from_str("1D").is_err());
assert!(from_str("INFD").is_err());
assert!(from_str("1S", true).is_err());
assert!(from_str("1M", true).is_err());
assert!(from_str("1H", true).is_err());
assert!(from_str("1D", true).is_err());
assert!(from_str("INFD", true).is_err());
}
}

View file

@ -4444,7 +4444,6 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_stays_same
}
#[rstest]
#[case::exponent_exceed_float_max("1.0e100000")]
#[case::underscore_delimiter("1_000")]
#[case::only_point(".")]
#[case::space_in_primes("' '")]