mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-24 05:26:42 +02:00
Merge pull request #546 from RedEtherbloom/tkw3_fix
Switch to taskwarrior v3.X backend
This commit is contained in:
commit
c3f2d0601d
7 changed files with 57 additions and 49 deletions
2
.github/workflows/cd.yml
vendored
2
.github/workflows/cd.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -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
2
Cargo.lock
generated
|
@ -1329,7 +1329,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "taskwarrior-tui"
|
||||
version = "0.25.4"
|
||||
version = "0.26.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"better-panic",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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.
|
||||
|
||||
[](https://github.com/kdheepak/taskwarrior-tui/actions?query=workflow%3ACI)
|
||||
[](./LICENSE)
|
||||
|
|
85
src/app.rs
85
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)]
|
||||
|
@ -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>>();
|
||||
|
|
|
@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue