diff options
Diffstat (limited to '')
-rw-r--r-- | modules/by-name/ti/timewarrior/module.nix | 45 | ||||
-rw-r--r-- | modules/by-name/ti/timewarrior/nord.theme.license | 9 | ||||
-rwxr-xr-x | modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-timewarrior.py | 94 | ||||
-rwxr-xr-x | modules/by-name/ti/timewarrior/taskwarrior_hooks/on-modify.track-timewarrior.py | 109 | ||||
-rwxr-xr-x | modules/by-name/ti/timewarrior/taskwarrior_hooks/on-modify.track-total-active-time.py (renamed from modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-total-active-time.py) | 52 |
5 files changed, 153 insertions, 156 deletions
diff --git a/modules/by-name/ti/timewarrior/module.nix b/modules/by-name/ti/timewarrior/module.nix index d6b4dc32..90a541d8 100644 --- a/modules/by-name/ti/timewarrior/module.nix +++ b/modules/by-name/ti/timewarrior/module.nix @@ -1,3 +1,12 @@ +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. { config, lib, @@ -7,49 +16,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.taskwarrior - # TODO: Use a `taskw` package, that actually supports newer python variants <2024-07-13> - (pkgs.python311.withPackages (pythonPackages: - with pythonPackages; [ - taskw - ])) + pkgs.taskwarrior3 + 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.taskwarrior pkgs.timewarrior]} + install -Dm755 ${./taskwarrior_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.taskwarrior - # TODO: Use a `taskw` package, that actually supports newer python variants <2024-07-13> - (pkgs.python311.withPackages (pythonPackages: - with pythonPackages; [ - taskw - ])) + pkgs.taskwarrior3 + 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.taskwarrior]} + install -Dm755 ${./taskwarrior_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/nord.theme.license b/modules/by-name/ti/timewarrior/nord.theme.license new file mode 100644 index 00000000..eae6a84c --- /dev/null +++ b/modules/by-name/ti/timewarrior/nord.theme.license @@ -0,0 +1,9 @@ +nixos-config - My current NixOS configuration + +Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +SPDX-License-Identifier: GPL-3.0-or-later + +This file is part of my nixos-config. + +You should have received a copy of the License along with this program. +If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. 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") diff --git a/modules/by-name/ti/timewarrior/taskwarrior_hooks/on-modify.track-timewarrior.py b/modules/by-name/ti/timewarrior/taskwarrior_hooks/on-modify.track-timewarrior.py new file mode 100755 index 00000000..3b42b3f2 --- /dev/null +++ b/modules/by-name/ti/timewarrior/taskwarrior_hooks/on-modify.track-timewarrior.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +# nixos-config - My current NixOS configuration +# +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# Copyright 2016 - 2021, 2023, Gothenburg Bit Factory +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +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/taskwarrior_hooks/on-modify.track-total-active-time.py index d5b380d0..303a5c57 100755 --- a/modules/by-name/ti/timewarrior/taskwarirror_hooks/on-modify_track-total-active-time.py +++ b/modules/by-name/ti/timewarrior/taskwarrior_hooks/on-modify.track-total-active-time.py @@ -1,8 +1,17 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# nixos-config - My current NixOS configuration +# # Copyright (C) 2016-present Arctic Ice Studio <development@arcticicestudio.com> # Copyright (C) 2016-present Sven Greb <development@svengreb.de> +# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This file is part of my nixos-config. +# +# You should have received a copy of the License along with this program. +# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. # Project: igloo # Repository: https://github.com/arcticicestudio/igloo @@ -20,37 +29,24 @@ 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 """ import datetime 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 - -"""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)?") +MAX_ACTIVE = 1 """The duration type either as integer (in seconds), as ISO-8601 formatted string ("PT1H10M31S") or the seconds suffixed with "seconds".""" DurationType = TypeVar("DurationType", str, int) @@ -63,31 +59,7 @@ def duration_str_to_time_delta(duration_str: DurationType) -> datetime.timedelta :return: The duration as timedelta object """ if duration_str.startswith("P"): - match = ISO8601DURATION.match(duration_str) - if match: - year = match.group(2) - month = match.group(4) - day = match.group(6) - hour = match.group(8) - minute = match.group(10) - second = match.group(12) - value = 0 - if second: - value += int(second) - if minute: - value += int(minute) * 60 - if hour: - value += int(hour) * 3600 - if day: - value += int(day) * 3600 * 24 - if month: - # Assume a month is 30 days for now. - value += int(month) * 3600 * 24 * 30 - if year: - # Assume a year is 365 days for now. - value += int(year) * 3600 * 24 * 365 - else: - value = int(duration_str) + value = datetime.fromisoformat(duration_str) elif duration_str.endswith("seconds"): value = int(duration_str.rstrip("seconds")) else: |