Merge lp:~laurynas-speicys/gtimelog/categorized-reports into lp:~gtimelog-dev/gtimelog/trunk
- categorized-reports
- Merge into trunk
Proposed by
Laurynas Speicys
Status: | Merged |
---|---|
Approved by: | Marius Gedminas |
Approved revision: | 169 |
Merge reported by: | Marius Gedminas |
Merged at revision: | not available |
Proposed branch: | lp:~laurynas-speicys/gtimelog/categorized-reports |
Merge into: | lp:~gtimelog-dev/gtimelog/trunk |
Diff against target: |
890 lines (+477/-175) 2 files modified
src/gtimelog/main.py (+289/-125) src/gtimelog/test_gtimelog.py (+188/-50) |
To merge this branch: | bzr merge lp:~laurynas-speicys/gtimelog/categorized-reports |
Related bugs: | |
Related blueprints: |
New Style of Categorized Reports
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Laurynas Speicys (community) | Needs Resubmitting | ||
Marius Gedminas | Needs Fixing | ||
Review via email: mp+45865@code.launchpad.net |
Commit message
Description of the change
Adds support for categorized weekly and monthly reports.
Enable the feature by adding 'report_style = categorized' to the gtimelogrc file.
The feature is disabled by default (i.e., if 'report_style' is missing or its value is other than 'categorized').
To post a comment you must log in.
- 169. By Laurynas Speicys
-
Merged trunk.
Used bzr merge lp:~/mgedmin/gtimelog/trunk
Revision history for this message
Laurynas Speicys (laurynas-speicys) wrote : | # |
Conflicts resolved.
review:
Needs Resubmitting
Revision history for this message
Laurynas Speicys (laurynas-speicys) wrote : | # |
Thanks for merging it into trunk!
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/gtimelog/main.py' |
2 | --- src/gtimelog/main.py 2011-01-13 22:15:15 +0000 |
3 | +++ src/gtimelog/main.py 2011-01-19 21:53:37 +0000 |
4 | @@ -300,6 +300,37 @@ |
5 | slack.sort() |
6 | return work, slack |
7 | |
8 | + def categorized_work_entries(self, skip_first=True): |
9 | + """Return consolidated work entries grouped by category. |
10 | + |
11 | + Category is a string preceding the first ':' in the entry. |
12 | + |
13 | + Return two dicts: |
14 | + - {<category>: <entry list>}, where <category> is a category string |
15 | + and <entry list> is a sorted list that contains tuples (start, |
16 | + entry, duration); entry is stripped of its category prefix. |
17 | + - {<category>: <total duration>}, where <total duration> is the |
18 | + total duration of work in the <category>. |
19 | + """ |
20 | + |
21 | + work, slack = self.grouped_entries(skip_first=skip_first) |
22 | + entries = {} |
23 | + totals = {} |
24 | + for start, entry, duration in work: |
25 | + if ': ' in entry: |
26 | + cat, clipped_entry = entry.split(': ', 1) |
27 | + entry_list = entries.get(cat, []) |
28 | + entry_list.append((start, clipped_entry, duration)) |
29 | + entries[cat] = entry_list |
30 | + totals[cat] = totals.get(cat, datetime.timedelta(0)) + duration |
31 | + else: |
32 | + entry_list = entries.get(None, []) |
33 | + entry_list.append((start, entry, duration)) |
34 | + entries[None] = entry_list |
35 | + totals[None] = totals.get( |
36 | + None, datetime.timedelta(0)) + duration |
37 | + return entries, totals |
38 | + |
39 | def totals(self): |
40 | """Calculate total time of work and slacking entries. |
41 | |
42 | @@ -408,7 +439,110 @@ |
43 | items.sort() |
44 | writer.writerows(items) |
45 | |
46 | - def report_categories(self, output, categories): |
47 | + |
48 | +class Reports(object): |
49 | + """Generation of reports.""" |
50 | + |
51 | + def __init__(self, window): |
52 | + self.window = window |
53 | + |
54 | + def _categorizing_report(self, output, email, who, subject, period_name, |
55 | + estimated_column=False): |
56 | + """A report that displays entries by category. |
57 | + |
58 | + Writes a report template in RFC-822 format to output. |
59 | + |
60 | + The report looks like |
61 | + | time |
62 | + | Overhead: |
63 | + | Status meeting 43 |
64 | + | Mail 1:50 |
65 | + | -------------------------------- |
66 | + | 2:33 |
67 | + | |
68 | + | Compass: |
69 | + | Compass: hotpatch 2:13 |
70 | + | Call with a client 30 |
71 | + | -------------------------------- |
72 | + | 3:43 |
73 | + | |
74 | + | No category: |
75 | + | SAT roundup 1:00 |
76 | + | -------------------------------- |
77 | + | 1:00 |
78 | + | |
79 | + | Total work done this week: 6:26 |
80 | + | |
81 | + | Categories by time spent: |
82 | + | |
83 | + | Compass 3:43 |
84 | + | Overhead 2:33 |
85 | + | No category 1:00 |
86 | + |
87 | + """ |
88 | + window = self.window |
89 | + |
90 | + print >> output, "To: %(email)s" % {'email': email} |
91 | + print >> output, "Subject: %s" % subject |
92 | + print >> output |
93 | + items = list(window.all_entries()) |
94 | + if not items: |
95 | + print >> output, "No work done this %s." % period_name |
96 | + return |
97 | + print >> output, " " * 46, |
98 | + if estimated_column: |
99 | + print >> output, "estimated actual" |
100 | + else: |
101 | + print >> output, " time" |
102 | + |
103 | + total_work, total_slacking = window.totals() |
104 | + entries, totals = window.categorized_work_entries() |
105 | + if entries: |
106 | + categories = entries.keys() |
107 | + categories.sort() |
108 | + if categories[0] == None: |
109 | + categories = categories[1:] |
110 | + categories.append('No category') |
111 | + e = entries.pop(None) |
112 | + entries['No category'] = e |
113 | + t = totals.pop(None) |
114 | + totals['No category'] = t |
115 | + for cat in categories: |
116 | + print >> output, '%s:' % cat |
117 | + |
118 | + work = [(entry, duration) |
119 | + for start, entry, duration in entries[cat]] |
120 | + work.sort() |
121 | + for entry, duration in work: |
122 | + if not duration: |
123 | + continue # skip empty "arrival" entries |
124 | + |
125 | + entry = entry[:1].upper() + entry[1:] |
126 | + if estimated_column: |
127 | + print >> output, (u" %-46s %-14s %s" % |
128 | + (entry, '-', format_duration_short(duration))) |
129 | + else: |
130 | + print >> output, (u" %-61s %+5s" % |
131 | + (entry, format_duration_short(duration))) |
132 | + |
133 | + print >> output, '-' * 70 |
134 | + print >> output, (u"%+70s" % |
135 | + format_duration_short(totals[cat])) |
136 | + print >> output |
137 | + print >> output, ("Total work done this %s: %s" % |
138 | + (period_name, format_duration_short(total_work))) |
139 | + |
140 | + print >> output |
141 | + |
142 | + ordered_by_time = [(time, cat) for cat, time in totals.items()] |
143 | + ordered_by_time.sort(reverse=True) |
144 | + max_cat_length = max([len(cat) for cat in totals.keys()]) |
145 | + line_format = ' %-' + str(max_cat_length + 4) + 's %+5s' |
146 | + print >> output, 'Categories by time spent:' |
147 | + for time, cat in ordered_by_time: |
148 | + print >> output, line_format % (cat, format_duration_short(time)) |
149 | + |
150 | + def _report_categories(self, output, categories): |
151 | """A helper method that lists time spent per category. |
152 | |
153 | Use this to add a section in a report looks similar to this: |
154 | @@ -437,23 +571,111 @@ |
155 | '(none)', format_duration_long(categories[None])) |
156 | print >> output |
157 | |
158 | + def _plain_report(self, output, email, who, subject, period_name, |
159 | + estimated_column=False): |
160 | + """Format a report that does not categorize entries. |
161 | + |
162 | + Writes a report template in RFC-822 format to output. |
163 | + """ |
164 | + window = self.window |
165 | + |
166 | + print >> output, "To: %(email)s" % {'email': email} |
167 | + print >> output, 'Subject: %s' % subject |
168 | + print >> output |
169 | + items = list(window.all_entries()) |
170 | + if not items: |
171 | + print >> output, "No work done this %s." % period_name |
172 | + return |
173 | + print >> output, " " * 46, |
174 | + if estimated_column: |
175 | + print >> output, "estimated actual" |
176 | + else: |
177 | + print >> output, " time" |
178 | + work, slack = window.grouped_entries() |
179 | + total_work, total_slacking = window.totals() |
180 | + categories = {} |
181 | + if work: |
182 | + work = [(entry, duration) for start, entry, duration in work] |
183 | + work.sort() |
184 | + for entry, duration in work: |
185 | + if not duration: |
186 | + continue # skip empty "arrival" entries |
187 | + |
188 | + if ': ' in entry: |
189 | + cat, task = entry.split(': ', 1) |
190 | + categories[cat] = categories.get( |
191 | + cat, datetime.timedelta(0)) + duration |
192 | + else: |
193 | + categories[None] = categories.get( |
194 | + None, datetime.timedelta(0)) + duration |
195 | + |
196 | + entry = entry[:1].upper() + entry[1:] |
197 | + if estimated_column: |
198 | + print >> output, (u"%-46s %-14s %s" % |
199 | + (entry, '-', format_duration_long(duration))) |
200 | + else: |
201 | + print >> output, (u"%-62s %s" % |
202 | + (entry, format_duration_long(duration))) |
203 | + print >> output |
204 | + print >> output, ("Total work done this %s: %s" % |
205 | + (period_name, format_duration_long(total_work))) |
206 | + |
207 | + if categories: |
208 | + self._report_categories(output, categories) |
209 | + |
210 | + def weekly_report_categorized(self, output, email, who, |
211 | + estimated_column=False): |
212 | + """Format a weekly report with entries displayed under categories.""" |
213 | + week = self.window.min_timestamp.strftime('%V') |
214 | + subject = 'Weekly report for %s (week %s)' % (who, week) |
215 | + return self._categorizing_report(output, email, who, subject, |
216 | + period_name='week', |
217 | + estimated_column=estimated_column) |
218 | + |
219 | + def monthly_report_categorized(self, output, email, who, |
220 | + estimated_column=False): |
221 | + """Format a monthly report with entries displayed under categories.""" |
222 | + month = self.window.min_timestamp.strftime('%Y/%m') |
223 | + subject = 'Monthly report for %s (%s)' % (who, month) |
224 | + return self._categorizing_report(output, email, who, subject, |
225 | + period_name='month', |
226 | + estimated_column=estimated_column) |
227 | + |
228 | + def weekly_report_plain(self, output, email, who, estimated_column=False): |
229 | + """Format a weekly report .""" |
230 | + week = self.window.min_timestamp.strftime('%V') |
231 | + subject = 'Weekly report for %s (week %s)' % (who, week) |
232 | + return self._plain_report(output, email, who, subject, |
233 | + period_name='week', |
234 | + estimated_column=estimated_column) |
235 | + |
236 | + def monthly_report_plain(self, output, email, who, estimated_column=False): |
237 | + """Format a monthly report .""" |
238 | + month = self.window.min_timestamp.strftime('%Y/%m') |
239 | + subject = 'Monthly report for %s (%s)' % (who, month) |
240 | + return self._plain_report(output, email, who, subject, |
241 | + period_name='month', |
242 | + estimated_column=estimated_column) |
243 | + |
244 | def daily_report(self, output, email, who): |
245 | """Format a daily report. |
246 | |
247 | Writes a daily report template in RFC-822 format to output. |
248 | """ |
249 | + window = self.window |
250 | + |
251 | # Locale is set as a side effect of 'import gtk', so strftime('%a') |
252 | # would give us translated names |
253 | weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] |
254 | - weekday = weekday_names[self.min_timestamp.weekday()] |
255 | - week = self.min_timestamp.strftime('%V') |
256 | + weekday = weekday_names[window.min_timestamp.weekday()] |
257 | + week = window.min_timestamp.strftime('%V') |
258 | print >> output, "To: %(email)s" % {'email': email} |
259 | print >> output, ("Subject: %(date)s report for %(who)s" |
260 | " (%(weekday)s, week %(week)s)" |
261 | - % {'date': self.min_timestamp.strftime('%Y-%m-%d'), |
262 | + % {'date': window.min_timestamp.strftime('%Y-%m-%d'), |
263 | 'weekday': weekday, 'week': week, 'who': who}) |
264 | print >> output |
265 | - items = list(self.all_entries()) |
266 | + items = list(window.all_entries()) |
267 | if not items: |
268 | print >> output, "No work done today." |
269 | return |
270 | @@ -461,8 +683,8 @@ |
271 | entry = entry[:1].upper() + entry[1:] |
272 | print >> output, "%s at %s" % (entry, start.strftime('%H:%M')) |
273 | print >> output |
274 | - work, slack = self.grouped_entries() |
275 | - total_work, total_slacking = self.totals() |
276 | + work, slack = window.grouped_entries() |
277 | + total_work, total_slacking = window.totals() |
278 | if work: |
279 | categories = {} |
280 | for start, entry, duration in work: |
281 | @@ -482,7 +704,7 @@ |
282 | format_duration_long(total_work)) |
283 | |
284 | if categories: |
285 | - self.report_categories(output, categories) |
286 | + self._report_categories(output, categories) |
287 | |
288 | print >> output, 'Slacking:\n' |
289 | |
290 | @@ -495,105 +717,6 @@ |
291 | print >> output, ("Time spent slacking: %s" % |
292 | format_duration_long(total_slacking)) |
293 | |
294 | - def weekly_report(self, output, email, who, estimated_column=False): |
295 | - """Format a weekly report. |
296 | - |
297 | - Writes a weekly report template in RFC-822 format to output. |
298 | - """ |
299 | - week = self.min_timestamp.strftime('%V') |
300 | - print >> output, "To: %(email)s" % {'email': email} |
301 | - print >> output, "Subject: Weekly report for %s (week %s)" % (who, |
302 | - week) |
303 | - print >> output |
304 | - items = list(self.all_entries()) |
305 | - if not items: |
306 | - print >> output, "No work done this week." |
307 | - return |
308 | - print >> output, " " * 46, |
309 | - if estimated_column: |
310 | - print >> output, "estimated actual" |
311 | - else: |
312 | - print >> output, " time" |
313 | - work, slack = self.grouped_entries() |
314 | - total_work, total_slacking = self.totals() |
315 | - categories = {} |
316 | - if work: |
317 | - work = [(entry, duration) for start, entry, duration in work] |
318 | - work.sort() |
319 | - for entry, duration in work: |
320 | - if not duration: |
321 | - continue # skip empty "arrival" entries |
322 | - |
323 | - if ': ' in entry: |
324 | - cat, task = entry.split(': ', 1) |
325 | - categories[cat] = categories.get( |
326 | - cat, datetime.timedelta(0)) + duration |
327 | - else: |
328 | - categories[None] = categories.get( |
329 | - None, datetime.timedelta(0)) + duration |
330 | - |
331 | - entry = entry[:1].upper() + entry[1:] |
332 | - if estimated_column: |
333 | - print >> output, (u"%-46s %-14s %s" % |
334 | - (entry, '-', format_duration_long(duration))) |
335 | - else: |
336 | - print >> output, (u"%-62s %s" % |
337 | - (entry, format_duration_long(duration))) |
338 | - print >> output |
339 | - print >> output, ("Total work done this week: %s" % |
340 | - format_duration_long(total_work)) |
341 | - |
342 | - if categories: |
343 | - self.report_categories(output, categories) |
344 | - |
345 | - def monthly_report(self, output, email, who): |
346 | - """Format a monthly report. |
347 | - |
348 | - Writes a monthly report template in RFC-822 format to output. |
349 | - """ |
350 | - |
351 | - month = self.min_timestamp.strftime('%Y/%m') |
352 | - print >> output, "To: %(email)s" % {'email': email} |
353 | - print >> output, "Subject: Monthly report for %s (%s)" % (who, month) |
354 | - print >> output |
355 | - |
356 | - items = list(self.all_entries()) |
357 | - if not items: |
358 | - print >> output, "No work done this month." |
359 | - return |
360 | - |
361 | - print >> output, " " * 46 |
362 | - |
363 | - work, slack = self.grouped_entries() |
364 | - total_work, total_slacking = self.totals() |
365 | - categories = {} |
366 | - |
367 | - if work: |
368 | - work = [(entry, duration) for start, entry, duration in work] |
369 | - work.sort() |
370 | - for entry, duration in work: |
371 | - if not duration: |
372 | - continue # skip empty "arrival" entries |
373 | - |
374 | - if ': ' in entry: |
375 | - cat, task = entry.split(': ', 1) |
376 | - categories[cat] = categories.get( |
377 | - cat, datetime.timedelta(0)) + duration |
378 | - else: |
379 | - categories[None] = categories.get( |
380 | - None, datetime.timedelta(0)) + duration |
381 | - |
382 | - entry = entry[:1].upper() + entry[1:] |
383 | - print >> output, (u"%-62s %s" % |
384 | - (entry, format_duration_long(duration))) |
385 | - print >> output |
386 | - |
387 | - print >> output, ("Total work done this month: %s" % |
388 | - format_duration_long(total_work)) |
389 | - |
390 | - if categories: |
391 | - self.report_categories(output, categories) |
392 | - |
393 | |
394 | class TimeLog(object): |
395 | """Time log. |
396 | @@ -800,6 +923,8 @@ |
397 | prefer_old_tray_icon = False |
398 | start_in_tray = False |
399 | |
400 | + report_style = 'plain' |
401 | + |
402 | def _config(self): |
403 | config = ConfigParser.RawConfigParser() |
404 | config.add_section('gtimelog') |
405 | @@ -821,6 +946,7 @@ |
406 | config.set('gtimelog', 'show_tray_icon', str(self.show_tray_icon)) |
407 | config.set('gtimelog', 'prefer_app_indicator', str(self.prefer_app_indicator)) |
408 | config.set('gtimelog', 'prefer_old_tray_icon', str(self.prefer_old_tray_icon)) |
409 | + config.set('gtimelog', 'report_style', str(self.report_style)) |
410 | config.set('gtimelog', 'start_in_tray', str(self.start_in_tray)) |
411 | return config |
412 | |
413 | @@ -847,6 +973,7 @@ |
414 | 'prefer_app_indicator') |
415 | self.prefer_old_tray_icon = config.getboolean('gtimelog', |
416 | 'prefer_old_tray_icon') |
417 | + self.report_style = config.get('gtimelog', 'report_style') |
418 | self.start_in_tray = config.getboolean('gtimelog', 'start_in_tray') |
419 | |
420 | def save(self, filename): |
421 | @@ -1418,15 +1545,15 @@ |
422 | |
423 | def on_daily_report_activate(self, widget): |
424 | """File -> Daily Report""" |
425 | - window = self.timelog.window |
426 | - self.mail(window.daily_report) |
427 | + reports = Reports(self.timelog.window) |
428 | + self.mail(reports.daily_report) |
429 | |
430 | def on_yesterdays_report_activate(self, widget): |
431 | """File -> Daily Report for Yesterday""" |
432 | max = self.timelog.window.min_timestamp |
433 | min = max - datetime.timedelta(1) |
434 | - window = self.timelog.window_for(min, max) |
435 | - self.mail(window.daily_report) |
436 | + reports = Reports(self.timelog.window_for(min, max)) |
437 | + self.mail(reports.daily_report) |
438 | |
439 | def on_previous_day_report_activate(self, widget): |
440 | """File -> Daily Report for a Previous Day""" |
441 | @@ -1435,8 +1562,8 @@ |
442 | min = datetime.datetime.combine(day, |
443 | self.timelog.virtual_midnight) |
444 | max = min + datetime.timedelta(1) |
445 | - window = self.timelog.window_for(min, max) |
446 | - self.mail(window.daily_report) |
447 | + reports = Reports(self.timelog.window_for(min, max)) |
448 | + self.mail(reports.daily_report) |
449 | |
450 | def choose_date(self): |
451 | """Pop up a calendar dialog. |
452 | @@ -1467,21 +1594,40 @@ |
453 | |
454 | def on_weekly_report_activate(self, widget): |
455 | """File -> Weekly Report""" |
456 | - window = self.weekly_window() |
457 | - self.mail(window.weekly_report) |
458 | + day = self.timelog.day |
459 | + reports = Reports(self.weekly_window(day=day)) |
460 | + if self.settings.report_style == 'plain': |
461 | + report = reports.weekly_report_plain |
462 | + elif self.settings.report_style == 'categorized': |
463 | + report = reports.weekly_report_categorized |
464 | + else: |
465 | + report = reports.weekly_report_plain |
466 | + self.mail(report) |
467 | |
468 | def on_last_weeks_report_activate(self, widget): |
469 | """File -> Weekly Report for Last Week""" |
470 | day = self.timelog.day - datetime.timedelta(7) |
471 | - window = self.weekly_window(day=day) |
472 | - self.mail(window.weekly_report) |
473 | + reports = Reports(self.weekly_window(day=day)) |
474 | + if self.settings.report_style == 'plain': |
475 | + report = reports.weekly_report_plain |
476 | + elif self.settings.report_style == 'categorized': |
477 | + report = reports.weekly_report_categorized |
478 | + else: |
479 | + report = reports.weekly_report_plain |
480 | + self.mail(report) |
481 | |
482 | def on_previous_week_report_activate(self, widget): |
483 | """File -> Weekly Report for a Previous Week""" |
484 | day = self.choose_date() |
485 | if day: |
486 | - window = self.weekly_window(day=day) |
487 | - self.mail(window.weekly_report) |
488 | + reports = Reports(self.weekly_window(day=day)) |
489 | + if self.settings.report_style == 'plain': |
490 | + report = reports.weekly_report_plain |
491 | + elif self.settings.report_style == 'categorized': |
492 | + report = reports.weekly_report_categorized |
493 | + else: |
494 | + report = reports.weekly_report_plain |
495 | + self.mail(report) |
496 | |
497 | def monthly_window(self, day=None): |
498 | if not day: |
499 | @@ -1499,19 +1645,37 @@ |
500 | """File -> Monthly Report for a Previous Month""" |
501 | day = self.choose_date() |
502 | if day: |
503 | - window = self.monthly_window(day=day) |
504 | - self.mail(window.monthly_report) |
505 | + reports = Reports(self.monthly_window(day=day)) |
506 | + if self.settings.report_style == 'plain': |
507 | + report = reports.monthly_report_plain |
508 | + elif self.settings.report_style == 'categorized': |
509 | + report = reports.monthly_report_categorized |
510 | + else: |
511 | + report = reports.monthly_report_plain |
512 | + self.mail(report) |
513 | |
514 | def on_last_month_report_activate(self, widget): |
515 | """File -> Monthly Report for Last Month""" |
516 | day = self.timelog.day - datetime.timedelta(self.timelog.day.day) |
517 | - window = self.monthly_window(day=day) |
518 | - self.mail(window.monthly_report) |
519 | + reports = Reports(self.monthly_window(day=day)) |
520 | + if self.settings.report_style == 'plain': |
521 | + report = reports.monthly_report_plain |
522 | + elif self.settings.report_style == 'categorized': |
523 | + report = reports.monthly_report_categorized |
524 | + else: |
525 | + report = reports.monthly_report_plain |
526 | + self.mail(report) |
527 | |
528 | def on_monthly_report_activate(self, widget): |
529 | """File -> Monthly Report""" |
530 | - window = self.monthly_window() |
531 | - self.mail(window.monthly_report) |
532 | + reports = Reports(self.monthly_window()) |
533 | + if self.settings.report_style == 'plain': |
534 | + report = reports.monthly_report_plain |
535 | + elif self.settings.report_style == 'categorized': |
536 | + report = reports.monthly_report_categorized |
537 | + else: |
538 | + report = reports.monthly_report_plain |
539 | + self.mail(report) |
540 | |
541 | def on_open_complete_spreadsheet_activate(self, widget): |
542 | """Report -> Complete Report in Spreadsheet""" |
543 | |
544 | === modified file 'src/gtimelog/test_gtimelog.py' |
545 | --- src/gtimelog/test_gtimelog.py 2011-01-10 19:43:44 +0000 |
546 | +++ src/gtimelog/test_gtimelog.py 2011-01-19 21:53:37 +0000 |
547 | @@ -231,14 +231,175 @@ |
548 | |
549 | """ |
550 | |
551 | -def doctest_TimeWindow_report_categories(): |
552 | - r"""Tests for TimeWindow.report_categories |
553 | +def doctest_TimeWindow_to_csv_daily(): |
554 | + r"""Tests for TimeWindow.to_csv_daily |
555 | + |
556 | + >>> from datetime import datetime, time |
557 | + >>> min = datetime(2008, 6, 1) |
558 | + >>> max = datetime(2008, 7, 1) |
559 | + >>> vm = time(2, 0) |
560 | + |
561 | + >>> from StringIO import StringIO |
562 | + >>> sampledata = StringIO(''' |
563 | + ... 2008-06-03 12:45: start |
564 | + ... 2008-06-03 13:00: something |
565 | + ... 2008-06-03 14:45: something else |
566 | + ... 2008-06-03 15:45: etc |
567 | + ... 2008-06-05 12:45: start |
568 | + ... 2008-06-05 13:15: something |
569 | + ... ''') |
570 | + |
571 | + >>> from gtimelog.main import TimeWindow |
572 | + >>> window = TimeWindow(sampledata, min, max, vm) |
573 | + |
574 | + >>> import sys |
575 | + >>> window.to_csv_daily(sys.stdout) |
576 | + date,day-start (hours),slacking (hours),work (hours) |
577 | + 2008-06-03,12.75,0.0,3.0 |
578 | + 2008-06-04,0.0,0.0,0.0 |
579 | + 2008-06-05,12.75,0.0,0.5 |
580 | + |
581 | + """ |
582 | + |
583 | +def doctest_Reports_weekly_report_categorized(): |
584 | + r"""Tests for Reports.weekly_report_categorized |
585 | + |
586 | + >>> import sys |
587 | + |
588 | + >>> from datetime import datetime, time |
589 | + >>> from tempfile import NamedTemporaryFile |
590 | + >>> from gtimelog.main import TimeWindow, Reports |
591 | + |
592 | + >>> vm = time(2, 0) |
593 | + >>> min = datetime(2010, 1, 25) |
594 | + >>> max = datetime(2010, 1, 31) |
595 | + >>> fh = NamedTemporaryFile() |
596 | + |
597 | + >>> window = TimeWindow(fh.name, min, max, vm) |
598 | + >>> reports = Reports(window) |
599 | + >>> reports.weekly_report_categorized(sys.stdout, 'foo@bar.com', |
600 | + ... 'Bob Jones') |
601 | + To: foo@bar.com |
602 | + Subject: Weekly report for Bob Jones (week 04) |
603 | + <BLANKLINE> |
604 | + No work done this week. |
605 | + |
606 | + >>> _ = [fh.write(s + '\n') for s in [ |
607 | + ... '2010-01-30 09:00: start', |
608 | + ... '2010-01-30 09:23: Bing: stuff', |
609 | + ... '2010-01-30 12:54: Bong: other stuff', |
610 | + ... '2010-01-30 13:32: lunch **', |
611 | + ... '2010-01-30 23:46: misc', |
612 | + ... '']] |
613 | + >>> fh.flush() |
614 | + |
615 | + >>> window = TimeWindow(fh.name, min, max, vm) |
616 | + >>> reports = Reports(window) |
617 | + >>> reports.weekly_report_categorized(sys.stdout, 'foo@bar.com', |
618 | + ... 'Bob Jones') |
619 | + To: foo@bar.com |
620 | + Subject: Weekly report for Bob Jones (week 04) |
621 | + <BLANKLINE> |
622 | + time |
623 | + Bing: |
624 | + <BLANKLINE> |
625 | + Stuff 0:23 |
626 | + ---------------------------------------------------------------------- |
627 | + 0:23 |
628 | + <BLANKLINE> |
629 | + Bong: |
630 | + <BLANKLINE> |
631 | + Other stuff 3:31 |
632 | + ---------------------------------------------------------------------- |
633 | + 3:31 |
634 | + <BLANKLINE> |
635 | + No category: |
636 | + <BLANKLINE> |
637 | + Misc 10:14 |
638 | + ---------------------------------------------------------------------- |
639 | + 10:14 |
640 | + <BLANKLINE> |
641 | + Total work done this week: 14:08 |
642 | + <BLANKLINE> |
643 | + Categories by time spent: |
644 | + No category 10:14 |
645 | + Bong 3:31 |
646 | + Bing 0:23 |
647 | + |
648 | + """ |
649 | + |
650 | +def doctest_Reports_monthly_report_categorized(): |
651 | + r"""Tests for Reports.monthly_report_categorized |
652 | + |
653 | + >>> import sys |
654 | + |
655 | + >>> from datetime import datetime, time |
656 | + >>> from tempfile import NamedTemporaryFile |
657 | + >>> from gtimelog.main import TimeWindow, Reports |
658 | + |
659 | + >>> vm = time(2, 0) |
660 | + >>> min = datetime(2010, 1, 25) |
661 | + >>> max = datetime(2010, 1, 31) |
662 | + >>> fh = NamedTemporaryFile() |
663 | + |
664 | + >>> window = TimeWindow(fh.name, min, max, vm) |
665 | + >>> reports = Reports(window) |
666 | + >>> reports.monthly_report_categorized(sys.stdout, 'foo@bar.com', |
667 | + ... 'Bob Jones') |
668 | + To: foo@bar.com |
669 | + Subject: Monthly report for Bob Jones (2010/01) |
670 | + <BLANKLINE> |
671 | + No work done this month. |
672 | + |
673 | + >>> _ = [fh.write(s + '\n') for s in [ |
674 | + ... '2010-01-30 09:00: start', |
675 | + ... '2010-01-30 09:23: Bing: stuff', |
676 | + ... '2010-01-30 12:54: Bong: other stuff', |
677 | + ... '2010-01-30 13:32: lunch **', |
678 | + ... '2010-01-30 23:46: misc', |
679 | + ... '']] |
680 | + >>> fh.flush() |
681 | + |
682 | + >>> window = TimeWindow(fh.name, min, max, vm) |
683 | + >>> reports = Reports(window) |
684 | + >>> reports.monthly_report_categorized(sys.stdout, 'foo@bar.com', |
685 | + ... 'Bob Jones') |
686 | + To: foo@bar.com |
687 | + Subject: Monthly report for Bob Jones (2010/01) |
688 | + <BLANKLINE> |
689 | + time |
690 | + Bing: |
691 | + Stuff 0:23 |
692 | + ---------------------------------------------------------------------- |
693 | + 0:23 |
694 | + <BLANKLINE> |
695 | + Bong: |
696 | + Other stuff 3:31 |
697 | + ---------------------------------------------------------------------- |
698 | + 3:31 |
699 | + <BLANKLINE> |
700 | + No category: |
701 | + Misc 10:14 |
702 | + ---------------------------------------------------------------------- |
703 | + 10:14 |
704 | + <BLANKLINE> |
705 | + Total work done this month: 14:08 |
706 | + <BLANKLINE> |
707 | + Categories by time spent: |
708 | + No category 10:14 |
709 | + Bong 3:31 |
710 | + Bing 0:23 |
711 | + |
712 | + """ |
713 | + |
714 | +def doctest_Reports_report_categories(): |
715 | + r"""Tests for Reports._report_categories |
716 | |
717 | >>> import sys |
718 | |
719 | >>> from datetime import datetime, time, timedelta |
720 | >>> from tempfile import NamedTemporaryFile |
721 | - >>> from gtimelog.main import TimeWindow |
722 | + >>> from gtimelog.main import TimeWindow, Reports |
723 | |
724 | >>> vm = time(2, 0) |
725 | >>> min = datetime(2010, 1, 25) |
726 | @@ -250,7 +411,8 @@ |
727 | ... None: timedelta(1)} |
728 | |
729 | >>> window = TimeWindow(fh.name, min, max, vm) |
730 | - >>> window.report_categories(sys.stdout, categories) |
731 | + >>> reports = Reports(window) |
732 | + >>> reports._report_categories(sys.stdout, categories) |
733 | <BLANKLINE> |
734 | By category: |
735 | <BLANKLINE> |
736 | @@ -260,13 +422,13 @@ |
737 | |
738 | """ |
739 | |
740 | -def doctest_TimeWindow_daily_report(): |
741 | - r"""Tests for TimeWindow.daily_report |
742 | +def doctest_Reports_daily_report(): |
743 | + r"""Tests for Reports.daily_report |
744 | >>> import sys |
745 | |
746 | >>> from datetime import datetime, time |
747 | >>> from tempfile import NamedTemporaryFile |
748 | - >>> from gtimelog.main import TimeWindow |
749 | + >>> from gtimelog.main import TimeWindow, Reports |
750 | |
751 | >>> vm = time(2, 0) |
752 | >>> min = datetime(2010, 1, 30) |
753 | @@ -274,7 +436,8 @@ |
754 | >>> fh = NamedTemporaryFile() |
755 | |
756 | >>> window = TimeWindow(fh.name, min, max, vm) |
757 | - >>> window.daily_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
758 | + >>> reports = Reports(window) |
759 | + >>> reports.daily_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
760 | To: foo@bar.com |
761 | Subject: 2010-01-30 report for Bob Jones (Sat, week 04) |
762 | <BLANKLINE> |
763 | @@ -290,7 +453,8 @@ |
764 | >>> fh.flush() |
765 | |
766 | >>> window = TimeWindow(fh.name, min, max, vm) |
767 | - >>> window.daily_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
768 | + >>> reports = Reports(window) |
769 | + >>> reports.daily_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
770 | To: foo@bar.com |
771 | Subject: 2010-01-30 report for Bob Jones (Sat, week 04) |
772 | <BLANKLINE> |
773 | @@ -316,14 +480,14 @@ |
774 | |
775 | """ |
776 | |
777 | -def doctest_TimeWindow_weekly_report(): |
778 | - r"""Tests for TimeWindow.weekly_report |
779 | +def doctest_Reports_weekly_report_plain(): |
780 | + r"""Tests for Reports.weekly_report_plain |
781 | |
782 | >>> import sys |
783 | |
784 | >>> from datetime import datetime, time |
785 | >>> from tempfile import NamedTemporaryFile |
786 | - >>> from gtimelog.main import TimeWindow |
787 | + >>> from gtimelog.main import TimeWindow, Reports |
788 | |
789 | >>> vm = time(2, 0) |
790 | >>> min = datetime(2010, 1, 25) |
791 | @@ -331,7 +495,8 @@ |
792 | >>> fh = NamedTemporaryFile() |
793 | |
794 | >>> window = TimeWindow(fh.name, min, max, vm) |
795 | - >>> window.weekly_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
796 | + >>> reports = Reports(window) |
797 | + >>> reports.weekly_report_plain(sys.stdout, 'foo@bar.com', 'Bob Jones') |
798 | To: foo@bar.com |
799 | Subject: Weekly report for Bob Jones (week 04) |
800 | <BLANKLINE> |
801 | @@ -347,7 +512,8 @@ |
802 | >>> fh.flush() |
803 | |
804 | >>> window = TimeWindow(fh.name, min, max, vm) |
805 | - >>> window.weekly_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
806 | + >>> reports = Reports(window) |
807 | + >>> reports.weekly_report_plain(sys.stdout, 'foo@bar.com', 'Bob Jones') |
808 | To: foo@bar.com |
809 | Subject: Weekly report for Bob Jones (week 04) |
810 | <BLANKLINE> |
811 | @@ -367,14 +533,14 @@ |
812 | |
813 | """ |
814 | |
815 | -def doctest_TimeWindow_monthly_report(): |
816 | - r"""Tests for TimeWindow.monthly_report |
817 | +def doctest_Reports_monthly_report_plain(): |
818 | + r"""Tests for Reports.monthly_report_plain |
819 | |
820 | >>> import sys |
821 | |
822 | >>> from datetime import datetime, time |
823 | >>> from tempfile import NamedTemporaryFile |
824 | - >>> from gtimelog.main import TimeWindow |
825 | + >>> from gtimelog.main import TimeWindow, Reports |
826 | |
827 | >>> vm = time(2, 0) |
828 | >>> min = datetime(2007, 9, 1) |
829 | @@ -382,7 +548,8 @@ |
830 | >>> fh = NamedTemporaryFile() |
831 | |
832 | >>> window = TimeWindow(fh.name, min, max, vm) |
833 | - >>> window.monthly_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
834 | + >>> reports = Reports(window) |
835 | + >>> reports.monthly_report_plain(sys.stdout, 'foo@bar.com', 'Bob Jones') |
836 | To: foo@bar.com |
837 | Subject: Monthly report for Bob Jones (2007/09) |
838 | <BLANKLINE> |
839 | @@ -398,11 +565,12 @@ |
840 | >>> fh.flush() |
841 | |
842 | >>> window = TimeWindow(fh.name, min, max, vm) |
843 | - >>> window.monthly_report(sys.stdout, 'foo@bar.com', 'Bob Jones') |
844 | + >>> reports = Reports(window) |
845 | + >>> reports.monthly_report_plain(sys.stdout, 'foo@bar.com', 'Bob Jones') |
846 | To: foo@bar.com |
847 | Subject: Monthly report for Bob Jones (2007/09) |
848 | <BLANKLINE> |
849 | - <BLANKLINE> |
850 | + time |
851 | Bing: stuff 23 min |
852 | Bong: other stuff 3 hours 31 min |
853 | Misc 2 hours 14 min |
854 | @@ -418,36 +586,6 @@ |
855 | |
856 | """ |
857 | |
858 | -def doctest_TimeWindow_to_csv_daily(): |
859 | - r"""Tests for TimeWindow.to_csv_daily |
860 | - |
861 | - >>> from datetime import datetime, time |
862 | - >>> min = datetime(2008, 6, 1) |
863 | - >>> max = datetime(2008, 7, 1) |
864 | - >>> vm = time(2, 0) |
865 | - |
866 | - >>> from StringIO import StringIO |
867 | - >>> sampledata = StringIO(''' |
868 | - ... 2008-06-03 12:45: start |
869 | - ... 2008-06-03 13:00: something |
870 | - ... 2008-06-03 14:45: something else |
871 | - ... 2008-06-03 15:45: etc |
872 | - ... 2008-06-05 12:45: start |
873 | - ... 2008-06-05 13:15: something |
874 | - ... ''') |
875 | - |
876 | - >>> from gtimelog.main import TimeWindow |
877 | - >>> window = TimeWindow(sampledata, min, max, vm) |
878 | - |
879 | - >>> import sys |
880 | - >>> window.to_csv_daily(sys.stdout) |
881 | - date,day-start (hours),slacking (hours),work (hours) |
882 | - 2008-06-03,12.75,0.0,3.0 |
883 | - 2008-06-04,0.0,0.0,0.0 |
884 | - 2008-06-05,12.75,0.0,0.5 |
885 | - |
886 | - """ |
887 | - |
888 | |
889 | def additional_tests(): # for setup.py |
890 | return doctest.DocTestSuite(optionflags=doctest.NORMALIZE_WHITESPACE) |
Can you please resolve those merge conflicts?