Skip to content

Instantly share code, notes, and snippets.

@ssokolow
Last active September 9, 2021 18:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ssokolow/acd90d9aea74a6a50995 to your computer and use it in GitHub Desktop.
Save ssokolow/acd90d9aea74a6a50995 to your computer and use it in GitHub Desktop.
"Tasks Due Today" popup for TaskWarrior
tasklib
pytz
tzlocal
#!/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 :
@gagallo7
Copy link

gagallo7 commented Sep 9, 2021

Great script!
Thank you!

One note

With tasklib==2.3.0, I needed to change this import.

from tasklib.task import TaskWarrior

To:

from tasklib import TaskWarrior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment