Skip to content

Commit 6f680f7

Browse files
authored
feat(flagset): support time duration (#11)
* feat(duration): wrap std time duration and impl value trait * feat(mod): rexport time mod * test(duration): properly parse subsecond units * build(feature): use duration constants * feat(duration): format to milli,micro,or nanoseconds when subsecond * test(duration): add case for fractional special cases * feat(duration): completely parse and format duration * refactor(duration): impl fromstr vs value * feat(value/slice): impl intoiterator and fromiterator traits * feat(time/duration): add simple constructor method * feat(flagset): add methods for creating flags based on time durations
1 parent bccb508 commit 6f680f7

File tree

3 files changed

+366
-1
lines changed

3 files changed

+366
-1
lines changed

src/lib.rs

+95
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
66
#![feature(type_name_of_val)]
77
#![feature(test)]
8+
#![feature(duration_constants)]
89

910
extern crate test;
1011

12+
pub mod time;
1113
mod value;
1214

1315
pub use value::Slice;
@@ -528,6 +530,90 @@ impl FlagSet {
528530
})
529531
}
530532

533+
#[doc = "Defines a `time::Duration` flag with specified name, default value, and usage string."]
534+
pub fn duration<S: Into<String>, U: Into<String>>(
535+
&mut self,
536+
name: S,
537+
value: time::Duration,
538+
usage: U,
539+
) {
540+
self.add_flag(Flag {
541+
name: name.into(),
542+
shorthand: char::default(),
543+
usage: usage.into(),
544+
value: Box::new(value),
545+
def_value: String::new(),
546+
changed: false,
547+
no_opt_def_value: String::new(),
548+
deprecated: String::new(),
549+
hidden: false,
550+
shorthand_deprecated: String::new(),
551+
})
552+
}
553+
554+
#[doc = "duration_p is like duration, but accepts a shorthand letter that can be used after a single dash."]
555+
pub fn duration_p<S, U>(&mut self, name: S, shorthand: char, value: time::Duration, usage: U)
556+
where
557+
S: Into<String>,
558+
U: Into<String>,
559+
{
560+
self.add_flag(Flag {
561+
name: name.into(),
562+
shorthand,
563+
usage: usage.into(),
564+
value: Box::new(value),
565+
def_value: String::new(),
566+
changed: false,
567+
no_opt_def_value: String::new(),
568+
deprecated: String::new(),
569+
hidden: false,
570+
shorthand_deprecated: String::new(),
571+
})
572+
}
573+
574+
#[doc = "duration_slice defines a `Slice<time::Duration>` flag with specified name, default value, and usage string."]
575+
pub fn duration_slice<S: Into<String>, U: Into<String>>(
576+
&mut self,
577+
name: S,
578+
value: value::Slice<time::Duration>,
579+
usage: U,
580+
) {
581+
self.add_flag(Flag {
582+
name: name.into(),
583+
shorthand: char::default(),
584+
usage: usage.into(),
585+
value: Box::new(value),
586+
def_value: String::new(),
587+
changed: false,
588+
no_opt_def_value: String::new(),
589+
deprecated: String::new(),
590+
hidden: false,
591+
shorthand_deprecated: String::new(),
592+
})
593+
}
594+
595+
#[doc = "duration_p_slice is like duration_slice, but accepts a shorthand letter that can used after a single dash."]
596+
pub fn duration_p_slice<S: Into<String>, U: Into<String>>(
597+
&mut self,
598+
name: S,
599+
shorthand: char,
600+
value: value::Slice<time::Duration>,
601+
usage: U,
602+
) {
603+
self.add_flag(Flag {
604+
name: name.into(),
605+
shorthand,
606+
usage: usage.into(),
607+
value: Box::new(value),
608+
def_value: String::new(),
609+
changed: false,
610+
no_opt_def_value: String::new(),
611+
deprecated: String::new(),
612+
hidden: false,
613+
shorthand_deprecated: String::new(),
614+
})
615+
}
616+
531617
builtin_flag_val!(char, char);
532618
builtin_flag_val!(string, String);
533619
builtin_flag_val!(uint8, u8);
@@ -951,6 +1037,15 @@ mod tests {
9511037
assert_eq!(flags.args().len(), 1);
9521038
}
9531039

1040+
#[test]
1041+
fn duration_value() {
1042+
let mut flags = FlagSet::new("test");
1043+
flags.duration("time", time::Duration::new(1, 0), "test");
1044+
1045+
let val = flags.value_of::<time::Duration>("time").unwrap();
1046+
assert_eq!(val.as_secs(), 1);
1047+
}
1048+
9541049
#[bench]
9551050
fn bench_parse(b: &mut Bencher) {
9561051
let mut flags = FlagSet::new("bench");

src/time.rs

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
//! Temporal quantification.
2+
3+
use std::convert;
4+
use std::fmt;
5+
use std::num;
6+
use std::ops;
7+
use std::str;
8+
use std::time;
9+
10+
/// A Duration type to represent a span of time.
11+
///
12+
/// A string representing the duration is of the form "72h3m0.5s".
13+
/// As a special case, durations less than one second format use a
14+
/// smaller unit (milli-, micro-, or nanoseconds) to ensure that the
15+
/// leading digit is non-zero. The zero duration formats as 0s.
16+
///
17+
/// ```
18+
/// use std::time;
19+
/// use pflag::Value;
20+
/// use pflag::time::Duration;
21+
///
22+
/// let mut d = Duration::default();
23+
/// d.set("1h1m10.987654321s".to_string());
24+
///
25+
/// assert_eq!(d, Duration::from(time::Duration::new(3670, 987654321)));
26+
/// assert_eq!(format!("{}", d), "1h1m10.987654321s");
27+
/// ```
28+
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
29+
pub struct Duration(time::Duration);
30+
31+
impl Duration {
32+
pub fn new(secs: u64, nanos: u32) -> Self {
33+
Self(time::Duration::new(secs, nanos))
34+
}
35+
}
36+
37+
impl str::FromStr for Duration {
38+
type Err = num::ParseIntError;
39+
40+
fn from_str(s: &str) -> Result<Self, Self::Err> {
41+
let mut chars = s.chars().peekable();
42+
let mut secs = 0;
43+
let mut nanos = 0;
44+
45+
loop {
46+
let (s, c) = scan_digits(&mut chars);
47+
let num: u64 = s.parse()?;
48+
match c {
49+
'h' => {
50+
secs += num * 3600;
51+
}
52+
'm' => {
53+
let c = chars.peek().unwrap();
54+
if *c == 's' {
55+
chars.next();
56+
nanos += num * 1e6 as u64;
57+
break;
58+
}
59+
secs += num * 60;
60+
}
61+
'µ' => {
62+
nanos += num * 1e3 as u64;
63+
break;
64+
}
65+
'n' => {
66+
nanos += num;
67+
break;
68+
}
69+
's' => {
70+
secs += num;
71+
break;
72+
}
73+
'.' => {
74+
let (mut s, c) = scan_digits(&mut chars);
75+
let mut pad = 9;
76+
if c == 's' {
77+
secs += num;
78+
} else if c == 'm' {
79+
nanos += num * 1e6 as u64;
80+
pad = 6;
81+
} else {
82+
nanos += num * 1e3 as u64;
83+
pad = 3;
84+
}
85+
(0..pad - s.len()).for_each(|_| s.push('0'));
86+
let s = s.trim_start_matches('0');
87+
let frac: u64 = s.parse()?;
88+
nanos += frac;
89+
break;
90+
}
91+
_ => break,
92+
}
93+
}
94+
95+
Ok(Self(time::Duration::new(secs, nanos as u32)))
96+
}
97+
}
98+
99+
fn scan_digits<I: Iterator<Item = char>>(iter: &mut I) -> (String, char) {
100+
let mut s = String::new();
101+
loop {
102+
let c = iter.next().unwrap();
103+
if c.is_alphabetic() || c == '.' {
104+
return (s, c);
105+
}
106+
107+
s.push(c);
108+
}
109+
}
110+
111+
impl fmt::Display for Duration {
112+
// largest time fmt is 5119296h10m10.000000000s
113+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114+
if self.0 == time::Duration::default() {
115+
return write!(f, "0s");
116+
}
117+
let mut buf = [0 as u8; 32];
118+
let mut w = buf.len();
119+
if self.0 < time::Duration::SECOND {
120+
let mut prec = 0;
121+
w -= 1;
122+
buf[w] = 's' as u8;
123+
w -= 1;
124+
if self.0 < time::Duration::MICROSECOND {
125+
buf[w] = 'n' as u8;
126+
} else if self.0 < time::Duration::MILLISECOND {
127+
prec = 3;
128+
w -= 1;
129+
// U+00B5 'µ' micro sign == 0xC2 0xB5
130+
buf[w] = 0xC2;
131+
buf[w + 1] = 0xB5;
132+
} else {
133+
prec = 6;
134+
buf[w] = 'm' as u8;
135+
}
136+
let (w2, u) = fmt_frac(&mut buf[..w], self.0.as_nanos() as u64, prec);
137+
w = fmt_int(&mut buf[..w2], u);
138+
} else {
139+
w -= 1;
140+
buf[w] = 's' as u8;
141+
142+
let (w2, mut u) = fmt_frac(&mut buf[..w], self.0.as_nanos() as u64, 9);
143+
144+
w = fmt_int(&mut buf[..w2], u % 60);
145+
u /= 60;
146+
147+
if u > 0 {
148+
w -= 1;
149+
buf[w] = 'm' as u8;
150+
w = fmt_int(&mut buf[..w], u % 60);
151+
u /= 60;
152+
153+
if u > 0 {
154+
w -= 1;
155+
buf[w] = 'h' as u8;
156+
w = fmt_int(&mut buf[..w], u)
157+
}
158+
}
159+
}
160+
161+
f.write_str(str::from_utf8(&buf[w..]).unwrap())
162+
}
163+
}
164+
165+
fn fmt_frac(buf: &mut [u8], mut v: u64, prec: u8) -> (usize, u64) {
166+
// Omit trailing zeros up to and including decimal point.
167+
let mut w = buf.len();
168+
let mut print = false;
169+
for _ in 0..prec {
170+
let digit = v % 10;
171+
print = print || digit != 0;
172+
if print {
173+
w -= 1;
174+
buf[w] = digit as u8 + '0' as u8;
175+
}
176+
v /= 10;
177+
}
178+
if print {
179+
w -= 1;
180+
buf[w] = '.' as u8;
181+
}
182+
(w, v)
183+
}
184+
185+
fn fmt_int(buf: &mut [u8], mut v: u64) -> usize {
186+
let mut w = buf.len();
187+
if v == 0 {
188+
w -= 1;
189+
buf[w] = '0' as u8;
190+
} else {
191+
while v > 0 {
192+
w -= 1;
193+
buf[w] = (v % 10) as u8 + '0' as u8;
194+
v /= 10;
195+
}
196+
}
197+
w
198+
}
199+
200+
impl From<time::Duration> for Duration {
201+
fn from(d: time::Duration) -> Self {
202+
Self(d)
203+
}
204+
}
205+
206+
impl Into<time::Duration> for Duration {
207+
fn into(self) -> time::Duration {
208+
self.0
209+
}
210+
}
211+
212+
impl ops::Deref for Duration {
213+
type Target = time::Duration;
214+
215+
fn deref(&self) -> &Self::Target {
216+
&self.0
217+
}
218+
}
219+
220+
impl convert::AsRef<time::Duration> for Duration {
221+
fn as_ref(&self) -> &time::Duration {
222+
&self.0
223+
}
224+
}
225+
226+
#[cfg(test)]
227+
mod tests {
228+
use super::*;
229+
use crate::value::Value;
230+
231+
#[test]
232+
fn format() {
233+
let cases = vec![
234+
("3ns", time::Duration::new(0, 3)),
235+
("3.000001ms", time::Duration::new(0, 3000001)),
236+
("3s", time::Duration::new(3, 0)),
237+
("1m0s", time::Duration::new(60, 0)),
238+
("1h0m0s", time::Duration::new(3600, 0)),
239+
("1h0m0.0001s", time::Duration::new(3600, 100000)),
240+
];
241+
242+
cases
243+
.into_iter()
244+
.map(|(f, d)| (f, Duration::from(d)))
245+
.for_each(|(f, d)| {
246+
assert_eq!(f, format!("{}", d));
247+
let mut d2 = Duration::default();
248+
let res = d2.set(f.to_string());
249+
assert!(res.is_ok());
250+
assert_eq!(d, d2);
251+
})
252+
}
253+
}

0 commit comments

Comments
 (0)