i3status_rs/formatting.rs
1//! # Formatting system
2//! Many blocks have a `format` configuration option, which allows to heavily customize the block's
3//! appearance. In short, each block with `format` option provides a set of values, which are
4//! displayed according to `format`. `format`'s value is just a text with embedded variables.
5//! Similarly to PHP and shell, variable name must start with a `$`:
6//! `this is a variable: -> $var <-`.
7//!
8//! Also, format strings can embed icons. For example, `^icon_ping` in `" ^icon_ping $ping "` gets
9//! substituted with a "ping" icon from your icon set. For a complete list of icons, see
10//! [this](https://github.com/greshake/i3status-rust/blob/master/doc/themes.md#available-icon-overrides).
11//!
12//! # Types
13//!
14//! The allowed types of variables are:
15//!
16//! Type | Default formatter
17//! --------------------------|------------------
18//! Text | `str`
19//! Number | `eng`
20//! Datetime | `datetime`
21//! Duration | `duration`
22//! [Flag](#how-to-use-flags) | N/A
23//!
24//! # Formatters
25//!
26//! A formatter is something that converts a value into a text. Because there are many ways to do
27//! this, a number of formatters is available. Formatter can be specified using the syntax similar
28//! to method calls in many programming languages: `<variable>.<formatter>(<args>)`. For example:
29//! `$title.str(min_w:10, max_w:20)`.
30//!
31//! ## `str` - Format text
32//!
33//! Argument | Description |Default value
34//! -----------------------|---------------------------------------------------|-------------
35//! `min_width` or `min_w` | if text is shorter it will be padded using spaces | `0`
36//! `max_width` or `max_w` | if text is longer it will be truncated | Infinity
37//! `width` or `w` | Text will be exactly this length by padding or truncating as needed | N/A
38//! `rot_interval` | if text is longer than `max_width` it will be rotated every `rot_interval` seconds, if set | None
39//! `rot_separator` | if text is longer than `max_width` it will be rotated with this seporator | <code>\"\|\"</code>
40//!
41//! Note: width just changes the values of both min_width and max_width to be the same. Use width
42//! if you want the values to be the same, or the other two otherwise. Don't mix width with
43//! min_width or max_width.
44//!
45//! ## `eng` - Format numbers using engineering notation
46//!
47//! Argument | Description |Default value
48//! ----------------|--------------------------------------------------------------------------------------------------|-------------
49//! `width` or `w` | the resulting text will be at least `width` characters long | `2`
50//! `unit` or `u` | some values have a [unit](unit::Unit), and it is possible to convert them by setting this option | N/A
51//! `hide_unit` | hide the unit symbol | `false`
52//! `unit_space` | have a whitespace before unit symbol | `false`
53//! `prefix` or `p` | specify this argument if you want to set the minimal [SI prefix](prefix::Prefix) | N/A
54//! `hide_prefix` | hide the prefix symbol | `false`
55//! `prefix_space` | have a whitespace before prefix symbol | `false`
56//! `force_prefix` | force the prefix value instead of setting a "minimal prefix" | `false`
57//! `pad_with` | the character that is used to pad the number to be `width` long | ` ` (a space)
58//! `range` | a range of allowed values, in the format `<start>..<end>`, inclusive. Both start and end are optional. Can be used to, for example, hide the block when the value is not in a given range. | `..`
59//!
60//! ## `bar` - Display numbers as progress bars
61//!
62//! Argument | Description |Default value
63//! -----------------------|---------------------------------------------------------------------------------|-------------------------
64//! `width` or `w` | the width of the bar (in characters) | `5` (`1` for `vertical`)
65//! `max_value` | which value is treated as "full". For example, for battery level `100` is full. | `100`
66//! `vertical` or `v` | whether to render the bar vertically or not | `false`
67//!
68//! ## `tally` - Display numbers as tally marks
69//!
70//! Argument | Description |Default value
71//! ---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------
72//! `style` or `s` | One of [`chinese_counting_rods`/`ccr`](https://en.wikipedia.org/wiki/Counting_rods), [`chinese_tally`/`ct`, `western_tally`/`wt`, `western_tally_ungrouped`/`wtu`](https://en.wikipedia.org/wiki/Tally_marks) | western_tally
73//!
74//! ## `pango-str` - Just display the text without pango markup escaping
75//!
76//! No arguments.
77//!
78//! ## `datetime` - Display datetime
79//!
80//! Argument | Description |Default value
81//! -----------------------|-----------------------------------------------------------------------------------------------------------|-------------
82//! `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'`
83//! `locale` or `l` | Locale to apply when formatting the time | System locale
84//!
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//!
109//! # Handling missing placeholders and incorrect types
110//!
111//! Some blocks allow missing placeholders, for example [bluetooth](crate::blocks::bluetooth)'s
112//! "percentage" may be absent if the device is not supported. To handle such cases it is possible
113//! to queue multiple formats together by using `|` symbol: `<something that can fail>|<otherwise
114//! try this>|<or this>`.
115//!
116//! In addition, formats can be recursive. To set a format inside of another format, place it
117//! inside of `{}`. For example, in `Percentage: {$percentage|N/A}` the text "Percentage: " will be
118//! always displayed, followed by the actual percentage or "N/A" in case percentage is not
119//! available. This example does exactly the same thing as `Percentage: $percentage|Percentage: N/A`
120//!
121//! # How to use flags
122//!
123//! Some blocks provide flags, which can be used to change the format based on some criteria. For
124//! example, [taskwarrior](crate::blocks::taskwarrior) defines `done` if the count is zero. In
125//! general, flags are used in this way:
126//!
127//! ```text
128//! $a{a is set}|$b$c{b and c are set}|${b|c}{b or c is set}|neither flag is set
129//! ```
130
131pub mod config;
132pub mod formatter;
133pub mod parse;
134pub mod prefix;
135pub mod scheduling;
136pub mod template;
137pub mod unit;
138pub mod value;
139
140use std::borrow::Cow;
141use std::collections::HashMap;
142
143use crate::config::SharedConfig;
144use crate::errors::*;
145use template::FormatTemplate;
146use value::Value;
147
148pub type Values = HashMap<Cow<'static, str>, Value>;
149
150#[derive(Debug, thiserror::Error)]
151pub enum FormatError {
152 #[error("Placeholder '{0}' not found")]
153 PlaceholderNotFound(String),
154 #[error("{} cannot be formatted with '{}' formatter", .ty, .fmt)]
155 IncompatibleFormatter { ty: &'static str, fmt: &'static str },
156 #[error("Number {0} is out of range")]
157 NumberOutOfRange(f64),
158 #[error(transparent)]
159 Other(#[from] Error),
160}
161
162#[derive(Debug, Clone)]
163pub struct Format {
164 full: FormatTemplate,
165 short: FormatTemplate,
166 intervals: Vec<u64>,
167}
168
169impl Format {
170 pub fn contains_key(&self, key: &str) -> bool {
171 self.full.contains_key(key) || self.short.contains_key(key)
172 }
173
174 pub fn intervals(&self) -> Vec<u64> {
175 self.intervals.clone()
176 }
177
178 pub fn render(
179 &self,
180 values: &Values,
181 config: &SharedConfig,
182 ) -> Result<(Vec<Fragment>, Vec<Fragment>)> {
183 let full = self
184 .full
185 .render(values, config)
186 .error("Failed to render full text")?;
187 let short = self
188 .short
189 .render(values, config)
190 .error("Failed to render short text")?;
191 Ok((full, short))
192 }
193}
194
195#[derive(Debug, Default, Clone)]
196pub struct Fragment {
197 pub text: String,
198 pub metadata: Metadata,
199}
200
201impl From<String> for Fragment {
202 fn from(text: String) -> Self {
203 Self {
204 text,
205 metadata: Default::default(),
206 }
207 }
208}
209
210impl Fragment {
211 pub fn formatted_text(&self) -> String {
212 match (self.metadata.italic, self.metadata.underline) {
213 (true, true) => format!("<i><u>{}</u></i>", self.text),
214 (false, true) => format!("<u>{}</u>", self.text),
215 (true, false) => format!("<i>{}</i>", self.text),
216 (false, false) => self.text.clone(),
217 }
218 }
219}
220
221#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
222pub struct Metadata {
223 pub instance: Option<&'static str>,
224 pub underline: bool,
225 pub italic: bool,
226}
227
228impl Metadata {
229 pub fn is_default(&self) -> bool {
230 *self == Default::default()
231 }
232}