Merge lp:~laurynas-speicys/gtimelog/categorized-reports into lp:~gtimelog-dev/gtimelog/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
Reviewer Review Type Date Requested Status
Laurynas Speicys (community) Needs Resubmitting
Marius Gedminas Needs Fixing
Review via email: mp+45865@code.launchpad.net

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.
Revision history for this message
Marius Gedminas (mgedmin) wrote :

Can you please resolve those merge conflicts?

review: Needs Fixing
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)

Subscribers

People subscribed via source and target branches