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//! Note: for arguments that accept a boolean value, just specifying the argument will be treated as `arg:true`.
32//!
33//! ## `str` - Format text
34//!
35//! Argument | Description |Default value
36//! -----------------------|---------------------------------------------------|-------------
37//! `min_width` or `min_w` | if text is shorter it will be padded using spaces | `0`
38//! `max_width` or `max_w` | if text is longer it will be truncated | Infinity
39//! `width` or `w` | Text will be exactly this length by padding or truncating as needed | N/A
40//! `rot_interval` | if text is longer than `max_width` it will be rotated every `rot_interval` seconds, if set | None
41//! `rot_separator` | if text is longer than `max_width` it will be rotated with this seporator | <code>\"\|\"</code>
42//!
43//! Note: width just changes the values of both min_width and max_width to be the same. Use width
44//! if you want the values to be the same, or the other two otherwise. Don't mix width with
45//! min_width or max_width.
46//!
47//! ## `eng` - Format numbers using engineering notation
48//!
49//! Argument | Description |Default value
50//! ----------------|--------------------------------------------------------------------------------------------------|-------------
51//! `width` or `w` | the resulting text will be at least `width` characters long | `2`
52//! `unit` or `u` | some values have a [unit](unit::Unit), and it is possible to convert them by setting this option | N/A
53//! `hide_unit` | hide the unit symbol | `false`
54//! `unit_space` | have a whitespace before unit symbol | `false`
55//! `prefix` or `p` | specify this argument if you want to set the minimal [SI prefix](prefix::Prefix) | N/A
56//! `hide_prefix` | hide the prefix symbol | `false`
57//! `prefix_space` | have a whitespace before prefix symbol | `false`
58//! `force_prefix` | force the prefix value instead of setting a "minimal prefix" | `false`
59//! `pad_with` | the character that is used to pad the number to be `width` long | ` ` (a space)
60//! `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. | `..`
61//! `show` | show this value. Can be used with `range` for conditional formatting | `true`
62//!
63//! ## `bar` - Display numbers as progress bars
64//!
65//! Argument | Description |Default value
66//! -----------------------|---------------------------------------------------------------------------------|-------------------------
67//! `width` or `w` | the width of the bar (in characters) | `5` (`1` for `vertical`)
68//! `max_value` | which value is treated as "full". For example, for battery level `100` is full. | `100`
69//! `vertical` or `v` | whether to render the bar vertically or not | `false`
70//!
71//! ## `tally` - Display numbers as tally marks
72//!
73//! Argument | Description |Default value
74//! ---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------
75//! `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
76//!
77//! ## `pango-str` - Just display the text without pango markup escaping
78//!
79//! No arguments.
80//!
81//! ## `datetime` - Display datetime
82//!
83//! Argument | Description |Default value
84//! -----------------------|-----------------------------------------------------------------------------------------------------------|-------------
85//! `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'`
86//! `locale` or `l` | Locale to apply when formatting the time | System locale
87//!
88//!
89//! ## `duration`/`dur` - Format durations
90//!
91//! Argument | Description |Default value
92//! -----------------|--------------------------------------------------------------------------------------------------|------------------------------------------------------
93//! `hms` | Should the format be hours:minutes:seconds.milliseconds | `false`
94//! `max_unit` | The largest unit to display the duration with (see below for the list of all possible units) | hms ? `h` : `y`
95//! `min_unit` | The smallest unit to display the duration with (see below for the list of all possible units) | `s`
96//! `units` | The number of units to display | min(# of units between `max_unit` and `min_unit``, 2)
97//! `round_up` | Round up to the nearest minimum displayed unit | `true`
98//! `unit_space` | Should there be a space between the value and unit symbol (not allowed when `hms:true`) | `false`
99//! `pad_with` | The character that is used to pad the numbers | hms ? `0` : ` ` (a space)
100//! `leading_zeroes` | If fewer than `units` are non-zero should leading numbers that have a value of zero be shown | `true`
101//!
102//! Unit | Description
103//! -----|------------
104//! y | years
105//! w | weeks
106//! d | days
107//! h | hours
108//! m | minutes
109//! s | seconds
110//! ms | milliseconds
111//!
112//! # Handling missing placeholders and incorrect types
113//!
114//! Some blocks allow missing placeholders, for example [bluetooth](crate::blocks::bluetooth)'s
115//! "percentage" may be absent if the device is not supported. To handle such cases it is possible
116//! to queue multiple formats together by using `|` symbol: `<something that can fail>|<otherwise
117//! try this>|<or this>`.
118//!
119//! In addition, formats can be recursive. To set a format inside of another format, place it
120//! inside of `{}`. For example, in `Percentage: {$percentage|N/A}` the text "Percentage: " will be
121//! always displayed, followed by the actual percentage or "N/A" in case percentage is not
122//! available. This example does exactly the same thing as `Percentage: $percentage|Percentage: N/A`
123//!
124//! # How to use flags
125//!
126//! Some blocks provide flags, which can be used to change the format based on some criteria. For
127//! example, [taskwarrior](crate::blocks::taskwarrior) defines `done` if the count is zero. In
128//! general, flags are used in this way:
129//!
130//! ```text
131//! $a{a is set}|$b$c{b and c are set}|${b|c}{b or c is set}|neither flag is set
132//! ```
133
134pub mod config;
135pub mod formatter;
136pub mod parse;
137pub mod prefix;
138pub mod scheduling;
139pub mod template;
140pub mod unit;
141pub mod value;
142
143use std::borrow::Cow;
144use std::collections::HashMap;
145
146use crate::config::SharedConfig;
147use crate::errors::*;
148use template::FormatTemplate;
149use value::Value;
150
151pub type Values = HashMap<Cow<'static, str>, Value>;
152
153#[derive(Debug, thiserror::Error)]
154pub enum FormatError {
155 #[error("Placeholder '{0}' not found")]
156 PlaceholderNotFound(String),
157 #[error("{} cannot be formatted with '{}' formatter", .ty, .fmt)]
158 IncompatibleFormatter { ty: &'static str, fmt: &'static str },
159 #[error("Number {0} is out of range")]
160 NumberOutOfRange(f64),
161 #[error(transparent)]
162 Other(#[from] Error),
163}
164
165#[derive(Debug, Clone)]
166pub struct Format {
167 full: FormatTemplate,
168 short: FormatTemplate,
169 intervals: Vec<u64>,
170}
171
172impl Format {
173 pub fn contains_key(&self, key: &str) -> bool {
174 self.full.contains_key(key) || self.short.contains_key(key)
175 }
176
177 pub fn intervals(&self) -> Vec<u64> {
178 self.intervals.clone()
179 }
180
181 pub fn render(
182 &self,
183 values: &Values,
184 config: &SharedConfig,
185 ) -> Result<(Vec<Fragment>, Vec<Fragment>)> {
186 let full = self
187 .full
188 .render(values, config)
189 .error("Failed to render full text")?;
190 let short = self
191 .short
192 .render(values, config)
193 .error("Failed to render short text")?;
194 Ok((full, short))
195 }
196}
197
198#[derive(Debug, Default, Clone)]
199pub struct Fragment {
200 pub text: String,
201 pub metadata: Metadata,
202}
203
204impl From<String> for Fragment {
205 fn from(text: String) -> Self {
206 Self {
207 text,
208 metadata: Default::default(),
209 }
210 }
211}
212
213impl Fragment {
214 pub fn formatted_text(&self) -> String {
215 match (self.metadata.italic, self.metadata.underline) {
216 (true, true) => format!("<i><u>{}</u></i>", self.text),
217 (false, true) => format!("<u>{}</u>", self.text),
218 (true, false) => format!("<i>{}</i>", self.text),
219 (false, false) => self.text.clone(),
220 }
221 }
222}
223
224#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
225pub struct Metadata {
226 pub instance: Option<&'static str>,
227 pub underline: bool,
228 pub italic: bool,
229}
230
231impl Metadata {
232 pub fn is_default(&self) -> bool {
233 *self == Default::default()
234 }
235}