diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 43f56dfc2..3757980d3 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -20,7 +20,6 @@ exacl filetime formatteriteminfo fsext -fundu getopts getrandom globset diff --git a/Cargo.lock b/Cargo.lock index df57479b3..c125d79e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 4ea6537dd..92a43dbdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/fuzz/fuzz_targets/fuzz_parse_time.rs b/fuzz/fuzz_targets/fuzz_parse_time.rs index 3aff82dc7..5745e5c87 100644 --- a/fuzz/fuzz_targets/fuzz_parse_time.rs +++ b/fuzz/fuzz_targets/fuzz_parse_time.rs @@ -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); } }); diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index e377b375f..2b533eade 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -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; diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index fd971fbb5..264ae29aa 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -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 = [ diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 0445f784c..61448388c 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -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::(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::::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::(options::MAX_UNCHANGED_STATS) { diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index a6cbaec79..9de335801 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -71,17 +71,15 @@ impl Config { let kill_after = match options.get_one::(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::(options::DURATION).unwrap()) { - Ok(duration) => duration, - Err(err) => return Err(UUsageError::new(ExitStatus::TimeoutFailed.into(), err)), - }; + parse_time::from_str(options.get_one::(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); diff --git a/src/uucore/src/lib/features/parser/parse_time.rs b/src/uucore/src/lib/features/parser/parse_time.rs index ef8407add..3ec2e6332 100644 --- a/src/uucore/src/lib/features/parser/parse_time.rs +++ b/src/uucore/src/lib/features/parser/parse_time.rs @@ -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 { +pub fn from_str(string: &str, allow_suffixes: bool) -> Result { // 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 { 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()); } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 6a9dcc896..b5fba824d 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -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("' '")]