File: gnu.rs

package info (click to toggle)
rust-fundu 1.0.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 996 kB
  • sloc: makefile: 2
file content (159 lines) | stat: -rw-r--r-- 5,667 bytes parent folder | download
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Copyright (c) 2023 Joining7943 <joining@posteo.de>
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

//! A gnu relative time parser as specified here
//! `https://www.gnu.org/software/coreutils/manual/html_node/Relative-items-in-date-strings.html`.

use clap::{command, Arg};
use fundu::TimeUnit::*;
use fundu::{
    CustomDurationParserBuilder, CustomTimeUnit, Duration, Multiplier, SaturatingInto, TimeKeyword,
};

const GNU_TIME_UNITS: [CustomTimeUnit<'static>; 8] = [
    CustomTimeUnit::with_default(Second, &["sec", "secs", "second", "seconds"]),
    CustomTimeUnit::with_default(Minute, &["min", "mins", "minute", "minutes"]),
    CustomTimeUnit::with_default(Hour, &["hour", "hours"]),
    CustomTimeUnit::with_default(Day, &["day", "days"]),
    CustomTimeUnit::with_default(Week, &["week", "weeks"]),
    CustomTimeUnit::new(Week, &["fortnight", "fortnights"], Some(Multiplier(2, 0))),
    CustomTimeUnit::with_default(Month, &["month", "months"]),
    CustomTimeUnit::with_default(Year, &["year", "years"]),
];

const GNU_KEYWORDS: [TimeKeyword<'static>; 3] = [
    TimeKeyword::new(Day, &["yesterday"], Some(Multiplier(-1, 0))),
    TimeKeyword::new(Day, &["tomorrow"], Some(Multiplier(1, 0))),
    TimeKeyword::new(Day, &["now", "today"], Some(Multiplier(0, 0))),
];

// Whitespace as defined in the POSIX locale (and used by gnu). In contrast to rust's definition of
// whitespace the POSIX definition includes the VERTICAL TAB:
// SPACE, HORIZONTAL TAB, LINE FEED, FORM FEED, CARRIAGE RETURN, VERTICAL TAB
const DELIMITER: fn(u8) -> bool =
    |byte| matches!(byte, b' ' | b'\t' | b'\n' | b'\x0c' | b'\r' | b'\x0b');

const PARSER_BUILDER: CustomDurationParserBuilder = CustomDurationParserBuilder::new()
    .allow_ago(DELIMITER)
    .allow_delimiter(DELIMITER)
    .allow_negative()
    .disable_exponent()
    .disable_fraction()
    .disable_infinity()
    .number_is_optional()
    .parse_multiple(DELIMITER, None);

fn make_plural(time: u64, singular: &str) -> String {
    if time > 1 {
        format!("{}s", singular)
    } else {
        singular.to_string()
    }
}

/// Create a human readable string like `100years 2hours 1min 40secs` from a `Duration`
fn make_human(duration: Duration) -> String {
    const YEAR: u64 = Year.multiplier().0.unsigned_abs();
    const MONTH: u64 = Month.multiplier().0.unsigned_abs();
    const WEEK: u64 = Week.multiplier().0.unsigned_abs();
    const DAY: u64 = Day.multiplier().0.unsigned_abs();
    const HOUR: u64 = Hour.multiplier().0.unsigned_abs();
    const MINUTE: u64 = Minute.multiplier().0.unsigned_abs();

    if duration.is_zero() {
        return "0sec".to_string();
    }

    let std_duration: std::time::Duration = duration.abs().saturating_into();

    let mut result = Vec::with_capacity(10);
    let mut secs = std_duration.as_secs();
    if secs > 0 {
        if secs >= YEAR {
            let years = secs / YEAR;
            result.push(format!("{}{}", years, make_plural(years, "year")));
            secs %= YEAR;
        }
        if secs >= MONTH {
            let months = secs / MONTH;
            result.push(format!("{}{}", months, make_plural(months, "month")));
            secs %= MONTH;
        }
        if secs >= WEEK {
            let weeks = secs / WEEK;
            result.push(format!("{}{}", weeks, make_plural(weeks, "week")));
            secs %= WEEK;
        }
        if secs >= DAY {
            let days = secs / DAY;
            result.push(format!("{}{}", days, make_plural(days, "day")));
            secs %= DAY;
        }
        if secs >= HOUR {
            let hours = secs / HOUR;
            result.push(format!("{}{}", hours, make_plural(hours, "hour")));
            secs %= HOUR;
        }
        if secs >= MINUTE {
            let minutes = secs / MINUTE;
            result.push(format!("{}{}", minutes, make_plural(minutes, "min")));
            secs %= MINUTE;
        }
        if secs >= 1 {
            result.push(format!("{}{}", secs, make_plural(secs, "sec")));
        }
    }

    if duration.is_negative() {
        format!("-{}", &result.join(" -"))
    } else {
        result.join(" ")
    }
}

fn main() {
    let matches = command!()
        .about(
            "A gnu relative time parser as specified in `info '(coreutils) Relative items in \
             date'`.",
        )
        .allow_negative_numbers(true)
        .allow_hyphen_values(true)
        .arg(
            Arg::new("GNU_RELATIVE_TIME")
                .action(clap::ArgAction::Set)
                .help(
                    "A relative time as specified in `info '(coreutils) Relative items in date \
                     strings'`",
                ),
        )
        .get_matches();

    let parser = PARSER_BUILDER
        .time_units(&GNU_TIME_UNITS)
        .keywords(&GNU_KEYWORDS)
        .build();

    let input: &String = matches
        .get_one("GNU_RELATIVE_TIME")
        .expect("One argument must be present");
    match parser.parse(input.trim()) {
        Ok(duration) => {
            let std_duration: std::time::Duration = duration.abs().saturating_into();
            println!("{:>8}: {}", "Original", input);
            println!(
                "{:>8}: {}",
                "Seconds",
                if duration.is_negative() {
                    format!("-{}", std_duration.as_secs())
                } else {
                    std_duration.as_secs().to_string()
                }
            );
            println!("{:>8}: {}", "Human", make_human(duration));
        }
        Err(error) => eprintln!("Failed to parse relative time '{}': {}", &input, error),
    }
}