i3status-rust icon indicating copy to clipboard operation
i3status-rust copied to clipboard

Issue 1703 timewarrior block

Open Mice7R opened this issue 2 years ago • 11 comments

Adds a block that displays the time of the current active task in timewarrior.

Closes #1703

Mice7R avatar Apr 25 '23 20:04 Mice7R

I can fix the things clippy is complaining about and the typo but what can I do with cspell? timew is the name of the program.

Mice7R avatar Apr 25 '23 20:04 Mice7R

timew is the name of the program

Please add it to https://github.com/greshake/i3status-rust/blob/master/cspell.yaml

MaxVerevkin avatar Apr 25 '23 21:04 MaxVerevkin

@notramo would you like to test this?

MaxVerevkin avatar Apr 27 '23 14:04 MaxVerevkin

This thing got stuck in pending. I've implemented your suggestion and updated the code for the new api.

Mice7R avatar Feb 12 '24 22:02 Mice7R

Wait, I've noticed there is an error when "continuing" a task

Mice7R avatar Feb 12 '24 22:02 Mice7R

There

Mice7R avatar Feb 12 '24 22:02 Mice7R

Looks like there's 2 small formatting issues (missing ,s)

Diff in /home/runner/work/i3status-rust/i3status-rust/src/blocks/timewarrior.rs at line 141:
             annotation: item.annotation,
             start: chrono::TimeZone::from_utc_datetime(
                 &chrono::Utc,
-                &chrono::NaiveDateTime::parse_from_str(&item.start, "%Y%m%dT%H%M%SZ").unwrap()
+                &chrono::NaiveDateTime::parse_from_str(&item.start, "%Y%m%dT%H%M%SZ").unwrap(),
             ),
             end: item.end.map(|v| {
                 chrono::TimeZone::from_utc_datetime(
Diff in /home/runner/work/i3status-rust/i3status-rust/src/blocks/timewarrior.rs at line 148:
                     &chrono::Utc,
-                    &chrono::NaiveDateTime::parse_from_str(&v, "%Y%m%dT%H%M%SZ").unwrap()
+                    &chrono::NaiveDateTime::parse_from_str(&v, "%Y%m%dT%H%M%SZ").unwrap(),
                 )
             }),
         }

bim9262 avatar Feb 14 '24 00:02 bim9262

You can avoid having to use the TimewarriorRAW intermediate stucture like this:

diff --git a/src/blocks/timewarrior.rs b/src/blocks/timewarrior.rs
index 26d4a67c..a232e1fd 100644
--- a/src/blocks/timewarrior.rs
+++ b/src/blocks/timewarrior.rs
@@ -35,7 +35,10 @@
 //! - `tasks`
 
 use super::prelude::*;
-use chrono::DateTime;
+
+use chrono::{offset::Utc, DateTime};
+use serde::de::{self, Deserialize, Deserializer};
+use serde_with::{serde_as, DeserializeAs};
 use tokio::process::Command;
 
 #[derive(Deserialize, Debug, SmartDefault)]
@@ -112,51 +115,33 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
     }
 }
 
-/// Raw output from timew
-#[derive(Deserialize, Debug)]
-struct TimewarriorRAW {
-    pub id: u32,
-    pub start: String,
-    pub tags: Vec<String>,
-    pub annotation: Option<String>,
-    pub end: Option<String>,
-}
-
 /// TimeWarrior entry
+#[serde_as]
 #[derive(Debug, PartialEq, Deserialize)]
-#[serde(from = "TimewarriorRAW")]
 struct TimewarriorData {
     pub id: u32,
-    pub start: DateTime<chrono::offset::Utc>,
+    #[serde_as(as = "DateTimeAsString")]
+    pub start: DateTime<Utc>,
     pub tags: Vec<String>,
     pub annotation: Option<String>,
-    pub end: Option<DateTime<chrono::offset::Utc>>,
+    #[serde_as(as = "Option<DateTimeAsString>")]
+    pub end: Option<DateTime<Utc>>,
 }
 
-impl From<TimewarriorRAW> for TimewarriorData {
-    fn from(item: TimewarriorRAW) -> Self {
-        Self {
-            id: item.id,
-            tags: item.tags,
-            annotation: item.annotation,
-            start: chrono::TimeZone::from_utc_datetime(
-                &chrono::Utc,
-                &chrono::NaiveDateTime::parse_from_str(&item.start, "%Y%m%dT%H%M%SZ").unwrap()
-            ),
-            end: item.end.map(|v| {
-                chrono::TimeZone::from_utc_datetime(
-                    &chrono::Utc,
-                    &chrono::NaiveDateTime::parse_from_str(&v, "%Y%m%dT%H%M%SZ").unwrap()
-                )
-            }),
-        }
-    }
-}
+struct DateTimeAsString;
 
-/// Format a DateTime given a format string
-#[allow(dead_code)]
-fn format_datetime(date: &DateTime<chrono::Utc>, format: &str) -> String {
-    date.format(format).to_string()
+impl<'de> DeserializeAs<'de, DateTime<Utc>> for DateTimeAsString {
+    fn deserialize_as<D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        use chrono::NaiveDateTime;
+        use de::Error;
+
+        let s = String::deserialize(deserializer)?;
+        let dt = NaiveDateTime::parse_from_str(&s, "%Y%m%dT%H%M%SZ").map_err(D::Error::custom)?;
+        Ok(DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc))
+    }
 }
 
 /// Execute "timew export now" and return the current task (if any)

bim9262 avatar Feb 14 '24 02:02 bim9262

Any maybe this is a stupid question, but as someone who's never used taskwarrior can you have multiple tasks at the same time?

bim9262 avatar Feb 14 '24 02:02 bim9262

Here's a version without using the RAW intermediate stuct:

diff --git a/src/blocks/timewarrior.rs b/src/blocks/timewarrior.rs
index 26d4a67c..2da0f92b 100644
--- a/src/blocks/timewarrior.rs
+++ b/src/blocks/timewarrior.rs
@@ -35,7 +35,9 @@
 //! - `tasks`
 
 use super::prelude::*;
-use chrono::DateTime;
+
+use chrono::{offset::Utc, DateTime};
+use serde::de::{self, Deserialize, Deserializer};
 use tokio::process::Command;
 
 #[derive(Deserialize, Debug, SmartDefault)]
@@ -68,7 +70,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
         if let Some(tw) = data {
             if tw.end.is_none() {
                 // only show active tasks
-                let elapsed = chrono::Utc::now() - tw.start;
+                let elapsed = chrono::Utc::now() - tw.start.0;
 
                 // calculate state
                 for (level, st) in [
@@ -112,51 +114,31 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
     }
 }
 
-/// Raw output from timew
-#[derive(Deserialize, Debug)]
-struct TimewarriorRAW {
-    pub id: u32,
-    pub start: String,
-    pub tags: Vec<String>,
-    pub annotation: Option<String>,
-    pub end: Option<String>,
-}
-
 /// TimeWarrior entry
 #[derive(Debug, PartialEq, Deserialize)]
-#[serde(from = "TimewarriorRAW")]
 struct TimewarriorData {
     pub id: u32,
-    pub start: DateTime<chrono::offset::Utc>,
+    pub start: SerdeDateTime,
     pub tags: Vec<String>,
     pub annotation: Option<String>,
-    pub end: Option<DateTime<chrono::offset::Utc>>,
+    pub end: Option<SerdeDateTime>,
 }
 
-impl From<TimewarriorRAW> for TimewarriorData {
-    fn from(item: TimewarriorRAW) -> Self {
-        Self {
-            id: item.id,
-            tags: item.tags,
-            annotation: item.annotation,
-            start: chrono::TimeZone::from_utc_datetime(
-                &chrono::Utc,
-                &chrono::NaiveDateTime::parse_from_str(&item.start, "%Y%m%dT%H%M%SZ").unwrap()
-            ),
-            end: item.end.map(|v| {
-                chrono::TimeZone::from_utc_datetime(
-                    &chrono::Utc,
-                    &chrono::NaiveDateTime::parse_from_str(&v, "%Y%m%dT%H%M%SZ").unwrap()
-                )
-            }),
-        }
-    }
-}
+#[derive(Debug, PartialEq)]
+struct SerdeDateTime(DateTime<Utc>);
 
-/// Format a DateTime given a format string
-#[allow(dead_code)]
-fn format_datetime(date: &DateTime<chrono::Utc>, format: &str) -> String {
-    date.format(format).to_string()
+impl<'de> Deserialize<'de> for SerdeDateTime {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        use chrono::NaiveDateTime;
+        use de::Error;
+
+        let s = String::deserialize(deserializer)?;
+        let dt = NaiveDateTime::parse_from_str(&s, "%Y%m%dT%H%M%SZ").map_err(D::Error::custom)?;
+        Ok(Self(DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc)))
+    }
 }
 
 /// Execute "timew export now" and return the current task (if any)

bim9262 avatar Feb 16 '24 02:02 bim9262

Any maybe this is a stupid question, but as someone who's never used taskwarrior can you have multiple tasks at the same time?

Timewarrior and Taskwarrior are 2 different programs. This PR is about Timewarrior. In Timewarrior, one period can have multiple tags, but they must be started at the same time and ended at the same time (as they are attached to one interval). If someone wants to finish one tag and continue the other, they can stop the current period then start new with only one tag.

notramo avatar Feb 16 '24 12:02 notramo