Last active
September 9, 2021 18:19
-
-
Save ssokolow/acd90d9aea74a6a50995 to your computer and use it in GitHub Desktop.
"Tasks Due Today" popup for TaskWarrior
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
tasklib | |
pytz | |
tzlocal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
# -*- coding: utf-8 -*- | |
# pylint: disable=invalid-name | |
"""Simple, Zenity-based "MUST Be Done Today" dialog for TaskWarrior | |
Dependencies: | |
- tasklib to talk to TaskWarrior | |
- pytz to convert from local time to UTC for tasklib | |
- tzlocal so pytz knows what "local time" means | |
""" | |
from __future__ import (absolute_import, division, print_function, | |
with_statement, unicode_literals) | |
__author__ = "Stephan Sokolow (deitarion/SSokolow)" | |
__appname__ = "tw-daily" | |
__version__ = "0.1pre0" | |
__license__ = "MIT" | |
import itertools, signal, subprocess, sys | |
from datetime import date, datetime, time, timedelta | |
from tzlocal import get_localzone | |
from pytz import utc | |
# http://tasklib.readthedocs.org/en/latest/ | |
from tasklib.task import TaskWarrior | |
WIDTH, HEIGHT = 400, 350 | |
DAYS_WARNING = 1 | |
REDISPLAY_UNTIL_EMPTY = True | |
# Edit this to use something like YAD instead | |
ZENITY_ARGV = ['zenity', '--list', '--checklist', '--window-icon', 'info', | |
'--title', 'Tasks Due Today', | |
'--text', 'Select tasks to mark as completed:', | |
'--width', str(WIDTH), '--height', str(HEIGHT), | |
'--column', 'Done', '--column', 'ID', '--column', 'Name', | |
'--hide-column', '2'] | |
# -- Used to ensure that a SIGTERM can be propagated to Zenity -- | |
children_to_kill = [] | |
def kill_children(signum, stackfrm): # pylint: disable=unused-argument | |
"""Signal handler to kill child processes and exit""" | |
while children_to_kill: | |
children_to_kill.pop().kill() | |
sys.exit(1) | |
signal.signal(signal.SIGTERM, kill_children) | |
def check_output(*popenargs, **kwargs): | |
r"""Subprocess.check_output from Py 2.7.6, modified to cleanup children.""" | |
if 'stdout' in kwargs: | |
raise ValueError('stdout argument not allowed, it will be overridden.') | |
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) | |
children_to_kill.append(process) | |
output, _ = process.communicate() | |
retcode = process.poll() | |
if retcode: | |
cmd = kwargs.get("args") | |
if cmd is None: | |
cmd = popenargs[0] | |
raise subprocess.CalledProcessError(retcode, cmd, output=output) | |
return output | |
# -- End SIGTERM Handler -- | |
def main(): | |
"""The main entry point, compatible with setuptools entry points.""" | |
taskw = TaskWarrior(data_location='~/.task', create=False) | |
while True: # Loop until Cancel or no more tasks | |
# Force TW 2.4.0 and below (at least) to generate recurring tasks | |
# See: https://bug.tasktools.org/browse/TW-1531 | |
taskw.execute_command([]) | |
# Stuff due before midnight today | |
# (Thanks to tbabej (tasklib co-author) for the timezone juggling code) | |
localzone = get_localzone() | |
tomorrow = localzone.localize(datetime.combine( | |
date.today() + timedelta(days=DAYS_WARNING), | |
time(hour=1, second=1))).astimezone(utc).replace(tzinfo=None) | |
tasks = taskw.tasks.pending().filter(due__before=tomorrow) | |
if not len(tasks): | |
return # No tasks due. Exit the program. | |
try: | |
output = check_output(ZENITY_ARGV + | |
list(itertools.chain.from_iterable( | |
('FALSE', str(x['id']), x['description']) for x in tasks))) | |
except subprocess.CalledProcessError: | |
return # Cancel clicked. End the program. | |
# Parse the dialog's OK-button result and mark the selected IDs done. | |
done_ids = [int(x) for x in output.strip().split('|') if x] | |
for task in tasks: # Make sure we're insulated from ID reassignments | |
if task['id'] in done_ids: | |
task.done() | |
if not REDISPLAY_UNTIL_EMPTY: | |
return # End the program. | |
if __name__ == '__main__': | |
main() | |
# vim: set sw=4 sts=4 expandtab : |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Great script!
Thank you!
One note
With
tasklib==2.3.0
, I needed to change this import.To: