diff options
Diffstat (limited to 'activity/_activitybase.py')
-rw-r--r-- | activity/_activitybase.py | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/activity/_activitybase.py b/activity/_activitybase.py new file mode 100644 index 0000000..3a0255f --- /dev/null +++ b/activity/_activitybase.py | |||
@@ -0,0 +1,161 @@ | |||
1 | import csv | ||
2 | from datetime import datetime | ||
3 | from cStringIO import StringIO | ||
4 | |||
5 | from MoinMoin.action import ActionBase | ||
6 | from MoinMoin.PageEditor import PageEditor | ||
7 | |||
8 | |||
9 | class FormattedDateTime: | ||
10 | |||
11 | def __init__(self, dt): | ||
12 | self.dt = dt | ||
13 | |||
14 | @classmethod | ||
15 | def from_now(cls): | ||
16 | return cls(datetime.now()) | ||
17 | |||
18 | @property | ||
19 | def time(self): | ||
20 | return self.dt.strftime('%H:%M:%S') | ||
21 | |||
22 | @property | ||
23 | def date(self): | ||
24 | return self.dt.strftime('%Y-%m-%d') | ||
25 | |||
26 | |||
27 | class DataRow(list): | ||
28 | |||
29 | _attr_map = ['start_date', 'start_time', 'end_date', 'end_time', 'task'] | ||
30 | |||
31 | def __setattr__(self, attr, value): | ||
32 | pos = self._attr_map.index(attr) | ||
33 | self[pos] = value | ||
34 | |||
35 | def __getattr__(self, attr): | ||
36 | pos = self._attr_map.index(attr) | ||
37 | return self[pos] | ||
38 | |||
39 | def _make_datetime(self, date, time): | ||
40 | if not date or not time: | ||
41 | return None | ||
42 | |||
43 | try: | ||
44 | return datetime.strptime(' '.join((date, time)), '%Y-%m-%d %H:%M:%S') | ||
45 | except ValueError as err: | ||
46 | return datetime.strptime(' '.join((date, time)), '%Y-%m-%d %H:%M') | ||
47 | |||
48 | @property | ||
49 | def start_datetime(self): | ||
50 | return self._make_datetime(self.start_date, self.start_time) | ||
51 | |||
52 | @property | ||
53 | def end_datetime(self): | ||
54 | return self._make_datetime(self.end_date, self.end_time) | ||
55 | |||
56 | @property | ||
57 | def has_ended(self): | ||
58 | return self.end_date != '' | ||
59 | |||
60 | def mark_ended(self, now=None): | ||
61 | if not now: | ||
62 | now = FormattedDateTime.from_now() | ||
63 | |||
64 | if not self.has_ended: | ||
65 | self.end_date = now.date | ||
66 | self.end_time = now.time | ||
67 | |||
68 | |||
69 | def parse_rows(data): | ||
70 | body = StringIO(data) | ||
71 | |||
72 | if data.startswith('#'): | ||
73 | body.readline() | ||
74 | |||
75 | reader = csv.reader(body, quotechar='"') | ||
76 | return [DataRow(r) for r in reader] | ||
77 | |||
78 | |||
79 | class ActivityAction(ActionBase): | ||
80 | |||
81 | def __init__(self, pagename, request): | ||
82 | ActionBase.__init__(self, pagename, request) | ||
83 | |||
84 | self.page = PageEditor(request, pagename) | ||
85 | self.use_ticket = True | ||
86 | |||
87 | def get_rows(self): | ||
88 | return parse_rows(self.page.body) | ||
89 | |||
90 | def update_page(self, rows): | ||
91 | out_page = StringIO() | ||
92 | out_page.write('#format timecsv\n') | ||
93 | csv.writer(out_page, quotechar='"').writerows(rows) | ||
94 | |||
95 | return self.page.saveText(out_page.getvalue(), self.request.rev or 0) | ||
96 | |||
97 | def start_activity(self, description): | ||
98 | rows = self.get_rows() | ||
99 | now = FormattedDateTime.from_now() | ||
100 | |||
101 | rows[-1].mark_ended(now) | ||
102 | rows.append((now.date, now.time, '', '', description)) | ||
103 | |||
104 | return self.update_page(rows) | ||
105 | |||
106 | def stop_activity(self): | ||
107 | rows = self.get_rows() | ||
108 | rows[-1].mark_ended() | ||
109 | self.update_page(rows) | ||
110 | |||
111 | @property | ||
112 | def can_use_activity(self): | ||
113 | return self.page.pi['format'] == 'timecsv' | ||
114 | |||
115 | |||
116 | class Analysis: | ||
117 | |||
118 | def __init__(self, data): | ||
119 | self.raw_data = data | ||
120 | self.data = {} | ||
121 | self.order = [] | ||
122 | |||
123 | @staticmethod | ||
124 | def _to_time(value): | ||
125 | hours = value // 3600 | ||
126 | minutes = (value // 60) - (hours * 60) | ||
127 | |||
128 | if hours == 0: | ||
129 | return '{} minutes'.format(minutes) | ||
130 | else: | ||
131 | return '{} hours {} minutes'.format(hours, minutes) | ||
132 | |||
133 | def process(self): | ||
134 | self.raw_data.sort(key=lambda i: i.start_datetime, reverse=True) | ||
135 | |||
136 | for row in self.raw_data: | ||
137 | date = row.start_datetime.date() | ||
138 | data = self.data.get(date, None) | ||
139 | |||
140 | if date not in self.order: | ||
141 | self.order.append(date) | ||
142 | |||
143 | if data is None: | ||
144 | data = self.data[date] = {} | ||
145 | |||
146 | if not data.get(row.task): | ||
147 | data[row.task] = [0, False] | ||
148 | |||
149 | if row.end_datetime: | ||
150 | data[row.task][0] += ( | ||
151 | row.end_datetime - row.start_datetime).seconds | ||
152 | else: | ||
153 | data[row.task][0] += ( | ||
154 | datetime.now() - row.start_datetime).seconds | ||
155 | |||
156 | if not row.has_ended: | ||
157 | data[row.task][1] = True | ||
158 | |||
159 | def __iter__(self): | ||
160 | for key in self.order: | ||
161 | yield key, dict((k, (self._to_time(v[0]), v[1])) for k, v in self.data[key].items()) | ||