Task managment through your menu-bar.
#!/usr/bin/env -S PATH="${PATH}:/opt/homebrew/bin:/usr/local/bin" python3
# -*- coding: utf-8 -*-
#
# Taskwarrior
#
# <xbar.title>Taskwarrior</xbar.title>
# <xbar.version>v2.0</xbar.version>
# <xbar.author>Christoph Russ</xbar.author>
# <xbar.author.github>christophruss</xbar.author.github>
# <xbar.desc>Task managment through your menu-bar.</xbar.desc>
# <xbar.dependencies>task,python</xbar.dependencies>
# <xbar.image>http://i.imgur.com/vjEt7Is.jpg</xbar.image>
#
# Dependencies:
# taskwarrior (http://taskwarrior.org)
# available via homebrew `brew install task`
#
# TODO
# * In a sub-menu (at the bottom) Allow to restart previously completed tasks
# * Allow to delete tasks (also in a sub-menu?)
#
import os
import re
import subprocess
from subprocess import Popen, PIPE
def trigger_actions(argv):
t_id = argv[-1]
action = argv[-2]
path_task = subprocess.check_output(['which', 'task']).decode('ascii').rstrip()
subprocess.call([path_task, t_id, action])
def build_command(t_id, action, refresh=True):
cmd = ''
if refresh:
cmd = cmd + ' refresh=true'
cmd = cmd + ' bash=python3 param1=' + __file__ + ' param2='
cmd = cmd + action
cmd = cmd + ' param3=' + str(t_id)
cmd = cmd + ' terminal=false'
return cmd
def print_output(
cmd,
color,
head,
print_content=True,
command='',
ignore_id_list=[],
highlight_id_list=[],
highlight_color=' color=Red',
highlight_command='',
alternate_command=''):
output = ''
path_task = subprocess.check_output(['which', 'task']).decode('ascii').rstrip()
# important: PIPE the stderr, since task likes to use that - a lot ...
p = Popen([path_task, cmd],
stdin=None, stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
output_lines = output.decode('utf-8').split('\n')
id_list = []
content_lines = []
for output_line in output_lines:
output_line = output_line.rstrip()
if not output_line:
continue
# When looking for 'active' or 'next' tasks, we want to look only at
# lines that start with a digit or a -. Other line are likely extra
# data like annotations from bugwarrior sync
content_id = output_line.split()[0]
if not content_id.isdigit() and content_id not in ['--', '-', 'ID']:
continue
line_groups = re.match('^(\s*\d+|\s*ID|\s*-+)(.*)', output_line)
content_lines.append('[{}]{}'.format(*line_groups.groups()))
content_count = len(content_lines[2:-1])
if head:
if content_count == 0:
print("ⓣ") # ⓪ #⓿
elif content_count < 21:
circle_number = ["⓪", "①", "②", "③", "④", "⑤", "⑥",
"⑦", "⑧", "⑨", "⑩", "⑪", "⑫", "⑬",
"â‘", "â‘®", "⑯", "â‘°", "⑱", "⑲", "⑳"]
print(circle_number[content_count]) # + '|' + color_pending
else:
print(str(content_count)) # + '| color=Red'
print('---')
if content_count < 1:
return id_list
table_head = content_lines[0]
# total_number_of_tasks = content_lines[-1]
content_formatting = ' | size=12 font=Courier'
if print_content:
print(table_head + content_formatting)
print('---')
for content_line in content_lines[2:-1]:
content_re = re.match('^\[\s*(.+)\]\s+([0-9A-Fa-f]+)?', content_line)
content_id = content_re.group(1)
if content_id == '-':
# should be the UUID in this case
content_id = content_re.group(2)
if content_id in ignore_id_list:
continue
id_list.append(content_id)
if print_content:
cmd = ''
if len(command) > 0:
cmd = build_command(t_id=content_id, action=command)
if content_id in highlight_id_list:
if len(highlight_command) > 0:
cmd = build_command(
t_id=content_id, action=highlight_command)
print(content_line + content_formatting + highlight_color + cmd)
else:
print(content_line + content_formatting + color + cmd)
# adding an alternative command (press ALT for this!)
# printing the same stuff again, only with a different action
# attached
if len(alternate_command) > 0:
alt_cmd = build_command(
t_id=content_id, action=alternate_command)
if content_id in highlight_id_list:
print(content_line + content_formatting + highlight_color + alt_cmd + ' alternate=true')
else:
print(content_line + content_formatting + color + alt_cmd + ' alternate=true')
return id_list
def is_darkmode():
FNULL = open(os.devnull, 'w')
return_code = subprocess.call(['/usr/bin/defaults', 'read', '-g',
'AppleInterfaceStyle'], stdout=FNULL, stderr=subprocess.STDOUT)
if (return_code == 1):
return False
else:
return True
def main(argv):
if len(argv) > 1:
trigger_actions(argv)
exit()
if is_darkmode():
color_running = ' color=Red'
color_pending = ' color=Yellow'
color_completed = ' color=Green'
else:
color_running = ' color=Red'
color_pending = ' color=Black'
color_completed = ' color=Green'
id_list = print_output('active', color_running, True, print_content=False)
if len(id_list) > 0:
print('---')
print_output(
'next',
color_pending,
False,
command='start',
highlight_id_list=id_list,
highlight_color=color_running,
highlight_command='stop',
alternate_command='done')
print('---')
# ok, so if you want to delete a command, you have to press done first ...
# sorry, but there is only one alternative command I can provide above ...
print_output(
'completed',
color_completed,
False,
command='start',
alternate_command='delete')
return
if __name__ == "__main__":
import sys
main(sys.argv)