Skip to content

Instantly share code, notes, and snippets.

@soraxas
Last active February 6, 2023 23:38
Show Gist options
  • Save soraxas/b25896f177f883a889a0763c8f593884 to your computer and use it in GitHub Desktop.
Save soraxas/b25896f177f883a889a0763c8f593884 to your computer and use it in GitHub Desktop.
script:Taskwarrior wrapper for intelligently parses datetime format (due, scheduled, etc.) in human language.
#!/bin/bash
# By soraxas
# This file shall be the wrapper for taskwarrior
# Do something like:
# alias task="taskwarrior-datetime-parser-wrapper"
# argunments that contain datetime format
TIME_FORMAT="(^|\W)(until|wait|due|scheduled):"
TASK="$(which task)"
if [ -z "$TASK" ]; then
echo "Binary task not found!"
exit 1
fi
# check if any argunment matches it
if ! echo "$@" | grep -qP "$TIME_FORMAT"; then
# no date format in args.
"$TASK" "$@"
exit $?
fi
# use python wrapper to intelligently parse datetime
# The reason to firt use a bash script to check is because
# python has a slow startup time. Which would slow down even
# the very basic command without any args.
# Therefore, this method ensures that python process is called
# only when it is necessary (i.e. when datetime format presents)
# and needs potential parsing.
PYTHON="$(which python3)"
# embed the python script here to avoid having multiple files.
"$PYTHON" -c '
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 its 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 couldnt understand it. Probably is TWs 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]
twc = TWCommandline(sys.argv)
# dont 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(["'"$TASK"'"] + twc.build_cmd()).returncode)
' $@
exit $?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment