forked from grovesteyn/tchart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tchart.py
executable file
·290 lines (257 loc) · 12.1 KB
/
tchart.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#!/usr/bin/env python
# Read task output and organise by day
import pdb
import json
import commands
import argparse
import snack # On Ubuntu do apt-get install python-newt to get snack module
from datetime import datetime, timedelta, date
from blessings import Terminal
from operator import itemgetter
class TaskReport(object):
"""
This is the main class for this programme.
"""
def __init__(self):
self.weeks = 4 # Number of weeks to cover
self.weekstart = date.today() - timedelta(date.weekday(date.today()))
self.cutoffdate = self.weekstart + timedelta(weeks=self.weeks)
self.construct_cmdline()
self.load_tasks()
# Call the appropriate function to either run the task selector,
# or print the task report to the console.
if self.selector:
while True:
selectorresult = self.run_selector()
if selectorresult[1]:
break
print selectorresult[0]
self.load_tasks()
else:
self.run_report()
def construct_cmdline(self):
"""
Construct the Taskwarrior commandline to be used.
"""
parser = argparse.ArgumentParser(description='Display tasks.')
parser.add_argument("-s", "--select", action="store_true",
help="select tasks and modify")
parser.add_argument("-f", "--filter", nargs='+',
help='a string specifying the task filter')
args = parser.parse_args()
# if args.select:
# print "select tasks"
if args.filter:
self.consoleline = " ".join(args.filter)
self.consoleline = (self.consoleline + ' status:pending') \
if self.consoleline.find('status:pending') < 0 \
else self.consoleline
self.consoleline = (self.consoleline + ' export') \
if self.consoleline.find('export') < 0 \
else self.consoleline
self.consoleline = 'task ' + self.consoleline
else:
# When no arguments are given.
self.consoleline = 'task due.any: status:pending export'
# Indicates whether task selector was requested. Either True or None
self.selector = args.select
print self.consoleline
print
def load_tasks(self):
"""
Load and sort the task list
"""
tstring = commands.getoutput(self.consoleline)
tstring = '[' + tstring + ']' # Adds brackets to comply with Json
tasks = json.loads(tstring)
# This section removes the tasks with no due dates and adds them to the
# front of the list after it has been sorted
due_list = []
nodue_list = []
for task in tasks:
if 'due' not in task:
nodue_list.append(task)
else:
due_list.append(task)
tasks = due_list
try:
# Group by date and then by project
tasks.sort(key=itemgetter('due', 'project'))
except KeyError: # If no 'project' attribute for one or more tasks
tasks.sort(key=itemgetter('due'))
print "Warning: Some tasks do not have a project assigned."
self.tasks = nodue_list + tasks
def run_report(self):
"""
Compile and print the task report.
"""
wdays = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]
letters = 3 # No of letters to use for wday names. 1 - 3
istty = True
indent = letters # The indent per week day
# TODO "indent" is used and defined here an in construct_cmdline. Define once?
if istty: # This is currently hard coded, but can be an input.
term = Terminal()
else:
term = Terminal(stream="not tty") # When not run from a tty
# Calcs how many task lines can fit onto the screen.
taskspace = (term.height if term.is_a_tty else 40) - (5 + letters)
# Compile the line showing each Monday's date.
dateline = self.weekstart.strftime("%d %b %Y")
if self.weeks > 1: # Add additional Mondays if required
for week in range(1, self.weeks): # i.e. week 1 is the second week.
# No of spaces to add on to existing dateline before
# inserting next date
weekindent = len(wdays) * week * indent - len(dateline)
dateline = dateline + ' ' * weekindent + (self.weekstart + \
timedelta(days=7 * week)).strftime("%d %b %Y") # Position
# Compile the day header sting (includes newlines)
dayheader = ''
for lineNo in range(letters):
for day in wdays * self.weeks:
# add the letter and spacing to indent for each day and makes mondays bold
dayheader = dayheader + (term.bold(day[lineNo]) if (day == \
"MON" and not self.selector) else day[lineNo]) + ' ' \
* (indent - 1)
dayheader = dayheader + '\n'
# Compile the multiline string containing the tasklines
taskstring = ""
for task in self.tasks: # Step through list of task dictionary objects
taskline, tdate = self.compile_taskline(task)
if tdate.date() < date.today():
# Add newline if not end of list and colour red
taskstring = taskstring + ('\n' if len(taskstring) != 0 else ''
) + term.red(taskline)
elif tdate.date() == date.today():
taskstring = taskstring + '\n' + term.yellow(taskline)
elif tdate.date() > date.today():
taskstring = taskstring + '\n' + taskline
# Removes lines that will not fit onto screen
terminal_lines = ''.join(taskstring.splitlines(True)[0:taskspace])
print dateline + '\n' + dayheader + terminal_lines
def run_selector(self):
"""
Compile and run the task selector.
See: http://www.wanware.com/tsgdocs/snack.html for info on the
python snack module.
Also look at the code in snack.py to understand what it is doing.
"""
wdays_short = "M T W T F S S "
extrahkeysdict = {'0': ' mod due:today',
'1': ' mod due:1d',
'2': ' mod due:2d',
'3': ' mod due:3d',
'4': ' mod due:4d',
'5': ' mod due:5d',
'6': ' mod due:6d',
'7': ' mod due:7d',
'8': ' mod due:1w',
'9': ' mod due:2w',
'd': ' done',
'i': ' info',
'h': ' HELP'}
for key in extrahkeysdict.keys():
snack.hotkeys[key] = ord(key) # Add to the snack hkey library
snack.hotkeys[ord(key)] = key
screen = snack.SnackScreen()
buttonlist = [("1d", "1d", "F1"),
("2d", "2d", "F2"),
("3d", "3d", "F3"),
("1w", "1w", "F4"),
("2w", "2w", "F5")
]
mybuttonbar = snack.ButtonBar(screen, buttonlist, 1)
lbox = snack.Listbox(height=20, width=90, returnExit=1, multiple=1,
border=1, scroll=1)
for task in self.tasks: # Step through list of task dictionary objects
taskline, tdate = self.compile_taskline(task)
lbox.append(taskline, task["id"])
grid = snack.GridForm(screen, wdays_short * self.weeks, 3, 3)
# Note the padding option. It is set to shift the lbox content one
# position to the right so as to allign with the dayheader at the top
grid.add(lbox, 0, 0, padding=(1, 0, 0, 0))
grid.add(mybuttonbar, 0, 1, growx=1)
grid.addHotKey('ESC')
for key in extrahkeysdict.keys():
grid.addHotKey(key)
result = grid.runOnce() # This is the key of the HotKey dict
buttonpressed = mybuttonbar.buttonPressed(result) # The Hotkey dict \
# value of key in 'result'. Returns None if no hotkey or button \
# pressed
selection = lbox.getSelection() # This is a list
# The first set of if options deals with all the exceptions while \
# the last 'else' deals with actual task manupulations.
if result == 'ESC':
screen.finish()
return (None, True)
elif result == 'h':
mytext = ''
for mykey in sorted(extrahkeysdict):
mytext = mytext + '\n' + mykey + ': ' + extrahkeysdict[mykey]
snack.ButtonChoiceWindow(screen, 'Help', mytext, ['Ok'])
screen.finish()
return (None, False)
elif result == 'i':
basictaskstr = 'task ' + str(lbox.current()) + ' info'
mytext = commands.getoutput(basictaskstr)
snack.ButtonChoiceWindow(screen, 'Task information', mytext, \
['Ok'],width=80)
screen.finish()
return (None, False)
else:
# Get the selected or current task/s from the listbox
if len(selection) > 0:
# Joins the selections into comma seperated list, and suppresses
# confirmation for this number of tasks
# basictaskstr = 'task ' + ','.join(map(repr, lbox.getSelection())) \
basictaskstr = 'task ' + ','.join(map(repr, selection)) \
+ ' rc.bulk:' + str(len(selection) + 1)
else: # If no selections have been made use the current task
basictaskstr = 'task ' + str(lbox.current())
# Figure out what commands to add, if any given
if buttonpressed:
# TODO Change values in Buttonlist dict so that it provides the full command\
# so that it can be used in the same way as if any other hotkey was pressed
basictaskstr = basictaskstr + ' mod due:' + buttonpressed
elif result in extrahkeysdict: # If a hotkey not linked to button.
basictaskstr = basictaskstr + extrahkeysdict[result]
else: # Else type in the rest of the command.
basictaskstr = basictaskstr + ' mod '
result2 = snack.EntryWindow(screen, "Command details...", \
"", [('Command to be executed:', basictaskstr)], width=50, \
entryWidth=30, buttons=[('OK', 'OK', 'F1'), ('Cancel', 'Cancel',
'ESC')]) # Look at the code for this function to understand this
screen.finish()
if result2[0] == 'Cancel':
return(None, True)
else:
print '>' + result2[1][0]
return (commands.getoutput(result2[1][0]), False)
def compile_taskline(self, task):
"""
Compile a task line to be used in the task selector or in the
task report.
"""
indent = 3 # The indent per week day
if 'due' not in task:
# Indents the task by 6 days at top of list to show that it does not \
# have a due date. datetime.min.time() is a constant for midnight
tdate = datetime.combine(self.weekstart + timedelta(days=6), \
datetime.min.time()) # Converts the date back to a datetime.
else:
# This assumes GMT+2 - change this for your timezone
tdate = datetime.strptime(task["due"], "%Y%m%dT%H%M%SZ") + \
timedelta(hours=2) # Assume tasks are ordered by due date
daydiff = tdate.date() - self.weekstart
daydiff = daydiff.days
taskline = indent * daydiff * " " + repr(task["id"]) + " (" + \
(task["project"] if "project" in task else "none") \
+ ")" + ("*" if "annotations" in task else " ") \
+ (task["description"].upper()
if ("priority" in task and task["priority"] == "H")
else task["description"])
return taskline, tdate
def main():
report = TaskReport()
if __name__ == '__main__':
main()