aboutsummaryrefslogtreecommitdiffstats
path: root/modules/home/conf/taskwarrior/hooks/scripts
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-05-23 13:31:11 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-05-23 13:33:40 +0200
commitfd9b0ecef4142a62b45404700ba1cff488f84a73 (patch)
treeef6c1f74f05a2220a41ccff4b0890c39229f32f7 /modules/home/conf/taskwarrior/hooks/scripts
parentrefactor(pkgs): Categorize into `by-name` shards (diff)
downloadnixos-config-fd9b0ecef4142a62b45404700ba1cff488f84a73.zip
refactor(modules/home): Setup as "normal" NixOS module
Diffstat (limited to 'modules/home/conf/taskwarrior/hooks/scripts')
-rwxr-xr-xmodules/home/conf/taskwarrior/hooks/scripts/on-add_enforce-policies.sh41
-rwxr-xr-xmodules/home/conf/taskwarrior/hooks/scripts/on-add_sync-git-repo.sh42
-rwxr-xr-xmodules/home/conf/taskwarrior/hooks/scripts/on-modify_sync-git-repo.sh43
-rwxr-xr-xmodules/home/conf/taskwarrior/hooks/scripts/on-modify_track-timewarrior.py94
-rwxr-xr-xmodules/home/conf/taskwarrior/hooks/scripts/on-modify_track-total-active-time.py148
5 files changed, 368 insertions, 0 deletions
diff --git a/modules/home/conf/taskwarrior/hooks/scripts/on-add_enforce-policies.sh b/modules/home/conf/taskwarrior/hooks/scripts/on-add_enforce-policies.sh
new file mode 100755
index 00000000..eaf7f30c
--- /dev/null
+++ b/modules/home/conf/taskwarrior/hooks/scripts/on-add_enforce-policies.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# override shell lib output to stdout
+eprint() {
+ # shellcheck disable=SC2317
+ print "$@"
+}
+eprintln() {
+ # shellcheck disable=SC2317
+ println "$@"
+}
+
+enable_hook_dbg() {
+ debug_hooks="$(task _get rc.debug.hooks)"
+ [ "$debug_hooks" ] && [ "$debug_hooks" -ge 1 ] && dbg_enable
+}
+
+enforce_project() {
+ project="$(jq '.project' "$(ptmp "$1")")"
+ [ "$project" = "null" ] && die "No project supplied!"
+
+ if grep -q "^$(echo "$project" | sed 's|"\(.*\)"|\1|')\$" "$(ptmp "%PROJECTS_NEWLINE")"; then
+ dbg "project('$project') is a valid part of %PROJECTS_COMMA"
+ else
+ die "The project '$(echo "$project" | sed 's|"||g')' is not registered with the nix config, registered projects: %PROJECTS_COMMA"
+ fi
+}
+
+read -r new_task
+# We don't change the task, thus immediately return the json
+echo "$new_task"
+
+enable_hook_dbg
+enforce_project "$new_task"
+
+exit 0
+
+# vim: ft=sh
diff --git a/modules/home/conf/taskwarrior/hooks/scripts/on-add_sync-git-repo.sh b/modules/home/conf/taskwarrior/hooks/scripts/on-add_sync-git-repo.sh
new file mode 100755
index 00000000..dadc96b0
--- /dev/null
+++ b/modules/home/conf/taskwarrior/hooks/scripts/on-add_sync-git-repo.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# override shell lib output to stdout
+eprint() {
+ # shellcheck disable=SC2317
+ print "$@"
+}
+eprintln() {
+ # shellcheck disable=SC2317
+ println "$@"
+}
+
+enable_hook_dbg() {
+ debug_hooks="$(task _get rc.debug.hooks)"
+ [ "$debug_hooks" ] && [ "$debug_hooks" -ge 1 ] && dbg_enable
+}
+
+update_git_repo() {
+ task_data="$(task _get rc.data.location)"
+ [ "$task_data" ] || die "Taskwarrior should have a location set"
+
+ cd "$task_data" || die "(BUG?): Your data.location path is not accessable"
+
+ [ -d ./.git/ ] || git init
+
+ git add .
+ git commit --message="chore: Update" --no-gpg-sign
+}
+
+read -r new_task
+# We don't change the task, thus immediately return the json
+echo "$new_task"
+
+enable_hook_dbg
+update_git_repo
+
+exit 0
+
+# vim: ft=sh
diff --git a/modules/home/conf/taskwarrior/hooks/scripts/on-modify_sync-git-repo.sh b/modules/home/conf/taskwarrior/hooks/scripts/on-modify_sync-git-repo.sh
new file mode 100755
index 00000000..25813e46
--- /dev/null
+++ b/modules/home/conf/taskwarrior/hooks/scripts/on-modify_sync-git-repo.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# override shell lib output to stdout
+eprint() {
+ # shellcheck disable=SC2317
+ print "$@"
+}
+eprintln() {
+ # shellcheck disable=SC2317
+ println "$@"
+}
+
+enable_hook_dbg() {
+ debug_hooks="$(task _get rc.debug.hooks)"
+ [ "$debug_hooks" ] && [ "$debug_hooks" -ge 1 ] && dbg_enable
+}
+
+update_git_repo() {
+ task_data="$(task _get rc.data.location)"
+ [ "$task_data" ] || die "Taskwarrior should have a location set"
+
+ cd "$task_data" || die "(BUG?): Your data.location path is not accessable"
+
+ [ -d ./.git/ ] || git init
+
+ git add .
+ git commit --message="chore: Update" --no-gpg-sign
+}
+
+read -r _old_task
+read -r new_task
+# We don't change the task, thus immediately return the json
+echo "$new_task"
+
+enable_hook_dbg
+update_git_repo
+
+exit 0
+
+# vim: ft=sh
diff --git a/modules/home/conf/taskwarrior/hooks/scripts/on-modify_track-timewarrior.py b/modules/home/conf/taskwarrior/hooks/scripts/on-modify_track-timewarrior.py
new file mode 100755
index 00000000..b482af6a
--- /dev/null
+++ b/modules/home/conf/taskwarrior/hooks/scripts/on-modify_track-timewarrior.py
@@ -0,0 +1,94 @@
+#!/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/home/conf/taskwarrior/hooks/scripts/on-modify_track-total-active-time.py b/modules/home/conf/taskwarrior/hooks/scripts/on-modify_track-total-active-time.py
new file mode 100755
index 00000000..d5b380d0
--- /dev/null
+++ b/modules/home/conf/taskwarrior/hooks/scripts/on-modify_track-total-active-time.py
@@ -0,0 +1,148 @@
+#!/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
+# task(1)
+
+"""A Taskwarrior hook to track the total active time of a task.
+
+The tracked time is stored in a UDA task duration attribute named ``totalactivetime`` of type ``duration`` holding the total number of seconds the task was
+active. The tracked time can then be included in any report by adding the ``totalactivetime`` column.
+
+By default, this plugin allows to have one task active at a time. This can be changed by setting ``max_active_tasks`` in ``.taskrc`` to a value greater than
+``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!
+
+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)?")
+
+"""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)
+
+
+def duration_str_to_time_delta(duration_str: DurationType) -> datetime.timedelta:
+ """Converts duration string into a timedelta object.
+
+ :param duration_str: The duration
+ :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)
+ elif duration_str.endswith("seconds"):
+ value = int(duration_str.rstrip("seconds"))
+ else:
+ value = int(duration_str)
+ return datetime.timedelta(seconds=value)
+
+
+def main():
+ original = json.loads(sys.stdin.readline())
+ modified = json.loads(sys.stdin.readline())
+
+ # An active task has just been started.
+ if "start" in modified and "start" not in original:
+ # 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)
+
+ # An active task has just been stopped.
+ if "start" in original and "start" not in modified:
+ # Calculate the elapsed time.
+ start = datetime.datetime.strptime(original["start"], TIME_FORMAT)
+ end = datetime.datetime.utcnow()
+
+ if UDA_KEY not in modified:
+ modified[UDA_KEY] = 0
+
+ this_duration = end - start
+ total_duration = this_duration + duration_str_to_time_delta(
+ str(modified[UDA_KEY])
+ )
+ print(
+ "Total Time Tracked: %s (%s in this instance)"
+ % (total_duration, this_duration)
+ )
+ modified[UDA_KEY] = (
+ str(int(total_duration.days * (60 * 60 * 24) + total_duration.seconds))
+ + "seconds"
+ )
+
+ return json.dumps(modified, separators=(",", ":"))
+
+
+def cmdline():
+ sys.stdout.write(main())
+
+
+if __name__ == "__main__":
+ cmdline()