1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use std::thread;
use std::time::Duration;

use uucore::{
    error::{UResult, USimpleError, UUsageError},
    format_usage, help_about, help_section, help_usage, show_error,
};

use clap::{crate_version, Arg, ArgAction, Command};
use fundu::{DurationParser, ParseError, SaturatingInto};

static ABOUT: &str = help_about!("sleep.md");
const USAGE: &str = help_usage!("sleep.md");
static AFTER_HELP: &str = help_section!("after help", "sleep.md");

mod options {
    pub const NUMBER: &str = "NUMBER";
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let matches = uu_app().try_get_matches_from(args)?;

    let numbers = matches
        .get_many::<String>(options::NUMBER)
        .ok_or_else(|| {
            USimpleError::new(
                1,
                format!(
                    "missing operand\nTry '{} --help' for more information.",
                    uucore::execution_phrase()
                ),
            )
        })?
        .map(|s| s.as_str())
        .collect::<Vec<_>>();

    sleep(&numbers)
}

pub fn uu_app() -> Command {
    Command::new(uucore::util_name())
        .version(crate_version!())
        .about(ABOUT)
        .after_help(AFTER_HELP)
        .override_usage(format_usage(USAGE))
        .infer_long_args(true)
        .arg(
            Arg::new(options::NUMBER)
                .help("pause for NUMBER seconds")
                .value_name(options::NUMBER)
                .action(ArgAction::Append),
        )
}

fn sleep(args: &[&str]) -> UResult<()> {
    let mut arg_error = false;

    use fundu::TimeUnit::{Day, Hour, Minute, Second};
    let parser = DurationParser::with_time_units(&[Second, Minute, Hour, Day]);

    let sleep_dur = args
        .iter()
        .filter_map(|input| match parser.parse(input.trim()) {
            Ok(duration) => Some(duration),
            Err(error) => {
                arg_error = true;

                let reason = match error {
                    ParseError::Empty if input.is_empty() => "Input was empty".to_string(),
                    ParseError::Empty => "Found only whitespace in input".to_string(),
                    ParseError::Syntax(pos, description)
                    | ParseError::TimeUnit(pos, description) => {
                        format!("{description} at position {}", pos.saturating_add(1))
                    }
                    ParseError::NegativeExponentOverflow | ParseError::PositiveExponentOverflow => {
                        "Exponent was out of bounds".to_string()
                    }
                    ParseError::NegativeNumber => "Number was negative".to_string(),
                    error => error.to_string(),
                };
                show_error!("invalid time interval '{input}': {reason}");

                None
            }
        })
        .fold(Duration::ZERO, |acc, n| {
            acc.saturating_add(SaturatingInto::<std::time::Duration>::saturating_into(n))
        });

    if arg_error {
        return Err(UUsageError::new(1, ""));
    };
    thread::sleep(sleep_dur);
    Ok(())
}