Merge pull request #546 from RedEtherbloom/tkw3_fix

Switch to taskwarrior v3.X backend
This commit is contained in:
Dheepak Krishnamurthy 2024-05-11 23:38:11 -04:00 committed by GitHub
commit c3f2d0601d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 57 additions and 49 deletions

View file

@ -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

View file

@ -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

2
Cargo.lock generated
View file

@ -1329,7 +1329,7 @@ dependencies = [
[[package]]
name = "taskwarrior-tui"
version = "0.25.4"
version = "0.26.0"
dependencies = [
"anyhow",
"better-panic",

View file

@ -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"
@ -57,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"] }

View file

@ -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)

View file

@ -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)]
@ -1304,12 +1304,11 @@ 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);
}
}
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<SystemTime> {
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<SystemTime> {
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<SystemTime>) -> Result<bool> {
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 {
@ -1794,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);
}
}
@ -1904,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);
}
}
@ -1957,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);
}
}
@ -2009,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);
}
}
@ -2096,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);
}
}
@ -2136,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);
}
}
@ -3763,13 +3767,28 @@ 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};
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);
@ -3801,7 +3820,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();
@ -3812,7 +3831,7 @@ mod tests {
}
fn teardown() {
let cd = Path::new(env!("TASKDATA"));
let cd = get_taskdata_path();
std::fs::remove_dir_all(cd).unwrap();
}
@ -3892,24 +3911,16 @@ 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!(
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();
@ -3958,7 +3969,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::<Vec<String>>();
@ -3977,7 +3988,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::<Vec<String>>();
@ -3989,7 +4000,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::<Vec<String>>();

View file

@ -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
]
}
}