Skip to content

Commit c75a183

Browse files
authored
Merge pull request #1008 from matt-snider/master
Support multiple filters in Taskwarrior block
2 parents bb93867 + 7b2fc4d commit c75a183

File tree

2 files changed

+86
-57
lines changed

2 files changed

+86
-57
lines changed

blocks.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,26 +1303,30 @@ Key | Values | Required | Default
13031303

13041304
## Taskwarrior
13051305

1306-
Creates a block which displays number of pending and started tasks of the current users taskwarrior list.
1306+
Creates a block which displays the number of tasks matching user-defined filters from the current user's taskwarrior list.
13071307

1308-
Clicking the left mouse button on the icon updates the number of pending tasks immediately.
1308+
Clicking the left mouse button on the icon updates the number of tasks immediately.
1309+
1310+
Clicking the right mouse button on the icon cycles the view of the block through the user's filters.
13091311

1310-
Clicking the right mouse button on the icon toggles the view of the block between filtered (default) and non-filtered
1311-
tasks. If there are no filters configured, the number of tasks stays the same and both modes are behaving
1312-
equally.
13131312

13141313
#### Examples
13151314

13161315
```toml
13171316
[[block]]
13181317
block = "taskwarrior"
13191318
interval = 60
1320-
format = "{count} open tasks"
1321-
format_singular = "{count} open task"
1319+
format = "{count} open tasks ({filter_name})"
1320+
format_singular = "{count} open task ({filter_name})"
13221321
format_everything_done = "nothing to do!"
13231322
warning_threshold = 10
13241323
critical_threshold = 20
1325-
filter_tags = ["work", "important"]
1324+
[[block.filters]]
1325+
name = "today"
1326+
filter = "+PENDING +OVERDUE or +DUETODAY"
1327+
[[block.filters]]
1328+
name = "some-project"
1329+
filter = "project:some-project +PENDING"
13261330
```
13271331

13281332
#### Options
@@ -1332,7 +1336,8 @@ Key | Values | Required | Default
13321336
`interval` | Update interval, in seconds. | No | `600` (10min)
13331337
`warning_threshold` | The threshold of pending (or started) tasks when the block turns into a warning state. | No | `10`
13341338
`critical_threshold` | The threshold of pending (or started) tasks when the block turns into a critical state. | No | `20`
1335-
`filter_tags` | A list of tags a task has to have before its counted as a pending task. | No | ```<empty>```
1339+
`filter_tags` | Deprecated in favour of `filters`. A list of tags a task has to have before its counted as a pending task. The list of tags will be appended to the base filter `-COMPLETED -DELETED`. | No | ```<empty>```
1340+
`filters` | A list of tables with the keys `name` and `filter`. `filter` specifies the criteria that must be met for a task to be counted towards this filter. | No | ```[{name = "pending", filter = "-COMPLETED -DELETED"}]```
13361341
`format` | A string to customise the output of this block. See below for available placeholders. Text may need to be escaped, refer to [Escaping Text](#escaping-text). | No | `"{count}"`
13371342
`format_singular` | Same as `format` but for when exactly one task is pending. | No | `"{count}"`
13381343
`format_everything_done` | Same as `format` but for when all tasks are completed. | No | `"{count}"`
@@ -1342,6 +1347,7 @@ Key | Values | Required | Default
13421347
Key | Value
13431348
----|-------
13441349
`{count}` | The number of pending tasks
1350+
`{filter_name}` | The name of the current filter
13451351

13461352
###### [↥ back to top](#list-of-available-blocks)
13471353

src/blocks/taskwarrior.rs

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ pub struct Taskwarrior {
2121
update_interval: Duration,
2222
warning_threshold: u32,
2323
critical_threshold: u32,
24-
filter_tags: Vec<String>,
25-
block_mode: TaskwarriorBlockMode,
24+
filters: Vec<Filter>,
25+
filter_index: usize,
2626
format: FormatTemplate,
2727
format_singular: FormatTemplate,
2828
format_everything_done: FormatTemplate,
@@ -34,6 +34,29 @@ pub struct Taskwarrior {
3434
tx_update_request: Sender<Task>,
3535
}
3636

37+
#[derive(Deserialize, Debug, Default, Clone)]
38+
#[serde(deny_unknown_fields)]
39+
pub struct Filter {
40+
pub name: String,
41+
pub filter: String,
42+
}
43+
44+
impl Filter {
45+
pub fn new(name: String, filter: String) -> Self {
46+
Filter { name, filter }
47+
}
48+
49+
pub fn legacy(name: String, tags: &[String]) -> Self {
50+
let tags = tags
51+
.iter()
52+
.map(|element| format!("+{}", element))
53+
.collect::<Vec<String>>()
54+
.join(" ");
55+
let filter = format!("-COMPLETED -DELETED {}", tags);
56+
Self::new(name, filter)
57+
}
58+
}
59+
3760
#[derive(Deserialize, Debug, Default, Clone)]
3861
#[serde(deny_unknown_fields)]
3962
pub struct TaskwarriorConfig {
@@ -53,32 +76,31 @@ pub struct TaskwarriorConfig {
5376
pub critical_threshold: u32,
5477

5578
/// A list of tags a task has to have before it's used for counting pending tasks
79+
/// (DEPRECATED) use filters instead
5680
#[serde(default = "TaskwarriorConfig::default_filter_tags")]
5781
pub filter_tags: Vec<String>,
5882

83+
/// A list of named filter criteria which must be fulfilled to be counted towards
84+
/// the total, when that filter is active.
85+
#[serde(default = "TaskwarriorConfig::default_filters")]
86+
pub filters: Vec<Filter>,
87+
5988
/// Format override
6089
#[serde(default = "TaskwarriorConfig::default_format")]
6190
pub format: String,
6291

63-
/// Format override if exactly one task is pending
92+
/// Format override if the count is one
6493
#[serde(default = "TaskwarriorConfig::default_format")]
6594
pub format_singular: String,
6695

67-
/// Format override if all tasks are completed
96+
/// Format override if the count is zero
6897
#[serde(default = "TaskwarriorConfig::default_format")]
6998
pub format_everything_done: String,
7099

71100
#[serde(default = "TaskwarriorConfig::default_color_overrides")]
72101
pub color_overrides: Option<BTreeMap<String, String>>,
73102
}
74103

75-
enum TaskwarriorBlockMode {
76-
// Show only the tasks which are filtered by the set tags and which are not completed.
77-
OnlyFilteredPendingTasks,
78-
// Show all pending tasks and ignore the filtering tags.
79-
AllPendingTasks,
80-
}
81-
82104
impl TaskwarriorConfig {
83105
fn default_interval() -> Duration {
84106
Duration::from_secs(600)
@@ -96,6 +118,13 @@ impl TaskwarriorConfig {
96118
vec![]
97119
}
98120

121+
fn default_filters() -> Vec<Filter> {
122+
vec![Filter::new(
123+
"pending".to_string(),
124+
"-COMPLETED -DELETED".to_string(),
125+
)]
126+
}
127+
99128
fn default_format() -> String {
100129
"{count}".to_owned()
101130
}
@@ -117,15 +146,22 @@ impl ConfigBlock for Taskwarrior {
117146
let output = ButtonWidget::new(config.clone(), &id)
118147
.with_icon("tasks")
119148
.with_text("-");
149+
// If the deprecated `filter_tags` option has been set,
150+
// convert it to the new `filter` format.
151+
let filters = if block_config.filter_tags.len() > 0 {
152+
vec![
153+
Filter::legacy("filtered".to_string(), &block_config.filter_tags),
154+
Filter::legacy("all".to_string(), &vec![]),
155+
]
156+
} else {
157+
block_config.filters
158+
};
120159

121160
Ok(Taskwarrior {
122161
id: pseudo_uuid(),
123162
update_interval: block_config.interval,
124163
warning_threshold: block_config.warning_threshold,
125164
critical_threshold: block_config.critical_threshold,
126-
filter_tags: block_config.filter_tags,
127-
block_mode: TaskwarriorBlockMode::OnlyFilteredPendingTasks,
128-
output,
129165
format: FormatTemplate::from_string(&block_config.format).block_error(
130166
"taskwarrior",
131167
"Invalid format specified for taskwarrior::format",
@@ -143,7 +179,10 @@ impl ConfigBlock for Taskwarrior {
143179
"Invalid format specified for taskwarrior::format_everything_done",
144180
)?,
145181
tx_update_request,
182+
filter_index: 0,
146183
config,
184+
filters,
185+
output,
147186
})
148187
}
149188
}
@@ -164,33 +203,20 @@ fn has_taskwarrior() -> Result<bool> {
164203
!= "")
165204
}
166205

167-
fn tags_to_filter(tags: &[String]) -> String {
168-
tags.iter()
169-
.map(|element| format!("+{}", element))
170-
.collect::<Vec<String>>()
171-
.join(" ")
172-
}
173-
174-
fn get_number_of_pending_tasks(tags: &[String]) -> Result<u32> {
206+
fn get_number_of_tasks(filter: &str) -> Result<u32> {
175207
String::from_utf8(
176208
Command::new("sh")
177-
.args(&[
178-
"-c",
179-
&format!(
180-
"task rc.gc=off -COMPLETED -DELETED {} count",
181-
tags_to_filter(tags)
182-
),
183-
])
209+
.args(&["-c", &format!("task rc.gc=off {} count", filter)])
184210
.output()
185211
.block_error(
186212
"taskwarrior",
187-
"failed to run taskwarrior for getting the number of pending tasks",
213+
"failed to run taskwarrior for getting the number of tasks",
188214
)?
189215
.stdout,
190216
)
191217
.block_error(
192218
"taskwarrior",
193-
"failed to get the number of pending tasks from taskwarrior",
219+
"failed to get the number of tasks from taskwarrior",
194220
)?
195221
.trim()
196222
.parse::<u32>()
@@ -202,20 +228,23 @@ impl Block for Taskwarrior {
202228
if !has_taskwarrior()? {
203229
self.output.set_text("?")
204230
} else {
205-
let filter_tags = match self.block_mode {
206-
TaskwarriorBlockMode::OnlyFilteredPendingTasks => self.filter_tags.clone(),
207-
TaskwarriorBlockMode::AllPendingTasks => vec![],
208-
};
209-
let number_of_pending_tasks = get_number_of_pending_tasks(&filter_tags)?;
210-
let values = map!("{count}" => number_of_pending_tasks);
211-
self.output.set_text(match number_of_pending_tasks {
231+
let filter = self.filters.get(self.filter_index).block_error(
232+
"taskwarrior",
233+
&format!("Filter at index {} does not exist", self.filter_index),
234+
)?;
235+
let number_of_tasks = get_number_of_tasks(&filter.filter)?;
236+
let values = map!(
237+
"{count}" => number_of_tasks.to_string(),
238+
"{filter_name}" => filter.name.clone()
239+
);
240+
self.output.set_text(match number_of_tasks {
212241
0 => self.format_everything_done.render_static_str(&values)?,
213242
1 => self.format_singular.render_static_str(&values)?,
214243
_ => self.format.render_static_str(&values)?,
215244
});
216-
if number_of_pending_tasks >= self.critical_threshold {
245+
if number_of_tasks >= self.critical_threshold {
217246
self.output.set_state(State::Critical);
218-
} else if number_of_pending_tasks >= self.warning_threshold {
247+
} else if number_of_tasks >= self.warning_threshold {
219248
self.output.set_state(State::Warning);
220249
} else {
221250
self.output.set_state(State::Idle);
@@ -237,14 +266,8 @@ impl Block for Taskwarrior {
237266
self.update()?;
238267
}
239268
MouseButton::Right => {
240-
match self.block_mode {
241-
TaskwarriorBlockMode::OnlyFilteredPendingTasks => {
242-
self.block_mode = TaskwarriorBlockMode::AllPendingTasks
243-
}
244-
TaskwarriorBlockMode::AllPendingTasks => {
245-
self.block_mode = TaskwarriorBlockMode::OnlyFilteredPendingTasks
246-
}
247-
}
269+
// Increment the filter_index, rotating at the end
270+
self.filter_index = (self.filter_index + 1) % self.filters.len();
248271
self.update()?;
249272
}
250273
_ => {}

0 commit comments

Comments
 (0)