commit c57ccab0396e3cf436845049b0a2398b88b7087f Author: Aleksander Cynarski Date: Mon Mar 26 19:41:04 2018 +0200 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b471a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.pyc +bin/* +lib/* +src/* +man/* +dist/* +docs/build/* +include/* +.Python +*egg* +share/* +.tox/* +build/* diff --git a/readme.rst b/readme.rst new file mode 100644 index 0000000..55d9538 --- /dev/null +++ b/readme.rst @@ -0,0 +1,61 @@ +Taskwarrior Time Tracking Hook +============================== + +Ensure you have taskwarrior `2.4.x` or higher. + +Install ++++++++ + +Install using pip:: + + pip install taskwarrior-time-tracking-hook + +And add it to your Taskwarrior hooks:: + + mkdir -p ~/.task/hooks + ln -s `which taskwarrior_time_tracking_hook` ~/.task/hooks/on-modify.timetracking + +Add the ``totalactivetime`` user defined attribute configuration:: + + task config uda.totalactivetime.type duration + task config uda.totalactivetime.label Total active time + task config uda.totalactivetime.values '' + +Add to reports (replace list with whichever report type you want to modify):: + + task show report.list.labels + ID,Active,Age,...,Urg + task show report.list.columns + id,start.age,entry.age,...,urgency + + task config report.list.labels 'ID,Active,Age,Time Spent,...,Urg' + task config report.list.labels 'id,start.age,entry.age,totalactivetime,...,urgency' + +Usage ++++++ + +Use ``task start`` and ``task stop`` to record when you have +started and stopped working on tasks. + +Tracked time is stored in a task duration attribute named ``totalactivetime`` +holding the total number of seconds that the task was active. + +By default, this plugin allows you to have one task active at a time. You can +change this by setting `max_active_tasks` in `taskrc` to a value greater than 1. + +Un-install +++++++++++ + +Delete the hook:: + + rm ~/.task/hooks/on-modify.timetracking + +Remove the User Defined Attribute (UDA) configuration:: + + task config uda.totalactivetime.values + task config uda.totalactivetime.label + task config uda.totalactivetime.type + +Remove the Python program:: + + pip uninstall taskwarrior-time-tracking-hook diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..05dc94d --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +from setuptools import setup, find_packages + +setup( + name='taskwarrior-daily-timetracking-hook', + version='0.0.1', + url='https://git.cynarski.pl/paramah/taskwarrior-daily-timetracking-hook.git', + description=( + 'Track your daily time in a UDA in taskwarrior' + ), + author='Aleksander Cynarski', + author_email='Aleksander Cynarski', + classifiers=[ + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + ], + install_requires=[ + "taskw" + ], + packages=find_packages(), + entry_points={ + 'console_scripts': [ + 'taskwarrior_daily_timetracking_hook = taskwarrior_daily_timetracking_hook:cmdline' + ], + }, +) diff --git a/taskwarrior_daily_timetracking_hook/__init__.py b/taskwarrior_daily_timetracking_hook/__init__.py new file mode 100644 index 0000000..b6ddc06 --- /dev/null +++ b/taskwarrior_daily_timetracking_hook/__init__.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +import datetime +import json +import re +import sys +import subprocess +from taskw import TaskWarrior + +TIME_FORMAT = '%Y%m%dT%H%M%SZ' +UDA_KEY = 'dailyactivetime' + +w = TaskWarrior() +config = w.load_config() +if ('max_active_tasks' in config): + MAX_ACTIVE = int(config['max_active_tasks']) +else: + MAX_ACTIVE = 1 + +ISO8601DURATION = re.compile( + "P((\d*)Y)?((\d*)M)?((\d*)D)?T((\d*)H)?((\d*)M)?((\d*)S)?") + +# Convert duration string into a timedelta object. +# Valid formats for duration_str include +# - int (in seconds) +# - string ending in seconds e.g "123seconds" +# - ISO-8601: e.g. "PT1H10M31S" +def duration_str_to_time_delta(duration_str): + 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 inactive task has just been started. + if 'start' in modified and 'start' not in original: + # Check if `task +ACTIVE count` is greater than MAX_ACTIVE. If so + # prevent this task from starting. + 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: + # Let's see how much time has elapsed + 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( + "Daily 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()