From d178b986adc4fcc71a02af0e49402b8d9e10808a Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Thu, 28 Mar 2024 04:41:22 +0100 Subject: [PATCH 01/10] fix: Switch to taskwarrior v3.X backend --- Cargo.lock | 2 +- Cargo.toml | 6 ++---- README.md | 2 +- src/app.rs | 28 ++++++++++++++++------------ 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8981dc33..4310507c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1329,7 +1329,7 @@ dependencies = [ [[package]] name = "taskwarrior-tui" -version = "0.25.4" +version = "0.26.0" dependencies = [ "anyhow", "better-panic", diff --git a/Cargo.toml b/Cargo.toml index d4ef53bc..f08e5d60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taskwarrior-tui" -version = "0.25.4" +version = "0.26.0" license = "MIT" description = "A Taskwarrior Terminal User Interface" repository = "https://github.com/kdheepak/taskwarrior-tui/" @@ -18,9 +18,7 @@ better-panic = "0.3.0" cassowary = "0.3.0" chrono = "0.4.26" clap = { version = "4.4.1", features = ["derive"] } -crossterm = { version = "0.27.0", features = [ - "event-stream", -] } +crossterm = { version = "0.27.0", features = ["event-stream"] } dirs = "5.0.1" futures = "0.3.28" itertools = "0.11.0" diff --git a/README.md b/README.md index c3c944f0..1fcfa336 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # `taskwarrior-tui` > [!IMPORTANT] -> `taskwarrior-tui` is only tested with `taskwarrior` v2.x. [`taskwarrior` v3.x](https://github.com/GothenburgBitFactory/taskwarrior/releases/tag/v3.0.0) may not work as intended. +> [`taskwarrior` v3.x](https://github.com/GothenburgBitFactory/taskwarrior/releases/tag/v3.0.0) may break `taskwarrior-tui` features in unexpected ways. Please file a bug report if you encounter a bug. [![CI](https://github.com/kdheepak/taskwarrior-tui/workflows/CI/badge.svg)](https://github.com/kdheepak/taskwarrior-tui/actions?query=workflow%3ACI) [![](https://img.shields.io/github/license/kdheepak/taskwarrior-tui)](./LICENSE) diff --git a/src/app.rs b/src/app.rs index 271e4d46..05963a51 100644 --- a/src/app.rs +++ b/src/app.rs @@ -64,7 +64,7 @@ const MAX_LINE: usize = 4096; lazy_static! { static ref START_TIME: Instant = Instant::now(); - static ref TASKWARRIOR_VERSION_SUPPORTED: Versioning = Versioning::new("2.6.0").unwrap(); + static ref TASKWARRIOR_VERSION_SUPPORTED: Versioning = Versioning::new("3.0.0").unwrap(); } #[derive(Debug)] @@ -1309,7 +1309,6 @@ impl TaskwarriorTui { } } - self.last_export = Some(std::time::SystemTime::now()); self.task_report_table.export_headers(None, &self.report)?; self.export_tasks()?; if self.config.uda_task_report_use_all_tasks_for_completion { @@ -1321,6 +1320,10 @@ impl TaskwarriorTui { self.task_details.clear(); self.dirty = false; self.save_history()?; + + // Some operations like export or summary change the taskwarrior database. + // The export time therefore gets set at the end, to avoid an infinite update loop. + self.last_export = Some(std::time::SystemTime::now()); } self.cursor_fix(); self.update_task_table_state(); @@ -1608,20 +1611,21 @@ impl TaskwarriorTui { } } - fn get_task_files_max_mtime(&self) -> Result { - let data_dir = shellexpand::tilde(&self.config.data_location).into_owned(); - ["backlog.data", "completed.data", "pending.data"] - .iter() - .map(|n| fs::metadata(Path::new(&data_dir).join(n)).map(|m| m.modified())) - .filter_map(Result::ok) - .filter_map(Result::ok) - .max() - .ok_or_else(|| anyhow!("Unable to get task files max time")) + fn get_task_database_mtime(&self) -> Result { + let data_dir = shellexpand::tilde(&self.config.data_location); + let database_path = Path::new(data_dir.as_ref()).join("taskchampion.sqlite3"); + + let metadata = fs::metadata(database_path).context("Fetching the metadate of the task database failed")?; + let mtime = metadata + .modified() + .context("Could not get mtime of task database, but fetching metadata succeeded")?; + + Ok(mtime) } pub fn tasks_changed_since(&mut self, prev: Option) -> Result { if let Some(prev) = prev { - let mtime = self.get_task_files_max_mtime()?; + let mtime = self.get_task_database_mtime()?; if mtime > prev { Ok(true) } else { From de552ee99c285fba5461bba67a4288dd0585eac9 Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Mon, 15 Apr 2024 13:49:09 +0200 Subject: [PATCH 02/10] fix: Update taskwarrior for CI/CD --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9869cbb5..dc88985a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -307,7 +307,7 @@ jobs: cd /tmp git clone https://github.com/GothenburgBitFactory/taskwarrior cd taskwarrior - git checkout v2.6.1 + git checkout v3.0.0 cmake -DCMAKE_BUILD_TYPE=release -DENABLE_SYNC=OFF . make sudo make install diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3734b434..0f891cbb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: cd /tmp git clone https://github.com/GothenburgBitFactory/taskwarrior cd taskwarrior - git checkout v2.6.1 + git checkout v3.0.0 cmake -DCMAKE_BUILD_TYPE=release -DENABLE_SYNC=OFF . make sudo make install From e8ca3e9cad69f776f053e0bb3dbc5521fc7f44c5 Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Tue, 16 Apr 2024 11:53:44 +0200 Subject: [PATCH 03/10] refactor: Use first to satisfy clippy --- src/app.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app.rs b/src/app.rs index 05963a51..b32254cf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1304,7 +1304,7 @@ impl TaskwarriorTui { self.get_context()?; let task_uuids = self.selected_task_uuids(); if self.current_selection_uuid.is_none() && self.current_selection_id.is_none() && task_uuids.len() == 1 { - if let Some(uuid) = task_uuids.get(0) { + if let Some(uuid) = task_uuids.first() { self.current_selection_uuid = Some(*uuid); } } @@ -1798,7 +1798,7 @@ impl TaskwarriorTui { }; if task_uuids.len() == 1 { - if let Some(uuid) = task_uuids.get(0) { + if let Some(uuid) = task_uuids.first() { self.current_selection_uuid = Some(*uuid); } } @@ -1908,7 +1908,7 @@ impl TaskwarriorTui { }; if task_uuids.len() == 1 { - if let Some(uuid) = task_uuids.get(0) { + if let Some(uuid) = task_uuids.first() { self.current_selection_uuid = Some(*uuid); } } @@ -1961,7 +1961,7 @@ impl TaskwarriorTui { }; if task_uuids.len() == 1 { - if let Some(uuid) = task_uuids.get(0) { + if let Some(uuid) = task_uuids.first() { self.current_selection_uuid = Some(*uuid); } } @@ -2013,7 +2013,7 @@ impl TaskwarriorTui { }; if task_uuids.len() == 1 { - if let Some(uuid) = task_uuids.get(0) { + if let Some(uuid) = task_uuids.first() { self.current_selection_uuid = Some(*uuid); } } @@ -2100,7 +2100,7 @@ impl TaskwarriorTui { } if task_uuids.len() == 1 { - if let Some(uuid) = task_uuids.get(0) { + if let Some(uuid) = task_uuids.first() { self.current_selection_uuid = Some(*uuid); } } @@ -2140,7 +2140,7 @@ impl TaskwarriorTui { } if task_uuids.len() == 1 { - if let Some(uuid) = task_uuids.get(0) { + if let Some(uuid) = task_uuids.first() { self.current_selection_uuid = Some(*uuid); } } From 64c5731ed80158565734c39ffc403e83b5c61f0e Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Tue, 16 Apr 2024 11:54:15 +0200 Subject: [PATCH 04/10] refactor: Remove let-return to satisfy clippy --- src/calendar.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/calendar.rs b/src/calendar.rs index 04d50566..a3c6efb6 100644 --- a/src/calendar.rs +++ b/src/calendar.rs @@ -249,7 +249,7 @@ impl<'a> Widget for Calendar<'a> { impl<'a> Calendar<'a> { fn generate_month_names() -> [&'a str; 12] { - let month_names = [ + [ Month::January.name(), Month::February.name(), Month::March.name(), @@ -262,7 +262,6 @@ impl<'a> Calendar<'a> { Month::October.name(), Month::November.name(), Month::December.name(), - ]; - month_names + ] } } From 64521704764bdac162e6cc18cb3d6f90517a3740 Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Tue, 16 Apr 2024 12:05:45 +0200 Subject: [PATCH 05/10] refactor: Resolve env var at runtime to remove compiler error --- src/app.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/app.rs b/src/app.rs index b32254cf..83704f8d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3768,12 +3768,19 @@ pub fn remove_tag(task: &mut Task, tag: &str) { #[cfg(test)] mod tests { - use std::{ffi::OsStr, fmt::Write, fs::File, io, path::Path}; + use std::{ffi::OsStr, fmt::Write, fs::File, io, path::{Path, PathBuf}}; use ratatui::{backend::TestBackend, buffer::Buffer}; use super::*; + fn get_taskdata_path() -> PathBuf { + let taskdata_env_var = std::env::var("TASKDATA").expect("TASKDATA environment variable not set."); + let taskdata_path = Path::new(&taskdata_env_var).to_owned(); + + taskdata_path + } + /// Returns a string representation of the given buffer for debugging purpose. fn buffer_view(buffer: &Buffer) -> String { let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3); @@ -3805,7 +3812,7 @@ mod tests { fn setup() { use std::process::Stdio; - let mut f = File::open(Path::new(env!("TASKDATA")).parent().unwrap().join("export.json")).unwrap(); + let mut f = File::open(get_taskdata_path().parent().unwrap().join("export.json")).unwrap(); let mut s = String::new(); f.read_to_string(&mut s).unwrap(); let tasks = task_hookrs::import::import(s.as_bytes()).unwrap(); @@ -3816,7 +3823,7 @@ mod tests { } fn teardown() { - let cd = Path::new(env!("TASKDATA")); + let cd = get_taskdata_path(); std::fs::remove_dir_all(cd).unwrap(); } @@ -3912,8 +3919,8 @@ mod tests { app.task_by_index(0).is_none(), "Expected task data to be empty but found {} tasks. Delete contents of {:?} and {:?} and run the tests again.", app.tasks.len(), - Path::new(env!("TASKDATA")), - Path::new(env!("TASKDATA")).parent().unwrap().join(".config") + get_taskdata_path(), + get_taskdata_path().parent().unwrap().join(".config") ); let app = TaskwarriorTui::new("next", false).await.unwrap(); From b941334a671b1e89b01d7c4e09c74f1e88863822 Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Tue, 16 Apr 2024 12:06:45 +0200 Subject: [PATCH 06/10] refactor: Use native tokio test macro --- src/app.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/app.rs b/src/app.rs index 83704f8d..4b2e3881 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3903,16 +3903,8 @@ mod tests { // teardown(); } - #[test] - fn test_taskwarrior_tui() { - let r = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { _test_taskwarrior_tui().await }); - } - - async fn _test_taskwarrior_tui() { + #[tokio::test] + async fn test_taskwarrior_tui() { let app = TaskwarriorTui::new("next", false).await.unwrap(); assert!( From e6dbb4d99c7ba0f632ed34308a32d90228d7996e Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Tue, 16 Apr 2024 13:29:25 +0200 Subject: [PATCH 07/10] refactor: Disable single_char_pattern lint for tests only --- src/app.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app.rs b/src/app.rs index 4b2e3881..efc42c43 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3767,6 +3767,8 @@ pub fn remove_tag(task: &mut Task, tag: &str) { } #[cfg(test)] +// Disabled, as "'" should be a String for more readable shlex shell escaping. +#[allow(clippy::single_char_pattern)] mod tests { use std::{ffi::OsStr, fmt::Write, fs::File, io, path::{Path, PathBuf}}; From 3b27e1aa5fc77572d12aecf9cd7aa2e3d4bac2e2 Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Tue, 16 Apr 2024 13:30:12 +0200 Subject: [PATCH 08/10] refactor: Remove superfluous vec! to comply with clippy --- src/app.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index efc42c43..cbd17261 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3963,7 +3963,7 @@ mod tests { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); let task = app.task_by_id(11).unwrap(); - let tags = vec!["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] + let tags = ["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] .iter() .map(ToString::to_string) .collect::>(); @@ -3982,7 +3982,7 @@ mod tests { app.update(true).await.unwrap(); let task = app.task_by_id(11).unwrap(); - let tags = vec!["next", "finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] + let tags = ["next", "finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] .iter() .map(ToString::to_string) .collect::>(); @@ -3994,7 +3994,7 @@ mod tests { app.update(true).await.unwrap(); let task = app.task_by_id(11).unwrap(); - let tags = vec!["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] + let tags = ["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] .iter() .map(ToString::to_string) .collect::>(); From d9e8c3b567ff1648f9510c988b7de870986e4cdc Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Tue, 16 Apr 2024 18:27:25 +0200 Subject: [PATCH 09/10] format: Format with nightly rustfmt --- src/app.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index cbd17261..031aa9c3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3770,7 +3770,13 @@ pub fn remove_tag(task: &mut Task, tag: &str) { // Disabled, as "'" should be a String for more readable shlex shell escaping. #[allow(clippy::single_char_pattern)] mod tests { - use std::{ffi::OsStr, fmt::Write, fs::File, io, path::{Path, PathBuf}}; + use std::{ + ffi::OsStr, + fmt::Write, + fs::File, + io, + path::{Path, PathBuf}, + }; use ratatui::{backend::TestBackend, buffer::Buffer}; From 0d23399031ec1839954470ed706ce34d6c076e72 Mon Sep 17 00:00:00 2001 From: RedEtherbloom Date: Mon, 29 Apr 2024 06:59:15 +0200 Subject: [PATCH 10/10] fix: Reenable lto --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f08e5d60..4de2253b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ taskwarrior-tui = { path = "/usr/bin/taskwarrior-tui" } [profile.release] debug = 1 incremental = true -lto = "off" +lto = "fat" [build-dependencies] clap = { version = "4.4.1", features = ["derive"] }