~kklimonda/ubuntu/natty/hamster-applet/lp.697667

« back to all changes in this revision

Viewing changes to src/hamster/reports.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Coulson
  • Date: 2010-02-10 02:52:31 UTC
  • mfrom: (1.1.14 upstream)
  • Revision ID: james.westby@ubuntu.com-20100210025231-x0q5h4q7nlvihl09
Tags: 2.29.90-0ubuntu1
* New upstream version
  - workspace tracking - switch activity, when switching desktops
  - chart improvements - theme friendly and less noisier
  - for those without GNOME panel there is now a standalone version, 
    accessible via Applications -> Accessories -> Time Tracker
  - overview window remembers position
  - maintaining cursor on the selected row after edits / refreshes
  - descriptions once again in the main input field, delimited by comma
  - activity suggestion box now sorts items by recency (Patryk Zawadzki)
  - searching
  - simplified save report dialog, thanks to the what you see is what you 
    report revamp
  - overview/stats replaced with activities / totals and stats accessible 
    from totals
  - interactive graphs to drill down in totals
  - miscellaneous performance improvements
  - pixel-perfect graphs
* Updated 01_startup-fix.patch to apply to new source layout
* debian/control:
  - Add build-depend on gnome-doc-utils

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# - coding: utf-8 -
 
2
 
 
3
# Copyright (C) 2008 Toms Bauģis <toms.baugis at gmail.com>
 
4
# Copyright (C) 2008 Nathan Samson <nathansamson at gmail dot com>
 
5
# Copyright (C) 2008 Giorgos Logiotatidis  <seadog at sealabs dot net>
 
6
 
 
7
# This file is part of Project Hamster.
 
8
 
 
9
# Project Hamster is free software: you can redistribute it and/or modify
 
10
# it under the terms of the GNU General Public License as published by
 
11
# the Free Software Foundation, either version 3 of the License, or
 
12
# (at your option) any later version.
 
13
 
 
14
# Project Hamster is distributed in the hope that it will be useful,
 
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
17
# GNU General Public License for more details.
 
18
 
 
19
# You should have received a copy of the GNU General Public License
 
20
# along with Project Hamster.  If not, see <http://www.gnu.org/licenses/>.
 
21
import stuff
 
22
import os
 
23
import datetime as dt
 
24
from xml.dom.minidom import Document
 
25
import csv
 
26
from hamster.i18n import C_
 
27
import copy
 
28
 
 
29
def simple(facts, start_date, end_date, format, path):
 
30
    facts = copy.deepcopy(facts) # dont want to do anything bad to the input
 
31
    report_path = stuff.locale_from_utf8(path)
 
32
 
 
33
    if format == "tsv":
 
34
        writer = TSVWriter(report_path)
 
35
    elif format == "xml":
 
36
        writer = XMLWriter(report_path)
 
37
    elif format == "ical":
 
38
        writer = ICalWriter(report_path)
 
39
    else: #default to HTML
 
40
        writer = HTMLWriter(report_path, start_date, end_date)
 
41
 
 
42
    writer.write_report(facts)
 
43
 
 
44
 
 
45
class ReportWriter(object):
 
46
    #a tiny bit better than repeating the code all the time
 
47
    def __init__(self, path, datetime_format = "%Y-%m-%d %H:%M:%S"):
 
48
        self.file = open(path, "w")
 
49
        self.datetime_format = datetime_format
 
50
 
 
51
    def write_report(self, facts):
 
52
        try:
 
53
            for fact in facts:
 
54
                fact["name"]= fact["name"].encode('utf-8')
 
55
                fact["description"] = (fact["description"] or u"").encode('utf-8')
 
56
                fact["category"] = (fact["category"] or _("Unsorted")).encode('utf-8')
 
57
 
 
58
                if self.datetime_format:
 
59
                    fact["start_time"] = fact["start_time"].strftime(self.datetime_format)
 
60
 
 
61
                    if fact["end_time"]:
 
62
                        fact["end_time"] = fact["end_time"].strftime(self.datetime_format)
 
63
                    else:
 
64
                        fact["end_time"] = ""
 
65
                fact["delta"] = fact["delta"].seconds / 60 + fact["delta"].days * 24 * 60
 
66
                fact["tags"] = ", ".join(fact["tags"])
 
67
 
 
68
                self._write_fact(self.file, fact)
 
69
 
 
70
            self._finish(self.file, facts)
 
71
        finally:
 
72
            self.file.close()
 
73
 
 
74
    def _start(self, file, facts):
 
75
        raise NotImplementedError
 
76
 
 
77
    def _write_fact(self, file, fact):
 
78
        raise NotImplementedError
 
79
 
 
80
    def _finish(self, file, facts):
 
81
        raise NotImplementedError
 
82
 
 
83
class ICalWriter(ReportWriter):
 
84
    """a lame ical writer, could not be bothered with finding a library"""
 
85
    def __init__(self, path):
 
86
        ReportWriter.__init__(self, path, datetime_format = "%Y%m%dT%H%M%S")
 
87
        self.file.write("BEGIN:VCALENDAR\nVERSION:1.0\n")
 
88
 
 
89
 
 
90
    def _write_fact(self, file, fact):
 
91
        #for now we will skip ongoing facts
 
92
        if not fact["end_time"]: return
 
93
 
 
94
        if fact["category"] == _("Unsorted"):
 
95
            fact["category"] = None
 
96
 
 
97
        self.file.write("""BEGIN:VEVENT
 
98
CATEGORIES:%(category)s
 
99
DTSTART:%(start_time)s
 
100
DTEND:%(end_time)s
 
101
SUMMARY:%(name)s
 
102
DESCRIPTION:%(description)s
 
103
END:VEVENT
 
104
""" % fact)
 
105
 
 
106
    def _finish(self, file, facts):
 
107
        self.file.write("END:VCALENDAR\n")
 
108
 
 
109
class TSVWriter(ReportWriter):
 
110
    def __init__(self, path):
 
111
        ReportWriter.__init__(self, path)
 
112
        self.csv_writer = csv.writer(self.file, dialect='excel-tab')
 
113
 
 
114
        headers = [# column title in the TSV export format
 
115
                   _("activity"),
 
116
                   # column title in the TSV export format
 
117
                   _("start time"),
 
118
                   # column title in the TSV export format
 
119
                   _("end time"),
 
120
                   # column title in the TSV export format
 
121
                   _("duration minutes"),
 
122
                   # column title in the TSV export format
 
123
                   _("category"),
 
124
                   # column title in the TSV export format
 
125
                   _("description"),
 
126
                   # column title in the TSV export format
 
127
                   _("tags")]
 
128
        self.csv_writer.writerow([h.encode('utf-8') for h in headers])
 
129
 
 
130
    def _write_fact(self, file, fact):
 
131
        self.csv_writer.writerow([fact[key] for key in ["name", "start_time",
 
132
                               "end_time", "delta", "category", "description",
 
133
                               "tags"]])
 
134
    def _finish(self, file, facts):
 
135
        pass
 
136
 
 
137
class XMLWriter(ReportWriter):
 
138
    def __init__(self, path):
 
139
        ReportWriter.__init__(self, path)
 
140
        self.doc = Document()
 
141
        self.activity_list = self.doc.createElement("activities")
 
142
 
 
143
    def _write_fact(self, file, fact):
 
144
        activity = self.doc.createElement("activity")
 
145
        activity.setAttribute("name", fact["name"])
 
146
        activity.setAttribute("start_time", fact["start_time"])
 
147
        activity.setAttribute("end_time", fact["end_time"])
 
148
        activity.setAttribute("duration_minutes", str(fact["delta"]))
 
149
        activity.setAttribute("category", fact["category"])
 
150
        activity.setAttribute("description", fact["description"])
 
151
        activity.setAttribute("tags", fact["tags"])
 
152
        self.activity_list.appendChild(activity)
 
153
 
 
154
    def _finish(self, file, facts):
 
155
        self.doc.appendChild(self.activity_list)
 
156
        file.write(self.doc.toxml())
 
157
 
 
158
 
 
159
 
 
160
class HTMLWriter(ReportWriter):
 
161
    def __init__(self, path, start_date, end_date):
 
162
        ReportWriter.__init__(self, path, datetime_format = None)
 
163
 
 
164
        dates_dict = stuff.dateDict(start_date, "start_")
 
165
        dates_dict.update(stuff.dateDict(end_date, "end_"))
 
166
        if start_date.year != end_date.year:
 
167
            self.title = _(u"Overview for %(start_B)s %(start_d)s, %(start_Y)s – %(end_B)s %(end_d)s, %(end_Y)s") % dates_dict
 
168
        elif start_date.month != end_date.month:
 
169
            self.title = _(u"Overview for %(start_B)s %(start_d)s – %(end_B)s %(end_d)s, %(end_Y)s") % dates_dict
 
170
        else:
 
171
            self.title = _(u"Overview for %(start_B)s %(start_d)s – %(end_d)s, %(end_Y)s") % dates_dict
 
172
 
 
173
        if start_date == end_date:
 
174
            self.title = _(u"Overview for %(start_B)s %(start_d)s, %(start_Y)s") % dates_dict
 
175
 
 
176
        self.sum_time = {}
 
177
        self.even_row = True
 
178
 
 
179
        """TODO bring template to external file or write to PDF"""
 
180
 
 
181
        self.file.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 
182
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
183
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 
184
<head>
 
185
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
 
186
    <meta name="author" content="hamster-applet" />
 
187
    <title>%(title)s</title>
 
188
    <style type="text/css">
 
189
        body {
 
190
            font-family: "sans-serif";
 
191
            font-size: 12px;
 
192
            padding: 12px;
 
193
            color: #303030;
 
194
 
 
195
        }
 
196
        h1 {
 
197
            border-bottom: 2px solid #303030;
 
198
            padding-bottom: 4px;
 
199
        }
 
200
        h2 {
 
201
            margin-top: 2em;
 
202
            border-bottom: 2px solid #303030;
 
203
        }
 
204
        th, td {
 
205
            text-align: left;
 
206
            padding: 3px;
 
207
            padding-right: 24px;
 
208
        }
 
209
 
 
210
        .row0 {
 
211
                background-color: #eee;
 
212
        }
 
213
 
 
214
        .row1 {
 
215
                background-color: #ffffff;
 
216
        }
 
217
 
 
218
    </style>
 
219
</head>
 
220
<body>
 
221
<h1>%(title)s</h1>""" % {"title": self.title} + """
 
222
<table>
 
223
    <tr>
 
224
        <th>""" + _("Date") + """</th>
 
225
        <th>""" + _("Activity") + """</th>
 
226
        <th>""" + _("Category") + """</th>
 
227
        <th>""" + _("Tags") + """</th>
 
228
        <th>""" + _("Start") + """</th>
 
229
        <th>""" + _("End") + """</th>
 
230
        <th>""" + _("Duration") + """</th>
 
231
        <th>""" + _("Description") + """</th>
 
232
    </tr>""")
 
233
 
 
234
    def _write_fact(self, report, fact):
 
235
        end_time = fact["end_time"]
 
236
 
 
237
        # ongoing task in current day
 
238
        end_time_str = ""
 
239
        if end_time:
 
240
            end_time_str = end_time.strftime('%H:%M')
 
241
 
 
242
        category = ""
 
243
        if fact["category"] != _("Unsorted"): #do not print "unsorted" in list
 
244
            category = fact["category"]
 
245
 
 
246
        description = fact["description"] or ""
 
247
 
 
248
        # fact date column in HTML report
 
249
        report.write("""<tr class="row%d">
 
250
                            <td>%s</td>
 
251
                            <td>%s</td>
 
252
                            <td>%s</td>
 
253
                            <td>%s</td>
 
254
                            <td>%s</td>
 
255
                            <td>%s</td>
 
256
                            <td>%s</td>
 
257
                            <td>%s</td>
 
258
                        </tr>
 
259
                       """ % (int(self.even_row),
 
260
                              fact["start_time"].strftime(
 
261
                                # date column format for each row in HTML report
 
262
                                # Using python datetime formatting syntax. See:
 
263
                                # http://docs.python.org/library/time.html#time.strftime
 
264
                              C_("html report","%b %d, %Y")),
 
265
                              fact["name"],
 
266
                              category,
 
267
                              fact["tags"],
 
268
                              fact["start_time"].strftime('%H:%M'),
 
269
                              end_time_str,
 
270
                              stuff.format_duration(fact["delta"]) or "",
 
271
                              description))
 
272
 
 
273
        self.even_row = not self.even_row
 
274
 
 
275
 
 
276
        # save data for summary table
 
277
        if fact["delta"]:
 
278
            id_string = "<td class=\"smallCell\">%s</td><td class=\"largeCell\">%s</td>" % (fact["category"], fact["name"])
 
279
            self.sum_time[id_string] = self.sum_time.get(id_string, 0) + fact["delta"]
 
280
 
 
281
    def _finish(self, report, facts):
 
282
        report.write("</table>")
 
283
 
 
284
        # summary table
 
285
        report.write("\n<h2>%s</h2>\n" % _("Totals"))
 
286
        report.write("""<table>
 
287
        <tr>
 
288
            <th>""" + _("Category") + """</th>
 
289
            <th>""" + _("Activity") + """</th>
 
290
            <th>""" + _("Duration") + """</th>
 
291
        </tr>\n""")
 
292
        tot_time = 0
 
293
        even_row = False
 
294
        for key in sorted(self.sum_time.keys()):
 
295
            report.write("    <tr class=\"row%d\">%s<td class=\"smallCell\">%s</td></tr>\n" % (int(even_row), key, stuff.format_duration(self.sum_time[key])))
 
296
            tot_time += self.sum_time[key]
 
297
 
 
298
            even_row = not even_row
 
299
 
 
300
        report.write("    <tr><th colspan=\"2\" style=\"text-align:right;\">" + _("Total Time") + ":</th><th>%s</th></tr>\n" % (stuff.format_duration(tot_time)))
 
301
        report.write("</table>\n")
 
302
 
 
303
        report.write("</body>\n</html>")
 
304