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
|
#!/usr/bin/env python
#
# laika prints a summary of the Launchpad bugs you touched this week
#
# man's best friend guides you through Launchpad
# 'Laika died within hours after launch' => an aptly named tool ;)
# http://en.wikipedia.org/wiki/Laika
#
# Copyright 2010 Alex Chiang <achiang@canonical.com>
#
# This program is distributed under the terms of the
# GNU General Public License version 2.
import datetime
from optparse import OptionParser
import os
import re
import sys
from launchpadlib.launchpad import Launchpad
UTCNOW = datetime.datetime.utcnow()
class Report(object):
'''An activity report for a specified Launchpad user.
In order of decreasing importance, search and display activity on
bugs assigned to me, then on bugs that I actually commented upon,
and finally bugs that I just opened, but did nothing further with.
Avoids duplication of activity. So if the status was changed on
a bug assigned to you and you also commented on it, only report the
status change.
'''
bugs = {}
def __init__(self, user, window):
cachedir = "/home/" + user + "/.launchpadlib/cache/"
self.launchpad = Launchpad.login_with('laika', 'production', cachedir)
self.user = self.launchpad.people[user]
self.window = window
def print_header(self, header):
print "==", header, "=="
def in_window(self, date):
'''Timezones do not exist, all datetime objects have to be naive.
Time zone aware means broken.
http://www.enricozini.org/2009/debian/using-python-datetime/
'''
win = datetime.timedelta(self.window)
date = date.replace(tzinfo=None)
delta = UTCNOW - date
return delta <= win
def print_bugid(self, task):
'''Using this interface adds the bug to global bug list.'''
ago = ""
self.bugs[task.bug.id] = 1
delta = UTCNOW - task.bug.date_last_updated.replace(tzinfo=None)
if delta.days > 0:
ago = "%d day%s" % (delta.days, "s" if delta.days > 1 else "")
hours = delta.seconds / 3600
if hours > 0:
ago += ", " if ago else ""
ago += "%d hour%s" % (hours, "s" if hours > 1 else "")
minutes = (delta.seconds - (hours * 3600)) / 60
if minutes > 0:
ago += ", " if ago else ""
ago += "%d minute%s" % (minutes, "s" if minutes > 1 else "")
print task.title
print "https://launchpad.net/bugs/" + str(task.bug.id)
print "last updated", ago, "ago"
def print_assignments(self):
statuses = ['closed', 'fix_released', 'fix_committed',
'in_progress', 'triaged', 'confirmed', 'created']
tasks = self.user.searchTasks(assignee=self.user)
self.print_header("Assigned Bugs")
for t in tasks:
if self.bugs.has_key(t.bug.id):
continue
updates = []
for s in statuses:
attr = 'date_' + s
date = getattr(t, attr)
if not date:
continue
if self.in_window(date):
updates.append(
"\t" + s + ": " + re.sub("\s.*$", "", str(date)))
if updates:
self.print_bugid(t)
for u in updates:
print u
print
print
def print_comments(self):
tasks = self.user.searchTasks(bug_commenter=self.user)
self.print_header("Commented Bugs")
for t in tasks:
if self.bugs.has_key(t.bug.id):
continue
for m in t.bug.messages:
if m.owner_link != self.user.self_link:
continue
if self.in_window(m.date_created):
self.print_bugid(t)
print
break
print
def print_reported(self):
tasks = self.user.searchTasks(bug_reporter=self.user)
self.print_header("Reported Bugs")
for t in tasks:
if self.bugs.has_key(t.bug.id):
continue
if self.in_window(t.bug.date_created):
self.print_bugid(t)
print
def render(self):
self.print_assignments()
self.print_comments()
self.print_reported()
def main():
parser = OptionParser()
parser.add_option('-u', '--user', dest='user',
default=os.getenv('USER'),
help='Specify the Launchpad user id. '
'Defaults to authenticated token owner.')
parser.add_option('-w', '--window', dest='window', type='int',
default=8,
help='Number af days of past activity to look for. '
'Defaults to 8 days')
options, arguments = parser.parse_args()
report = Report(options.user, options.window)
report.render()
if __name__ == "__main__":
main()
|