File: systemd.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 (116 lines) | stat: -rw-r--r-- 3,883 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
// Copyright (c) 2023 Joining7943 <joining@posteo.de>
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

//! A systemd time span parser as specified in `man systemd.time`. The output of this example is
//! imitating the output of `systemd-analyze timespan SYSTEMD_TIME_SPAN`

use std::time::Duration;

use clap::{command, Arg};
use fundu::TimeUnit::*;
use fundu::{CustomDurationParser, SYSTEMD_TIME_UNITS};

/// Create a human readable string like `100y 2h 46min 40s 123us 456ns` 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();
    const MILLIS_PER_NANO: u32 = 1_000_000;
    const MICROS_PER_NANO: u32 = 1_000;

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

    let mut result = Vec::with_capacity(10);
    let mut secs = duration.as_secs();
    if secs > 0 {
        if secs >= YEAR {
            result.push(format!("{}y", secs / YEAR));
            secs %= YEAR;
        }
        if secs >= MONTH {
            result.push(format!("{}month", secs / MONTH));
            secs %= MONTH;
        }
        if secs >= WEEK {
            result.push(format!("{}w", secs / WEEK));
            secs %= WEEK;
        }
        if secs >= DAY {
            result.push(format!("{}d", secs / DAY));
            secs %= DAY;
        }
        if secs >= HOUR {
            result.push(format!("{}h", secs / HOUR));
            secs %= HOUR;
        }
        if secs >= MINUTE {
            result.push(format!("{}min", secs / MINUTE));
            secs %= MINUTE;
        }
        if secs >= 1 {
            result.push(format!("{}s", secs));
        }
    }

    let mut nanos = duration.subsec_nanos();
    if nanos > 0 {
        if nanos >= MILLIS_PER_NANO {
            result.push(format!("{}ms", nanos / MILLIS_PER_NANO));
            nanos %= MILLIS_PER_NANO;
        }
        if nanos >= MICROS_PER_NANO {
            result.push(format!("{}us", nanos / MICROS_PER_NANO));
            nanos %= MICROS_PER_NANO;
        }
        if nanos >= 1 {
            result.push(format!("{}ns", nanos));
        }
    }

    result.join(" ")
}

fn main() {
    let matches = command!()
        .about(
            "A systemd time span parser as specified in `man systemd.time`. The output of this \
             example is imitating the output of `systemd-analyze timespan SYSTEMD_TIME_SPAN`",
        )
        .allow_negative_numbers(true)
        .arg(
            Arg::new("SYSTEMD_TIME_SPAN")
                .action(clap::ArgAction::Set)
                .help("A time span as specified in `man systemd.time`"),
        )
        .get_matches();

    let delimiter = |byte| matches!(byte, b' ' | b'\t' | b'\n' | b'\r');
    let parser = CustomDurationParser::builder()
        .time_units(&SYSTEMD_TIME_UNITS)
        .disable_exponent()
        .disable_fraction()
        .disable_infinity()
        .allow_delimiter(delimiter)
        .parse_multiple(delimiter, None)
        .build();

    let input: &String = matches
        .get_one("SYSTEMD_TIME_SPAN")
        .expect("At least one argument must be present");
    match parser.parse(input.trim()) {
        Ok(duration) => {
            let duration: std::time::Duration = duration.try_into().unwrap();
            println!("{:>8}: {}", "Original", input);
            println!("{:>8}: {}", "μs", duration.as_micros());
            println!("{:>8}: {}", "Human", make_human(duration));
        }
        Err(error) => eprintln!("Failed to parse time span '{}': {}", &input, error),
    }
}