Skip to content

Commit

Permalink
Add duration formatter (#2008)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bim9262 authored Sep 7, 2024
1 parent 41257f9 commit a2704ae
Show file tree
Hide file tree
Showing 8 changed files with 647 additions and 59 deletions.
29 changes: 18 additions & 11 deletions src/blocks/battery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@
//! -------------|-------------------------------------------------------------------------|-------------------|-----
//! `icon` | Icon based on battery's state | Icon | -
//! `percentage` | Battery level, in percent | Number | Percents
//! `time` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | String | -
//! `time_remaining` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | Duration | -
//! `time` | Time remaining until (dis)charge is complete. Presented only if battery's status is (dis)charging. | String *DEPRECATED* | -
//! `power` | Power consumption by the battery or from the power supply when charging | String or Float | Watts
//!
//! `time` has been deprecated in favor of `time_remaining`.
//!
//! # Examples
//!
//! Basic usage:
Expand All @@ -44,7 +47,7 @@
//! ```toml
//! [[block]]
//! block = "battery"
//! format = " $percentage {$time |}"
//! format = " $percentage {$time_remaining.dur(hms:true, min_unit:m) |}"
//! device = "DisplayDevice"
//! driver = "upower"
//! ```
Expand Down Expand Up @@ -163,15 +166,19 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {

info.power
.map(|p| values.insert("power".into(), Value::watts(p)));
info.time_remaining.map(|t| {
values.insert(
"time".into(),
Value::text(format!(
"{}:{:02}",
(t / 3600.) as i32,
(t % 3600. / 60.) as i32
)),
)
info.time_remaining.inspect(|&t| {
map! { @extend values
"time" => Value::text(
format!(
"{}:{:02}",
(t / 3600.) as i32,
(t % 3600. / 60.) as i32
),
),
"time_remaining" => Value::duration(
Duration::from_secs(t as u64),
),
}
});

let (icon_name, icon_value, state) = match (info.status, info.capacity) {
Expand Down
79 changes: 46 additions & 33 deletions src/blocks/tea_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@
//!
//! Key | Values | Default
//! ----|--------|--------
//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$minutes:$seconds \|}\"</code>
//! `format` | A string to customise the output of this block. See below for available placeholders. | <code>\" $icon {$time.duration(hms:true) \|}\"</code>
//! `increment` | The numbers of seconds to add each time the block is clicked. | 30
//! `done_cmd` | A command to run in `sh` when timer finishes. | None
//!
//! Placeholder | Value | Type | Unit
//! -----------------|----------------------------------------------------------------|--------|---------------
//! `icon` | A static icon | Icon | -
//! `hours` | The hours remaining on the timer | Text | h
//! `minutes` | The minutes remaining on the timer | Text | mn
//! `seconds` | The seconds remaining on the timer | Text | s
//! Placeholder | Value | Type | Unit
//! -----------------------|----------------------------------------------------------------|----------|---------------
//! `icon` | A static icon | Icon | -
//! `time` | The time remaining on the timer | Duration | -
//! `hours` *DEPRECATED* | The hours remaining on the timer | Text | h
//! `minutes` *DEPRECATED* | The minutes remaining on the timer | Text | mn
//! `seconds` *DEPRECATED* | The seconds remaining on the timer | Text | s
//!
//! `hours`, `minutes`, and `seconds` are unset when the timer is inactive.
//! `time`, `hours`, `minutes`, and `seconds` are unset when the timer is inactive.
//!
//! `hours`, `minutes`, and `seconds` have been deprecated in favor of `time`.
//!
//! Action | Default button
//! ------------|---------------
Expand All @@ -37,13 +40,14 @@

use super::prelude::*;
use crate::subprocess::spawn_shell;
use chrono::{Duration, Utc};

use std::time::{Duration, Instant};

#[derive(Deserialize, Debug, SmartDefault)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub format: FormatConfig,
pub increment: Option<i64>,
pub increment: Option<u64>,
pub done_cmd: Option<String>,
}

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

let format = config.format.with_default(" $icon {$minutes:$seconds |}")?;
let format = config
.format
.with_default(" $icon {$time.duration(hms:true) |}")?;

let increment =
Duration::try_seconds(config.increment.unwrap_or(30)).error("invalid increment value")?;
let mut timer_end = Utc::now();
let increment = Duration::from_secs(config.increment.unwrap_or(30));
let mut timer_end = Instant::now();

let mut timer_was_active = false;

loop {
let remaining_time = timer_end - Utc::now();
let is_timer_active = remaining_time > Duration::zero();
let mut widget = Widget::new().with_format(format.clone());

let remaining_time = timer_end - Instant::now();
let is_timer_active = !remaining_time.is_zero();

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

let (hours, minutes, seconds) = if is_timer_active {
(
remaining_time.num_hours(),
remaining_time.num_minutes() % 60,
remaining_time.num_seconds() % 60,
)
} else {
(0, 0, 0)
};
let mut values = map!(
"icon" => Value::icon("tea"),
);

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

widget.set_values(map!(
"icon" => Value::icon("tea"),
[if is_timer_active] "hours" => Value::text(format!("{hours:02}")),
[if is_timer_active] "minutes" => Value::text(format!("{minutes:02}")),
[if is_timer_active] "seconds" => Value::text(format!("{seconds:02}")),
));
if format.contains_key("hours") {
let hours = seconds / 3_600;
values.insert("hours".into(), Value::text(format!("{hours:02}")));
seconds %= 3_600;
}

if format.contains_key("minutes") {
let minutes = seconds / 60;
values.insert("minutes".into(), Value::text(format!("{minutes:02}")));
seconds %= 60;
}

values.insert("seconds".into(), Value::text(format!("{seconds:02}")));
}

widget.set_values(values);

api.set_widget(widget)?;

select! {
_ = timer.tick(), if is_timer_active => (),
_ = api.wait_for_update_request() => (),
Some(action) = actions.recv() => {
let now = Utc::now();
let now = Instant::now();
match action.as_ref() {
"increment" if is_timer_active => timer_end += increment,
"increment" => timer_end = now + increment,
Expand Down
23 changes: 13 additions & 10 deletions src/blocks/uptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
//!
//! Key | Values | Default
//! -----------|----------------------------|--------
//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $text "`
//! `format` | A string to customise the output of this block. See below for available placeholders | `" $icon $uptime "`
//! `interval` | Update interval in seconds | `60`
//!
//! Placeholder | Value | Type | Unit
//! --------------|-------------------------|--------|-----
//! `icon` | A static icon | Icon | -
//! `text` | Current uptime | Text | -
//! Placeholder | Value | Type | Unit
//! --------------------|-------------------------|----------|-----
//! `icon` | A static icon | Icon | -
//! `text` *DEPRECATED* | Current uptime | Text | -
//! `uptime` | Current uptime | Duration | -
//!
//! `text` has been deprecated in favor of `uptime`.
//!
//! # Example
//!
Expand All @@ -25,9 +28,6 @@
//!
//! # Used Icons
//! - `uptime`
//!
//! # TODO:
//! - Add `time` or `dur` formatter to `src/formatting/formatter.rs`

use super::prelude::*;
use tokio::fs::read_to_string;
Expand All @@ -41,7 +41,7 @@ pub struct Config {
}

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

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

let uptime = Duration::from_secs(seconds);

let weeks = seconds / 604_800;
seconds %= 604_800;
let days = seconds / 86_400;
Expand All @@ -75,7 +77,8 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let mut widget = Widget::new().with_format(format.clone());
widget.set_values(map! {
"icon" => Value::icon("uptime"),
"text" => Value::text(text)
"text" => Value::text(text),
"uptime" => Value::duration(uptime)
});
api.set_widget(widget)?;

Expand Down
26 changes: 26 additions & 0 deletions src/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
//! --------------------------|------------------
//! Text | `str`
//! Number | `eng`
//! Datetime | `datetime`
//! Duration | `duration`
//! [Flag](#how-to-use-flags) | N/A
//!
//! # Formatters
Expand Down Expand Up @@ -80,6 +82,30 @@
//! `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'`
//! `locale` or `l` | Locale to apply when formatting the time | System locale
//!
//!
//! ## `duration`/`dur` - Format durations
//!
//! Argument | Description |Default value
//! -----------------|--------------------------------------------------------------------------------------------------|------------------------------------------------------
//! `hms` | Should the format be hours:minutes:seconds.milliseconds | `false`
//! `max_unit` | The largest unit to display the duration with (see below for the list of all possible units) | hms ? `h` : `y`
//! `min_unit` | The smallest unit to display the duration with (see below for the list of all possible units) | `s`
//! `units` | The number of units to display | min(# of units between `max_unit` and `min_unit``, 2)
//! `round_up` | Round up to the nearest minimum displayed unit | `true`
//! `unit_space` | Should there be a space between the value and unit symbol (not allowed when `hms:true`) | `false`
//! `pad_with` | The character that is used to pad the numbers | hms ? `0` : ` ` (a space)
//! `leading_zeroes` | If fewer than `units` are non-zero should leading numbers that have a value of zero be shown | `true`
//!
//! Unit | Description
//! -----|------------
//! y | years
//! w | weeks
//! d | days
//! h | hours
//! m | minutes
//! s | seconds
//! ms | milliseconds
//!
//! # Handling missing placeholders and incorrect types
//!
//! Some blocks allow missing placeholders, for example [bluetooth](crate::blocks::bluetooth)'s
Expand Down
11 changes: 9 additions & 2 deletions src/formatting/formatter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use unicode_segmentation::UnicodeSegmentation;

use std::fmt::Debug;
use std::time::Duration;
use std::{borrow::Cow, fmt::Debug};

use super::parse::Arg;
use super::value::ValueInner as Value;
Expand All @@ -14,7 +14,7 @@ use crate::errors::*;
#[macro_export]
macro_rules! new_fmt {
($name:ident) => {{
fmt!($name,)
new_fmt!($name,)
}};
($name:ident, $($key:ident : $value:tt),* $(,)?) => {
new_formatter(stringify!($name), &[
Expand All @@ -29,6 +29,8 @@ mod tally;
pub use tally::TallyFormatter;
mod datetime;
pub use datetime::{DatetimeFormatter, DEFAULT_DATETIME_FORMATTER};
mod duration;
pub use duration::{DurationFormatter, DEFAULT_DURATION_FORMATTER};
mod eng;
pub use eng::{EngFormatter, DEFAULT_NUMBER_FORMATTER};
mod flag;
Expand All @@ -38,6 +40,10 @@ pub use pango::PangoStrFormatter;
mod str;
pub use str::{StrFormatter, DEFAULT_STRING_FORMATTER};

type PadWith = Cow<'static, str>;

const DEFAULT_NUMBER_PAD_WITH: PadWith = Cow::Borrowed(" ");

pub trait Formatter: Debug + Send + Sync {
fn format(&self, val: &Value, config: &SharedConfig) -> Result<String, FormatError>;

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

0 comments on commit a2704ae

Please sign in to comment.