greshake / i3status-rust

Very resourcefriendly and feature-rich replacement for i3status, written in pure Rust
GNU General Public License v3.0
2.85k stars 470 forks source link

Issue 1703 timewarrior block #1878

Open Mice7R opened 1 year ago

Mice7R commented 1 year ago

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

Closes #1703

Mice7R commented 1 year ago

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.

MaxVerevkin commented 1 year ago

timew is the name of the program

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

MaxVerevkin commented 1 year ago

@notramo would you like to test this?

Mice7R commented 6 months ago

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

Mice7R commented 6 months ago

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

Mice7R commented 6 months ago

There

bim9262 commented 6 months ago

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 commented 6 months ago

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 commented 6 months ago

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 commented 6 months ago

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)
notramo commented 6 months ago

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.