about summary refs log tree commit diff stats
path: root/modules
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-04-04 20:32:36 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-04-04 20:32:36 +0200
commit257b07f976b2628c4f6d356b3202a294df816fd2 (patch)
tree1e02a70ddbe76c5b7bdc8417750123b5fe77751b /modules
parentfix(modules/taskwarrior): Completely move to taskwarrior3 (diff)
downloadnixos-config-257b07f976b2628c4f6d356b3202a294df816fd2.zip
refactor(modules/timewarrior): Update the time tracking hooks
Both of them depended on the outdated `taskw` python package, that still
used taskwarrior 2.6.
Diffstat (limited to 'modules')
-rw-r--r--modules/by-name/ti/timewarrior/module.nix32
-rwxr-xr-xmodules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify.track-timewarrior.py124
-rwxr-xr-xmodules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify.track-total-active-time.py (renamed from modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-total-active-time.py)13
-rwxr-xr-xmodules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-timewarrior.py94
4 files changed, 138 insertions, 125 deletions
diff --git a/modules/by-name/ti/timewarrior/module.nix b/modules/by-name/ti/timewarrior/module.nix
index d262fbe6..bb4d8e26 100644
--- a/modules/by-name/ti/timewarrior/module.nix
+++ b/modules/by-name/ti/timewarrior/module.nix
@@ -7,49 +7,41 @@
   cfg = config.soispha.programs.timewarrior;
 
   track_timewarrior = pkgs.stdenv.mkDerivation {
-    name = "track_timewarrior.taskwarrior-hook";
+    name = "track-timewarrior";
     nativeBuildInputs = [
       pkgs.makeWrapper
     ];
     buildInputs = [
       pkgs.timewarrior
       pkgs.taskwarrior3
-      # TODO: Use a `taskw` package, that actually supports newer python variants <2024-07-13>
-      (pkgs.python311.withPackages (pythonPackages:
-        with pythonPackages; [
-          taskw
-        ]))
+      pkgs.python3
     ];
     dontUnpack = true;
     installPhase = ''
-      install -Dm755 ${./taskwarirror_hooks/on-modify_track-timewarrior.py} $out/bin/bin
-      wrapProgram $out/bin/bin \
-      --prefix PATH : ${lib.makeBinPath [pkgs.taskwarrior3 pkgs.timewarrior]}
+      install -Dm755 ${./taskwarirror_hooks/on-modify.track-timewarrior.py} $out/bin/track-timewarrior
+      wrapProgram $out/bin/track-timewarrior \
+      --set PATH ${lib.makeBinPath [pkgs.taskwarrior3 pkgs.timewarrior]}
     '';
 
-    meta.mainProgram = "bin";
+    meta.mainProgram = "track-timewarrior";
   };
   track_total_active_time = pkgs.stdenv.mkDerivation {
-    name = "track_total_active_time.taskwarrior-hook";
+    name = "track-total-active-time";
     nativeBuildInputs = [
       pkgs.makeWrapper
     ];
     buildInputs = [
       pkgs.taskwarrior3
-      # TODO: Use a `taskw` package, that actually supports newer python variants <2024-07-13>
-      (pkgs.python311.withPackages (pythonPackages:
-        with pythonPackages; [
-          taskw
-        ]))
+      pkgs.python3
     ];
     dontUnpack = true;
     installPhase = ''
-      install -Dm755 ${./taskwarirror_hooks/on-modify_track-total-active-time.py} $out/bin/bin
-      wrapProgram $out/bin/bin \
-      --prefix PATH : ${lib.makeBinPath [pkgs.taskwarrior3]}
+      install -Dm755 ${./taskwarirror_hooks/on-modify.track-total-active-time.py} $out/bin/track-total-active-time
+      wrapProgram $out/bin/track-total-active-time \
+      --set PATH ${lib.makeBinPath [pkgs.taskwarrior3]}
     '';
 
-    meta.mainProgram = "bin";
+    meta.mainProgram = "track-total-active-time";
   };
 in {
   options.soispha.programs.timewarrior = {
diff --git a/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify.track-timewarrior.py b/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify.track-timewarrior.py
new file mode 100755
index 00000000..0bef8bc2
--- /dev/null
+++ b/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify.track-timewarrior.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+###############################################################################
+#
+# Copyright 2016 - 2021, 2023, Gothenburg Bit Factory
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# https://www.opensource.org/licenses/mit-license.php
+#
+###############################################################################
+
+import json
+import subprocess
+import sys
+
+# Hook should extract all the following for use as Timewarrior tags:
+#   UUID
+#   Project
+#   Tags
+#   Description
+#   UDAs
+
+try:
+    input_stream = sys.stdin.buffer
+except AttributeError:
+    input_stream = sys.stdin
+
+
+MAX_ACTIVE = 1
+
+
+def extract_tags_from(json_obj) -> [str]:
+    # Extract attributes for use as tags.
+    tags = [json_obj["description"]]
+
+    if "project" in json_obj:
+        tags.append(json_obj["project"])
+
+    if "tags" in json_obj:
+        if type(json_obj["tags"]) is str:
+            # Usage of tasklib (e.g. in taskpirate) converts the tag list into a string
+            # If this is the case, convert it back into a list first
+            # See https://github.com/tbabej/taskpirate/issues/11
+            tags.extend(json_obj["tags"].split(","))
+        else:
+            tags.extend(json_obj["tags"])
+
+    return tags
+
+
+def extract_annotation_from(json_obj):
+    if "annotations" not in json_obj:
+        return "''"
+
+    return json_obj["annotations"][0]["description"]
+
+
+def main(old, new):
+    start_or_stop = ""
+
+    # Started task.
+    if "start" in new and "start" not in old:
+        # Prevent this task from starting if "task +ACTIVE count" is greater than "MAX_ACTIVE".
+        p = subprocess.Popen(
+            ["task", "+ACTIVE", "status:pending", "count", "rc.verbose:off"],
+            stdout=subprocess.PIPE,
+        )
+        out, err = p.communicate()
+        count = int(out.rstrip())
+        if count >= MAX_ACTIVE:
+            print(
+                f"Only {MAX_ACTIVE} task(s) can be active at a time.",
+            )
+            sys.exit(1)
+        else:
+            start_or_stop = "start"
+
+    # Stopped task.
+    elif ("start" not in new or "end" in new) and "start" in old:
+        start_or_stop = "stop"
+
+    if start_or_stop:
+        tags = extract_tags_from(new)
+
+        subprocess.call(["timew", start_or_stop] + tags + [":yes"])
+
+    # Modifications to task other than start/stop
+    elif "start" in new and "start" in old:
+        old_tags = extract_tags_from(old)
+        new_tags = extract_tags_from(new)
+
+        if old_tags != new_tags:
+            subprocess.call(["timew", "untag", "@1"] + old_tags + [":yes"])
+            subprocess.call(["timew", "tag", "@1"] + new_tags + [":yes"])
+
+        old_annotation = extract_annotation_from(old)
+        new_annotation = extract_annotation_from(new)
+
+        if old_annotation != new_annotation:
+            subprocess.call(["timew", "annotate", "@1", new_annotation])
+
+
+if __name__ == "__main__":
+    old = json.loads(input_stream.readline().decode("utf-8", errors="replace"))
+    new = json.loads(input_stream.readline().decode("utf-8", errors="replace"))
+    print(json.dumps(new))
+    main(old, new)
diff --git a/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-total-active-time.py b/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify.track-total-active-time.py
index d5b380d0..b98e02fc 100755
--- a/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-total-active-time.py
+++ b/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify.track-total-active-time.py
@@ -20,13 +20,10 @@ By default, this plugin allows to have one task active at a time. This can be ch
 ``1``.
 
 Note:
-    This hook requires Python 3 and the `taskw`_ package to be installed which provides the python bindings for Taskwarrior!
-    Also note that this hook is only compatible with Taskwarrior version greater or equal to 2.4!
+    Note that this hook is only compatible with Taskwarrior version greater or equal to 2.4!
 
 This hook is a fork from `kostajh/taskwarrior-time-tracking-hook`_
 
-.. _taskw:
-   https://pypi.python.org/pypi/taskw
 .. _kostajh/taskwarrior-time-tracking-hook:
    https://github.com/kostajh/taskwarrior-time-tracking-hook
 """
@@ -36,18 +33,12 @@ import json
 import re
 import sys
 import subprocess
-from taskw import TaskWarrior
 from typing import TypeVar
 
 TIME_FORMAT = "%Y%m%dT%H%M%SZ"
 UDA_KEY = "total_active_time"
 
-w = TaskWarrior(config_filename=sys.argv[4].replace("rc:", ""))
-config = w.load_config(config_filename=sys.argv[4].replace("rc:", ""))
-if "max_active_tasks" in config:
-    MAX_ACTIVE = int(config["max_active_tasks"])
-else:
-    MAX_ACTIVE = 1
+MAX_ACTIVE = 1
 
 """Compiled regular expression for the duration as ISO-8601 formatted string."""
 ISO8601DURATION = re.compile("P((\d*)Y)?((\d*)M)?((\d*)D)?T((\d*)H)?((\d*)M)?((\d*)S)?")
diff --git a/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-timewarrior.py b/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-timewarrior.py
deleted file mode 100755
index b482af6a..00000000
--- a/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-timewarrior.py
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2016-present Arctic Ice Studio <development@arcticicestudio.com>
-# Copyright (C) 2016-present Sven Greb <development@svengreb.de>
-
-# Project:    igloo
-# Repository: https://github.com/arcticicestudio/igloo
-# License:    MIT
-# References:
-#   https://taskwarrior.org/docs
-#   https://taskwarrior.org/docs/timewarrior
-#   timew(1)
-#   task(1)
-
-"""A Taskwarrior hook to track the time of a active task with Taskwarrior.
-
-This hook will extract all of the following for use as Timewarrior tags:
-
-* UUID
-* Project
-* Tags
-* Description
-* UDAs
-
-Note:
-    This hook requires Python 3 and is only compatible with Taskwarrior version greater or equal to 2.4!
-
-This hook is a fork from the `official on-modify.timewarrior hook`_.
-
-.. _`official on-modify.timewarrior hook`:
-   https://github.com/GothenburgBitFactory/timewarrior/blob/dev/ext/on-modify.timewarrior
-"""
-
-import subprocess
-import sys
-from json import loads, dumps
-from os import system
-from sys import stdin
-from taskw import TaskWarrior
-
-# Make no changes to the task, simply observe.
-old = loads(stdin.readline())
-new = loads(stdin.readline())
-print(dumps(new))
-
-
-w = TaskWarrior(config_filename=sys.argv[4].replace("rc:", ""))
-config = w.load_config(config_filename=sys.argv[4].replace("rc:", ""))
-if "max_active_tasks" in config:
-    MAX_ACTIVE = int(config["max_active_tasks"])
-else:
-    MAX_ACTIVE = 1
-
-
-# Extract attributes for use as tags.
-tags = [new["description"]]
-
-if "project" in new:
-    project = new["project"]
-    tags.append(project)
-    if "." in project:
-        tags.extend([tag for tag in project.split(".")])
-
-if "tags" in new:
-    tags.extend(new["tags"])
-
-combined = " ".join(["'%s'" % tag for tag in tags]).encode("utf-8").strip()
-
-# Task has been started.
-if "start" in new and "start" not in old:
-    # Prevent this task from starting if "task +ACTIVE count" is greater than "MAX_ACTIVE".
-    p = subprocess.Popen(
-        ["task", "+ACTIVE", "status:pending", "count", "rc.verbose:off"],
-        stdout=subprocess.PIPE,
-    )
-    out, err = p.communicate()
-    count = int(out.rstrip())
-    if count >= MAX_ACTIVE:
-        print(
-            "Only %d task(s) can be active at a time. "
-            "See 'max_active_tasks' in .taskrc." % MAX_ACTIVE
-        )
-        sys.exit(1)
-
-    system("timew start " + combined.decode() + " :yes")
-
-# Task has been stopped.
-elif "start" not in new and "start" in old:
-    system("timew stop " + combined.decode() + " :yes")
-
-# Any task that is active, with a non-pending status should not be tracked.
-elif "start" in new and new["status"] != "pending":
-    system("timew stop " + combined.decode() + " :yes")