Skip to content

Commit a2704ae

Browse files
authored
Add duration formatter (#2008)
* Add duration formatter * Add duration format to uptime block * Add duration format to tea_timer block * Add duration format to battery block * formatter[duration]: Rename show_leading_units_if_zero to leading_zeroes * formatter[duration]: Rename show_leading_units_if_zero to leading_zeroes in docs
1 parent 41257f9 commit a2704ae

File tree

8 files changed

+647
-59
lines changed

8 files changed

+647
-59
lines changed

src/blocks/battery.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
//! -------------|-------------------------------------------------------------------------|-------------------|-----
2929
//! `icon` | Icon based on battery's state | Icon | -
3030
//! `percentage` | Battery level, in percent | Number | Percents
31-
//! `time` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | String | -
31+
//! `time_remaining` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | Duration | -
32+
//! `time` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | String *DEPRECATED* | -
3233
//! `power` | Power consumption by the battery or from the power supply when charging | String or Float | Watts
3334
//!
35+
//! `time` has been deprecated in favor of `time_remaining`.
36+
//!
3437
//! # Examples
3538
//!
3639
//! Basic usage:
@@ -44,7 +47,7 @@
4447
//! ```toml
4548
//! [[block]]
4649
//! block = "battery"
47-
//! format = " $percentage {$time |}"
50+
//! format = " $percentage {$time_remaining.dur(hms:true, min_unit:m) |}"
4851
//! device = "DisplayDevice"
4952
//! driver = "upower"
5053
//! ```
@@ -163,15 +166,19 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
163166

164167
info.power
165168
.map(|p| values.insert("power".into(), Value::watts(p)));
166-
info.time_remaining.map(|t| {
167-
values.insert(
168-
"time".into(),
169-
Value::text(format!(
170-
"{}:{:02}",
171-
(t / 3600.) as i32,
172-
(t % 3600. / 60.) as i32
173-
)),
174-
)
169+
info.time_remaining.inspect(|&t| {
170+
map! { @extend values
171+
"time" => Value::text(
172+
format!(
173+
"{}:{:02}",
174+
(t / 3600.) as i32,
175+
(t % 3600. / 60.) as i32
176+
),
177+
),
178+
"time_remaining" => Value::duration(
179+
Duration::from_secs(t as u64),
180+
),
181+
}
175182
});
176183

177184
let (icon_name, icon_value, state) = match (info.status, info.capacity) {

src/blocks/tea_timer.rs

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@
44
//!
55
//! Key | Values | Default
66
//! ----|--------|--------
7-
//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$minutes:$seconds \|}\"</code>
7+
//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$time.duration(hms:true) \|}\"</code>
88
//! `increment` | The numbers of seconds to add each time the block is clicked. | 30
99
//! `done_cmd` | A command to run in `sh` when timer finishes. | None
1010
//!
11-
//! Placeholder | Value | Type | Unit
12-
//! -----------------|----------------------------------------------------------------|--------|---------------
13-
//! `icon` | A static icon | Icon | -
14-
//! `hours` | The hours remaining on the timer | Text | h
15-
//! `minutes` | The minutes remaining on the timer | Text | mn
16-
//! `seconds` | The seconds remaining on the timer | Text | s
11+
//! Placeholder | Value | Type | Unit
12+
//! -----------------------|----------------------------------------------------------------|----------|---------------
13+
//! `icon` | A static icon | Icon | -
14+
//! `time` | The time remaining on the timer | Duration | -
15+
//! `hours` *DEPRECATED* | The hours remaining on the timer | Text | h
16+
//! `minutes` *DEPRECATED* | The minutes remaining on the timer | Text | mn
17+
//! `seconds` *DEPRECATED* | The seconds remaining on the timer | Text | s
1718
//!
18-
//! `hours`, `minutes`, and `seconds` are unset when the timer is inactive.
19+
//! `time`, `hours`, `minutes`, and `seconds` are unset when the timer is inactive.
20+
//!
21+
//! `hours`, `minutes`, and `seconds` have been deprecated in favor of `time`.
1922
//!
2023
//! Action | Default button
2124
//! ------------|---------------
@@ -37,13 +40,14 @@
3740
3841
use super::prelude::*;
3942
use crate::subprocess::spawn_shell;
40-
use chrono::{Duration, Utc};
43+
44+
use std::time::{Duration, Instant};
4145

4246
#[derive(Deserialize, Debug, SmartDefault)]
4347
#[serde(deny_unknown_fields, default)]
4448
pub struct Config {
4549
pub format: FormatConfig,
46-
pub increment: Option<i64>,
50+
pub increment: Option<u64>,
4751
pub done_cmd: Option<String>,
4852
}
4953

@@ -59,17 +63,20 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
5963
let interval: Seconds = 1.into();
6064
let mut timer = interval.timer();
6165

62-
let format = config.format.with_default(" $icon {$minutes:$seconds |}")?;
66+
let format = config
67+
.format
68+
.with_default(" $icon {$time.duration(hms:true) |}")?;
6369

64-
let increment =
65-
Duration::try_seconds(config.increment.unwrap_or(30)).error("invalid increment value")?;
66-
let mut timer_end = Utc::now();
70+
let increment = Duration::from_secs(config.increment.unwrap_or(30));
71+
let mut timer_end = Instant::now();
6772

6873
let mut timer_was_active = false;
6974

7075
loop {
71-
let remaining_time = timer_end - Utc::now();
72-
let is_timer_active = remaining_time > Duration::zero();
76+
let mut widget = Widget::new().with_format(format.clone());
77+
78+
let remaining_time = timer_end - Instant::now();
79+
let is_timer_active = !remaining_time.is_zero();
7380

7481
if !is_timer_active && timer_was_active {
7582
if let Some(cmd) = &config.done_cmd {
@@ -78,32 +85,38 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
7885
}
7986
timer_was_active = is_timer_active;
8087

81-
let (hours, minutes, seconds) = if is_timer_active {
82-
(
83-
remaining_time.num_hours(),
84-
remaining_time.num_minutes() % 60,
85-
remaining_time.num_seconds() % 60,
86-
)
87-
} else {
88-
(0, 0, 0)
89-
};
88+
let mut values = map!(
89+
"icon" => Value::icon("tea"),
90+
);
9091

91-
let mut widget = Widget::new().with_format(format.clone());
92+
if is_timer_active {
93+
values.insert("time".into(), Value::duration(remaining_time));
94+
let mut seconds = remaining_time.as_secs();
9295

93-
widget.set_values(map!(
94-
"icon" => Value::icon("tea"),
95-
[if is_timer_active] "hours" => Value::text(format!("{hours:02}")),
96-
[if is_timer_active] "minutes" => Value::text(format!("{minutes:02}")),
97-
[if is_timer_active] "seconds" => Value::text(format!("{seconds:02}")),
98-
));
96+
if format.contains_key("hours") {
97+
let hours = seconds / 3_600;
98+
values.insert("hours".into(), Value::text(format!("{hours:02}")));
99+
seconds %= 3_600;
100+
}
101+
102+
if format.contains_key("minutes") {
103+
let minutes = seconds / 60;
104+
values.insert("minutes".into(), Value::text(format!("{minutes:02}")));
105+
seconds %= 60;
106+
}
107+
108+
values.insert("seconds".into(), Value::text(format!("{seconds:02}")));
109+
}
110+
111+
widget.set_values(values);
99112

100113
api.set_widget(widget)?;
101114

102115
select! {
103116
_ = timer.tick(), if is_timer_active => (),
104117
_ = api.wait_for_update_request() => (),
105118
Some(action) = actions.recv() => {
106-
let now = Utc::now();
119+
let now = Instant::now();
107120
match action.as_ref() {
108121
"increment" if is_timer_active => timer_end += increment,
109122
"increment" => timer_end = now + increment,

src/blocks/uptime.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
//!
88
//! Key | Values | Default
99
//! -----------|----------------------------|--------
10-
//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $text "`
10+
//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $uptime "`
1111
//! `interval` | Update interval in seconds | `60`
1212
//!
13-
//! Placeholder | Value | Type | Unit
14-
//! --------------|-------------------------|--------|-----
15-
//! `icon` | A static icon | Icon | -
16-
//! `text` | Current uptime | Text | -
13+
//! Placeholder | Value | Type | Unit
14+
//! --------------------|-------------------------|----------|-----
15+
//! `icon` | A static icon | Icon | -
16+
//! `text` *DEPRECATED* | Current uptime | Text | -
17+
//! `uptime` | Current uptime | Duration | -
18+
//!
19+
//! `text` has been deprecated in favor of `uptime`.
1720
//!
1821
//! # Example
1922
//!
@@ -25,9 +28,6 @@
2528
//!
2629
//! # Used Icons
2730
//! - `uptime`
28-
//!
29-
//! # TODO:
30-
//! - Add `time` or `dur` formatter to `src/formatting/formatter.rs`
3131
3232
use super::prelude::*;
3333
use tokio::fs::read_to_string;
@@ -41,7 +41,7 @@ pub struct Config {
4141
}
4242

4343
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
44-
let format = config.format.with_default(" $icon $text ")?;
44+
let format = config.format.with_default(" $icon $uptime ")?;
4545

4646
loop {
4747
let uptime = read_to_string("/proc/uptime")
@@ -53,6 +53,8 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
5353
.and_then(|u| u.parse().ok())
5454
.error("/proc/uptime has invalid content")?;
5555

56+
let uptime = Duration::from_secs(seconds);
57+
5658
let weeks = seconds / 604_800;
5759
seconds %= 604_800;
5860
let days = seconds / 86_400;
@@ -75,7 +77,8 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
7577
let mut widget = Widget::new().with_format(format.clone());
7678
widget.set_values(map! {
7779
"icon" => Value::icon("uptime"),
78-
"text" => Value::text(text)
80+
"text" => Value::text(text),
81+
"uptime" => Value::duration(uptime)
7982
});
8083
api.set_widget(widget)?;
8184

src/formatting.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
//! --------------------------|------------------
1818
//! Text | `str`
1919
//! Number | `eng`
20+
//! Datetime | `datetime`
21+
//! Duration | `duration`
2022
//! [Flag](#how-to-use-flags) | N/A
2123
//!
2224
//! # Formatters
@@ -80,6 +82,30 @@
8082
//! `format` or `f` | [chrono docs](https://docs.rs/chrono/0.3.0/chrono/format/strftime/index.html#specifiers) for all options. | `'%a %d/%m %R'`
8183
//! `locale` or `l` | Locale to apply when formatting the time | System locale
8284
//!
85+
//!
86+
//! ## `duration`/`dur` - Format durations
87+
//!
88+
//! Argument | Description |Default value
89+
//! -----------------|--------------------------------------------------------------------------------------------------|------------------------------------------------------
90+
//! `hms` | Should the format be hours:minutes:seconds.milliseconds | `false`
91+
//! `max_unit` | The largest unit to display the duration with (see below for the list of all possible units) | hms ? `h` : `y`
92+
//! `min_unit` | The smallest unit to display the duration with (see below for the list of all possible units) | `s`
93+
//! `units` | The number of units to display | min(# of units between `max_unit` and `min_unit``, 2)
94+
//! `round_up` | Round up to the nearest minimum displayed unit | `true`
95+
//! `unit_space` | Should there be a space between the value and unit symbol (not allowed when `hms:true`) | `false`
96+
//! `pad_with` | The character that is used to pad the numbers | hms ? `0` : ` ` (a space)
97+
//! `leading_zeroes` | If fewer than `units` are non-zero should leading numbers that have a value of zero be shown | `true`
98+
//!
99+
//! Unit | Description
100+
//! -----|------------
101+
//! y | years
102+
//! w | weeks
103+
//! d | days
104+
//! h | hours
105+
//! m | minutes
106+
//! s | seconds
107+
//! ms | milliseconds
108+
//!
83109
//! # Handling missing placeholders and incorrect types
84110
//!
85111
//! Some blocks allow missing placeholders, for example [bluetooth](crate::blocks::bluetooth)'s

src/formatting/formatter.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use unicode_segmentation::UnicodeSegmentation;
22

3-
use std::fmt::Debug;
43
use std::time::Duration;
4+
use std::{borrow::Cow, fmt::Debug};
55

66
use super::parse::Arg;
77
use super::value::ValueInner as Value;
@@ -14,7 +14,7 @@ use crate::errors::*;
1414
#[macro_export]
1515
macro_rules! new_fmt {
1616
($name:ident) => {{
17-
fmt!($name,)
17+
new_fmt!($name,)
1818
}};
1919
($name:ident, $($key:ident : $value:tt),* $(,)?) => {
2020
new_formatter(stringify!($name), &[
@@ -29,6 +29,8 @@ mod tally;
2929
pub use tally::TallyFormatter;
3030
mod datetime;
3131
pub use datetime::{DatetimeFormatter, DEFAULT_DATETIME_FORMATTER};
32+
mod duration;
33+
pub use duration::{DurationFormatter, DEFAULT_DURATION_FORMATTER};
3234
mod eng;
3335
pub use eng::{EngFormatter, DEFAULT_NUMBER_FORMATTER};
3436
mod flag;
@@ -38,6 +40,10 @@ pub use pango::PangoStrFormatter;
3840
mod str;
3941
pub use str::{StrFormatter, DEFAULT_STRING_FORMATTER};
4042

43+
type PadWith = Cow<'static, str>;
44+
45+
const DEFAULT_NUMBER_PAD_WITH: PadWith = Cow::Borrowed(" ");
46+
4147
pub trait Formatter: Debug + Send + Sync {
4248
fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError>;
4349

@@ -50,6 +56,7 @@ pub fn new_formatter(name: &str, args: &[Arg]) -> Result<Box<dyn Formatter>> {
5056
match name {
5157
"bar" => Ok(Box::new(BarFormatter::from_args(args)?)),
5258
"datetime" => Ok(Box::new(DatetimeFormatter::from_args(args)?)),
59+
"dur" | "duration" => Ok(Box::new(DurationFormatter::from_args(args)?)),
5360
"eng" => Ok(Box::new(EngFormatter::from_args(args)?)),
5461
"pango-str" => Ok(Box::new(PangoStrFormatter::from_args(args)?)),
5562
"str" => Ok(Box::new(StrFormatter::from_args(args)?)),

0 commit comments

Comments
 (0)