1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! # Formatting system
//! Many blocks have a `format` configuration option, which allows to heavily customize the block's
//! appearance. In short, each block with `format` option provides a set of values, which are
//! displayed according to `format`. `format`'s value is just a text with embedded variables.
//! Similarly to PHP and shell, variable name must start with a `$`:
//! `this is a variable: -> $var <-`.
//!
//! Also, format strings can embed icons. For example, `^icon_ping` in `" ^icon_ping $ping "` gets
//! substituted with a "ping" icon from your icon set. For a complete list of icons, see
//! [this](https://github.com/greshake/i3status-rust/blob/master/doc/themes.md#available-icon-overrides).
//!
//! # Types
//!
//! The allowed types of variables are:
//!
//! Type                      | Default formatter
//! --------------------------|------------------
//! Text                      | `str`
//! Number                    | `eng`
//! [Flag](#how-to-use-flags) | N/A
//!
//! # Formatters
//!
//! A formatter is something that converts a value into a text. Because there are many ways to do
//! this, a number of formatters is available. Formatter can be specified using the syntax similar
//! to method calls in many programming languages: `<variable>.<formatter>(<args>)`. For example:
//! `$title.str(min_w:10, max_w:20)`.
//!
//! ## `str` - Format text
//!
//! Argument               | Description                                       |Default value
//! -----------------------|---------------------------------------------------|-------------
//! `min_width` or `min_w` | if text is shorter it will be padded using spaces | `0`
//! `max_width` or `max_w` | if text is longer it will be truncated            | Infinity
//! `width` or `w`         | Text will be exactly this length by padding or truncating as needed | N/A
//! `rot_interval`         | if text is longer than `max_width` it will be rotated every `rot_interval` seconds, if set | None
//! `rot_separator`        | if text is longer than `max_width` it will be rotated with this seporator | <code>\"\|\"</code>
//!
//! Note: width just changes the values of both min_width and max_width to be the same. Use width
//! if you want the values to be the same, or the other two otherwise. Don't mix width with
//! min_width or max_width.
//!
//! ## `eng` - Format numbers using engineering notation
//!
//! Argument        | Description                                                                                      |Default value
//! ----------------|--------------------------------------------------------------------------------------------------|-------------
//! `width` or `w`  | the resulting text will be at least `width` characters long                                      | `2`
//! `unit` or `u`   | some values have a [unit](unit::Unit), and it is possible to convert them by setting this option | N/A
//! `hide_unit`     | hide the unit symbol                                                                             | `false`
//! `unit_space`    | have a whitespace before unit symbol                                                             | `false`
//! `prefix` or `p` | specify this argument if you want to set the minimal [SI prefix](prefix::Prefix)                 | N/A
//! `hide_prefix`   | hide the prefix symbol                                                                           | `false`
//! `prefix_space`  | have a whitespace before prefix symbol                                                           | `false`
//! `force_prefix`  | force the prefix value instead of setting a "minimal prefix"                                     | `false`
//! `pad_with`      | the character that is used to pad the number to be `width` long                                  | ` ` (a space)
//! `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. | `..`
//!
//! ## `bar` - Display numbers as progress bars
//!
//! Argument               | Description                                                                     |Default value
//! -----------------------|---------------------------------------------------------------------------------|-------------------------
//! `width` or `w`         | the width of the bar (in characters)                                            | `5` (`1` for `vertical`)
//! `max_value`            | which value is treated as "full". For example, for battery level `100` is full. | `100`
//! `vertical` or `v`      | whether to render the bar vertically or not                                     | `false`
//!
//! ## `pango-str` - Just display the text without pango markup escaping
//!
//! No arguments.
//!
//! ## `datetime` - Display datetime
//!
//! Argument               | Description                                                                                               |Default value
//! -----------------------|-----------------------------------------------------------------------------------------------------------|-------------
//! `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
//!
//! # Handling missing placeholders and incorrect types
//!
//! Some blocks allow missing placeholders, for example [bluetooth](crate::blocks::bluetooth)'s
//! "percentage" may be absent if the device is not supported. To handle such cases it is possible
//! to queue multiple formats together by using `|` symbol: `<something that can fail>|<otherwise
//! try this>|<or this>`.
//!
//! In addition, formats can be recursive. To set a format inside of another format, place it
//! inside of `{}`. For example, in `Percentage: {$percentage|N/A}` the text "Percentage: " will be
//! always displayed, followed by the actual percentage or "N/A" in case percentage is not
//! available. This example does exactly the same thing as `Percentage: $percentage|Percentage: N/A`
//!
//! # How to use flags
//!
//! Some blocks provide flags, which can be used to change the format based on some criteria. For
//! example, [taskwarrior](crate::blocks::taskwarrior) defines `done` if the count is zero. In
//! general, flags are used in this way:
//!
//! ```text
//! $a{a is set}|$b$c{b and c are set}|${b|c}{b or c is set}|neither flag is set
//! ```

pub mod config;
pub mod formatter;
pub mod parse;
pub mod prefix;
pub mod scheduling;
pub mod template;
pub mod unit;
pub mod value;

use std::borrow::Cow;
use std::collections::HashMap;

use crate::config::SharedConfig;
use crate::errors::*;
use template::FormatTemplate;
use value::Value;

pub type Values = HashMap<Cow<'static, str>, Value>;

#[derive(Debug, thiserror::Error)]
pub enum FormatError {
    #[error("Placeholder '{0}' not found")]
    PlaceholderNotFound(String),
    #[error("{} cannot be formatted with '{}' formatter", .ty, .fmt)]
    IncompatibleFormatter { ty: &'static str, fmt: &'static str },
    #[error("Number {0} is out of range")]
    NumberOutOfRange(f64),
    #[error(transparent)]
    Other(#[from] Error),
}

#[derive(Debug, Clone)]
pub struct Format {
    full: FormatTemplate,
    short: FormatTemplate,
    intervals: Vec<u64>,
}

impl Format {
    pub fn contains_key(&self, key: &str) -> bool {
        self.full.contains_key(key) || self.short.contains_key(key)
    }

    pub fn intervals(&self) -> Vec<u64> {
        self.intervals.clone()
    }

    pub fn render(
        &self,
        values: &Values,
        config: &SharedConfig,
    ) -> Result<(Vec<Fragment>, Vec<Fragment>)> {
        let full = self
            .full
            .render(values, config)
            .error("Failed to render full text")?;
        let short = self
            .short
            .render(values, config)
            .error("Failed to render short text")?;
        Ok((full, short))
    }
}

#[derive(Debug, Default, Clone)]
pub struct Fragment {
    pub text: String,
    pub metadata: Metadata,
}

impl From<String> for Fragment {
    fn from(text: String) -> Self {
        Self {
            text,
            metadata: Default::default(),
        }
    }
}

impl Fragment {
    pub fn formatted_text(&self) -> String {
        match (self.metadata.italic, self.metadata.underline) {
            (true, true) => format!("<i><u>{}</u></i>", self.text),
            (false, true) => format!("<u>{}</u>", self.text),
            (true, false) => format!("<i>{}</i>", self.text),
            (false, false) => self.text.clone(),
        }
    }
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Metadata {
    pub instance: Option<&'static str>,
    pub underline: bool,
    pub italic: bool,
}

impl Metadata {
    pub fn is_default(&self) -> bool {
        *self == Default::default()
    }
}