GothenburgBitFactory / GothenburgBitFactory/taskwarrior
Add more hook types, or allow on-launch hook to modify args
- Dominant language
- C++
- Stars
- 6.1k
- Forks
- 423
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 11
Description
#### To request a feature...
* Clearly describe the feature.
Provide hooks the ability to modify argunment
* Clearly state the use case. We are only interested in use cases, do not waste time with implementation details or suggested syntax.
This feature allows hooks to modify input arguments. Currently, there exists no way to accomplish such a task. I was fed up with needing to inputs current year for datetime inputs (e.g. for due, wait, etc.) and was about to write a hook that intelligently parses my inputs. However, I realised that the `on-add` and `on-modify` hooks run after the input argument had been parsed by TW. Therefore, if there are errors in input argument (e.g. datetime format not expected by TW), TW will fail to run before running the hooks. The `on-launch` hook, while runs before TW parsing happens, has no ability to modify input arguments as well.
For now, I had written a lightweight wrapper around TW that accomplishes this:
```python
#!/usr/bin/env python
import sys
import datetime
import dateutil.parser
import subprocess
def parse_with_dateutil(inputs, ensure_future=True):
# use dateutil to intelligently parse datetime.
parser = dateutil.parser.parser()
try:
out = parser.parse(inputs)
if out.hour == 0 and out.minute == 0:
# skip adding time if it's the default 00:00
# DATE_FORMAT = "YYYY-MM-DD"
DATEUTIL_FORMAT = "%Y-%m-%d"
else:
# DATE_FORMAT = "YYYY-MM-DDThh:mm"
DATEUTIL_FORMAT = "%Y-%m-%dT%H:%M%z"
except ValueError:
# dateutil couldn't understand it. Probably is TW's builtin special date
return None
if ensure_future:
# ensure the target is in the future
now = datetime.datetime.now()
if now > out:
# check that if user had omitted some settings, make the default to be the
# immediate next instance of the given ambigious time.
user_provided = parser._parse(inputs)[0]
if getattr(user_provided, "day") is None:
out = out + dateutil.relativedelta.relativedelta(days=1)
elif getattr(user_provided, "month") is None:
out = out + dateutil.relativedelta.relativedelta(months=1)
elif getattr(user_provided, "year") is None:
out = out + dateutil.relativedelta.relativedelta(years=1)
return out.strftime(DATEUTIL_FORMAT)
class TWCommandline:
def __init__(self, argv):
"""This class is for easy editing argv."""
self.argv = []
for arg in argv[1:]: # first arg is this script
# try different way to separatethe arg
for sep in ('=', ':'):
if len(arg.split(sep)) == 2:
_tmp = arg.split(sep)
self.argv.append([_tmp[0], sep, _tmp[1]])
break
else: # add the original argunment
self.argv.append(arg)
def build_cmd(self):
return ["".join(a) if isinstance(a, list) else a for a in self.argv]
# print(TWCommandline(sys.argv).out2())
twc = TWCommandline(sys.argv)
# don't do any modification if a custom dateformat is used
if not any(arg[0] == "rc.dateformat" for arg in twc.argv):
# else, use a dateutil-powered parser.
timeformat = ("until", "wait", "due", "scheduled")
for arg in twc.argv:
if arg[0] in timeformat:
result = parse_with_dateutil(arg[2])
if result is not None:
# notify user of the auto-parsing
print("> Auto-converted '{}:{}' -> '{}:{}'".format(
arg[0], arg[2], arg[0], result
))
# apply new parsed datetime
arg[2] = result
exit(subprocess.run(["/usr/bin/task"] + twc.build_cmd()).returncode)
```
With this script saved as `tw-wrapper.py` and with a bash alias
```sh
alias task="tw-wrapper.py"
```
I can achieve results like:
```sh
# Note that this command is ran at May 17th around 14:00
# AND I have enabled always assuming inputs are in the future (can be disabled)
$ task add test due:4th
> Auto-converted 'due:4th' -> 'due:2020-06-04'
Created task 33.
# This became next month's 4th because this month's 4th is in the past
$ task add test due:May-4th
> Auto-converted 'due:May-4th' -> 'due:2021-05-04'
Created task 34.
# Because May 4th this year is in the past, it interpret it as in the next year
$ task add test due:May-4th-2020
> Auto-converted 'due:May-4th-2020' -> 'due:2020-05-04'
Created task 35.
# If a year is provided as well (i.e. no ambiguity in input), it will take it as it is
$ task add test due:July-2th
> Auto-converted 'due:July-2th' -> 'due:2020-07-02'
Created task 36.
$ task add test due:1-1
> Auto-converted 'due:1-1' -> 'due:2021-01-01'
Created task 37.
$ task add test due:12-25
> Auto-converted 'due:12-25' -> 'due:2020-12-25'
Created task 38.
$ task add test due:9am
> Auto-converted 'due:9am' -> 'due:2020-05-18T09:00'
# 9am had passed today, so it became tomorrow's 9am
$ task add test due:9pm
> Auto-converted 'due:9pm' -> 'due:2020-05-17T21:00'
Created task 38
# 9pm hasn't pass so it became today's 9pm
```
This utilises the powerful `dateutil` so the possibilities are endless :) This also solves #1940
NOTE THAT while this script works, it is
1. Not as clean as a hook system, and
2. It does not allow auto-completion provided by TW in the shell (because it wraps the TW binary) so this is a huge let-down... (very frustrating to needing to type everything everytime!)
So, it would be good to provide hooks the ability to modify arguments on-the-fly (either by adding new hook type or just allow `on-launch` hook to modify arguments)
Thanks!
Contributor guide
Assessment
This issue has not been assessed yet.