##// END OF EJS Templates
code garden
marcink -
r1908:b3a3890b beta
parent child Browse files
Show More
@@ -1,449 +1,449 b''
1 # The code in this module is entirely lifted from the Lamson project
1 # The code in this module is entirely lifted from the Lamson project
2 # (http://lamsonproject.org/). Its copyright is:
2 # (http://lamsonproject.org/). Its copyright is:
3
3
4 # Copyright (c) 2008, Zed A. Shaw
4 # Copyright (c) 2008, Zed A. Shaw
5 # All rights reserved.
5 # All rights reserved.
6
6
7 # It is provided under this license:
7 # It is provided under this license:
8
8
9 # Redistribution and use in source and binary forms, with or without
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are met:
10 # modification, are permitted provided that the following conditions are met:
11
11
12 # * Redistributions of source code must retain the above copyright notice, this
12 # * Redistributions of source code must retain the above copyright notice, this
13 # list of conditions and the following disclaimer.
13 # list of conditions and the following disclaimer.
14
14
15 # * Redistributions in binary form must reproduce the above copyright notice,
15 # * Redistributions in binary form must reproduce the above copyright notice,
16 # this list of conditions and the following disclaimer in the documentation
16 # this list of conditions and the following disclaimer in the documentation
17 # and/or other materials provided with the distribution.
17 # and/or other materials provided with the distribution.
18
18
19 # * Neither the name of the Zed A. Shaw nor the names of its contributors may
19 # * Neither the name of the Zed A. Shaw nor the names of its contributors may
20 # be used to endorse or promote products derived from this software without
20 # be used to endorse or promote products derived from this software without
21 # specific prior written permission.
21 # specific prior written permission.
22
22
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 # POSSIBILITY OF SUCH DAMAGE.
34 # POSSIBILITY OF SUCH DAMAGE.
35
35
36 import os
36 import os
37 import mimetypes
37 import mimetypes
38 import string
38 import string
39 from email import encoders
39 from email import encoders
40 from email.charset import Charset
40 from email.charset import Charset
41 from email.utils import parseaddr
41 from email.utils import parseaddr
42 from email.mime.base import MIMEBase
42 from email.mime.base import MIMEBase
43
43
44 ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc']
44 ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc']
45 DEFAULT_ENCODING = "utf-8"
45 DEFAULT_ENCODING = "utf-8"
46 VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v
46 VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v
47
47
48
48
49 def normalize_header(header):
49 def normalize_header(header):
50 return string.capwords(header.lower(), '-')
50 return string.capwords(header.lower(), '-')
51
51
52
52
53 class EncodingError(Exception):
53 class EncodingError(Exception):
54 """Thrown when there is an encoding error."""
54 """Thrown when there is an encoding error."""
55 pass
55 pass
56
56
57
57
58 class MailBase(object):
58 class MailBase(object):
59 """MailBase is used as the basis of lamson.mail and contains the basics of
59 """MailBase is used as the basis of lamson.mail and contains the basics of
60 encoding an email. You actually can do all your email processing with this
60 encoding an email. You actually can do all your email processing with this
61 class, but it's more raw.
61 class, but it's more raw.
62 """
62 """
63 def __init__(self, items=()):
63 def __init__(self, items=()):
64 self.headers = dict(items)
64 self.headers = dict(items)
65 self.parts = []
65 self.parts = []
66 self.body = None
66 self.body = None
67 self.content_encoding = {'Content-Type': (None, {}),
67 self.content_encoding = {'Content-Type': (None, {}),
68 'Content-Disposition': (None, {}),
68 'Content-Disposition': (None, {}),
69 'Content-Transfer-Encoding': (None, {})}
69 'Content-Transfer-Encoding': (None, {})}
70
70
71 def __getitem__(self, key):
71 def __getitem__(self, key):
72 return self.headers.get(normalize_header(key), None)
72 return self.headers.get(normalize_header(key), None)
73
73
74 def __len__(self):
74 def __len__(self):
75 return len(self.headers)
75 return len(self.headers)
76
76
77 def __iter__(self):
77 def __iter__(self):
78 return iter(self.headers)
78 return iter(self.headers)
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return normalize_header(key) in self.headers
81 return normalize_header(key) in self.headers
82
82
83 def __setitem__(self, key, value):
83 def __setitem__(self, key, value):
84 self.headers[normalize_header(key)] = value
84 self.headers[normalize_header(key)] = value
85
85
86 def __delitem__(self, key):
86 def __delitem__(self, key):
87 del self.headers[normalize_header(key)]
87 del self.headers[normalize_header(key)]
88
88
89 def __nonzero__(self):
89 def __nonzero__(self):
90 return self.body != None or len(self.headers) > 0 or len(self.parts) > 0
90 return self.body != None or len(self.headers) > 0 or len(self.parts) > 0
91
91
92 def keys(self):
92 def keys(self):
93 """Returns the sorted keys."""
93 """Returns the sorted keys."""
94 return sorted(self.headers.keys())
94 return sorted(self.headers.keys())
95
95
96 def attach_file(self, filename, data, ctype, disposition):
96 def attach_file(self, filename, data, ctype, disposition):
97 """
97 """
98 A file attachment is a raw attachment with a disposition that
98 A file attachment is a raw attachment with a disposition that
99 indicates the file name.
99 indicates the file name.
100 """
100 """
101 assert filename, "You can't attach a file without a filename."
101 assert filename, "You can't attach a file without a filename."
102 ctype = ctype.lower()
102 ctype = ctype.lower()
103
103
104 part = MailBase()
104 part = MailBase()
105 part.body = data
105 part.body = data
106 part.content_encoding['Content-Type'] = (ctype, {'name': filename})
106 part.content_encoding['Content-Type'] = (ctype, {'name': filename})
107 part.content_encoding['Content-Disposition'] = (disposition,
107 part.content_encoding['Content-Disposition'] = (disposition,
108 {'filename': filename})
108 {'filename': filename})
109 self.parts.append(part)
109 self.parts.append(part)
110
110
111 def attach_text(self, data, ctype):
111 def attach_text(self, data, ctype):
112 """
112 """
113 This attaches a simpler text encoded part, which doesn't have a
113 This attaches a simpler text encoded part, which doesn't have a
114 filename.
114 filename.
115 """
115 """
116 ctype = ctype.lower()
116 ctype = ctype.lower()
117
117
118 part = MailBase()
118 part = MailBase()
119 part.body = data
119 part.body = data
120 part.content_encoding['Content-Type'] = (ctype, {})
120 part.content_encoding['Content-Type'] = (ctype, {})
121 self.parts.append(part)
121 self.parts.append(part)
122
122
123 def walk(self):
123 def walk(self):
124 for p in self.parts:
124 for p in self.parts:
125 yield p
125 yield p
126 for x in p.walk():
126 for x in p.walk():
127 yield x
127 yield x
128
128
129
129
130 class MailResponse(object):
130 class MailResponse(object):
131 """
131 """
132 You are given MailResponse objects from the lamson.view methods, and
132 You are given MailResponse objects from the lamson.view methods, and
133 whenever you want to generate an email to send to someone. It has the
133 whenever you want to generate an email to send to someone. It has the
134 same basic functionality as MailRequest, but it is designed to be written
134 same basic functionality as MailRequest, but it is designed to be written
135 to, rather than read from (although you can do both).
135 to, rather than read from (although you can do both).
136
136
137 You can easily set a Body or Html during creation or after by passing it
137 You can easily set a Body or Html during creation or after by passing it
138 as __init__ parameters, or by setting those attributes.
138 as __init__ parameters, or by setting those attributes.
139
139
140 You can initially set the From, To, and Subject, but they are headers so
140 You can initially set the From, To, and Subject, but they are headers so
141 use the dict notation to change them: msg['From'] = 'joe@test.com'.
141 use the dict notation to change them: msg['From'] = 'joe@test.com'.
142
142
143 The message is not fully crafted until right when you convert it with
143 The message is not fully crafted until right when you convert it with
144 MailResponse.to_message. This lets you change it and work with it, then
144 MailResponse.to_message. This lets you change it and work with it, then
145 send it out when it's ready.
145 send it out when it's ready.
146 """
146 """
147 def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None,
147 def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None,
148 separator="; "):
148 separator="; "):
149 self.Body = Body
149 self.Body = Body
150 self.Html = Html
150 self.Html = Html
151 self.base = MailBase([('To', To), ('From', From), ('Subject', Subject)])
151 self.base = MailBase([('To', To), ('From', From), ('Subject', Subject)])
152 self.multipart = self.Body and self.Html
152 self.multipart = self.Body and self.Html
153 self.attachments = []
153 self.attachments = []
154 self.separator = separator
154 self.separator = separator
155
155
156 def __contains__(self, key):
156 def __contains__(self, key):
157 return self.base.__contains__(key)
157 return self.base.__contains__(key)
158
158
159 def __getitem__(self, key):
159 def __getitem__(self, key):
160 return self.base.__getitem__(key)
160 return self.base.__getitem__(key)
161
161
162 def __setitem__(self, key, val):
162 def __setitem__(self, key, val):
163 return self.base.__setitem__(key, val)
163 return self.base.__setitem__(key, val)
164
164
165 def __delitem__(self, name):
165 def __delitem__(self, name):
166 del self.base[name]
166 del self.base[name]
167
167
168 def attach(self, filename=None, content_type=None, data=None,
168 def attach(self, filename=None, content_type=None, data=None,
169 disposition=None):
169 disposition=None):
170 """
170 """
171
171
172 Simplifies attaching files from disk or data as files. To attach
172 Simplifies attaching files from disk or data as files. To attach
173 simple text simple give data and a content_type. To attach a file,
173 simple text simple give data and a content_type. To attach a file,
174 give the data/content_type/filename/disposition combination.
174 give the data/content_type/filename/disposition combination.
175
175
176 For convenience, if you don't give data and only a filename, then it
176 For convenience, if you don't give data and only a filename, then it
177 will read that file's contents when you call to_message() later. If
177 will read that file's contents when you call to_message() later. If
178 you give data and filename then it will assume you've filled data
178 you give data and filename then it will assume you've filled data
179 with what the file's contents are and filename is just the name to
179 with what the file's contents are and filename is just the name to
180 use.
180 use.
181 """
181 """
182
182
183 assert filename or data, ("You must give a filename or some data to "
183 assert filename or data, ("You must give a filename or some data to "
184 "attach.")
184 "attach.")
185 assert data or os.path.exists(filename), ("File doesn't exist, and no "
185 assert data or os.path.exists(filename), ("File doesn't exist, and no "
186 "data given.")
186 "data given.")
187
187
188 self.multipart = True
188 self.multipart = True
189
189
190 if filename and not content_type:
190 if filename and not content_type:
191 content_type, encoding = mimetypes.guess_type(filename)
191 content_type, encoding = mimetypes.guess_type(filename)
192
192
193 assert content_type, ("No content type given, and couldn't guess "
193 assert content_type, ("No content type given, and couldn't guess "
194 "from the filename: %r" % filename)
194 "from the filename: %r" % filename)
195
195
196 self.attachments.append({'filename': filename,
196 self.attachments.append({'filename': filename,
197 'content_type': content_type,
197 'content_type': content_type,
198 'data': data,
198 'data': data,
199 'disposition': disposition,})
199 'disposition': disposition,})
200
200
201 def attach_part(self, part):
201 def attach_part(self, part):
202 """
202 """
203 Attaches a raw MailBase part from a MailRequest (or anywhere)
203 Attaches a raw MailBase part from a MailRequest (or anywhere)
204 so that you can copy it over.
204 so that you can copy it over.
205 """
205 """
206 self.multipart = True
206 self.multipart = True
207
207
208 self.attachments.append({'filename': None,
208 self.attachments.append({'filename': None,
209 'content_type': None,
209 'content_type': None,
210 'data': None,
210 'data': None,
211 'disposition': None,
211 'disposition': None,
212 'part': part,
212 'part': part,
213 })
213 })
214
214
215 def attach_all_parts(self, mail_request):
215 def attach_all_parts(self, mail_request):
216 """
216 """
217 Used for copying the attachment parts of a mail.MailRequest
217 Used for copying the attachment parts of a mail.MailRequest
218 object for mailing lists that need to maintain attachments.
218 object for mailing lists that need to maintain attachments.
219 """
219 """
220 for part in mail_request.all_parts():
220 for part in mail_request.all_parts():
221 self.attach_part(part)
221 self.attach_part(part)
222
222
223 self.base.content_encoding = mail_request.base.content_encoding.copy()
223 self.base.content_encoding = mail_request.base.content_encoding.copy()
224
224
225 def clear(self):
225 def clear(self):
226 """
226 """
227 Clears out the attachments so you can redo them. Use this to keep the
227 Clears out the attachments so you can redo them. Use this to keep the
228 headers for a series of different messages with different attachments.
228 headers for a series of different messages with different attachments.
229 """
229 """
230 del self.attachments[:]
230 del self.attachments[:]
231 del self.base.parts[:]
231 del self.base.parts[:]
232 self.multipart = False
232 self.multipart = False
233
233
234 def update(self, message):
234 def update(self, message):
235 """
235 """
236 Used to easily set a bunch of heading from another dict
236 Used to easily set a bunch of heading from another dict
237 like object.
237 like object.
238 """
238 """
239 for k in message.keys():
239 for k in message.keys():
240 self.base[k] = message[k]
240 self.base[k] = message[k]
241
241
242 def __str__(self):
242 def __str__(self):
243 """
243 """
244 Converts to a string.
244 Converts to a string.
245 """
245 """
246 return self.to_message().as_string()
246 return self.to_message().as_string()
247
247
248 def _encode_attachment(self, filename=None, content_type=None, data=None,
248 def _encode_attachment(self, filename=None, content_type=None, data=None,
249 disposition=None, part=None):
249 disposition=None, part=None):
250 """
250 """
251 Used internally to take the attachments mentioned in self.attachments
251 Used internally to take the attachments mentioned in self.attachments
252 and do the actual encoding in a lazy way when you call to_message.
252 and do the actual encoding in a lazy way when you call to_message.
253 """
253 """
254 if part:
254 if part:
255 self.base.parts.append(part)
255 self.base.parts.append(part)
256 elif filename:
256 elif filename:
257 if not data:
257 if not data:
258 data = open(filename).read()
258 data = open(filename).read()
259
259
260 self.base.attach_file(filename, data, content_type,
260 self.base.attach_file(filename, data, content_type,
261 disposition or 'attachment')
261 disposition or 'attachment')
262 else:
262 else:
263 self.base.attach_text(data, content_type)
263 self.base.attach_text(data, content_type)
264
264
265 ctype = self.base.content_encoding['Content-Type'][0]
265 ctype = self.base.content_encoding['Content-Type'][0]
266
266
267 if ctype and not ctype.startswith('multipart'):
267 if ctype and not ctype.startswith('multipart'):
268 self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
268 self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
269
269
270 def to_message(self):
270 def to_message(self):
271 """
271 """
272 Figures out all the required steps to finally craft the
272 Figures out all the required steps to finally craft the
273 message you need and return it. The resulting message
273 message you need and return it. The resulting message
274 is also available as a self.base attribute.
274 is also available as a self.base attribute.
275
275
276 What is returned is a Python email API message you can
276 What is returned is a Python email API message you can
277 use with those APIs. The self.base attribute is the raw
277 use with those APIs. The self.base attribute is the raw
278 lamson.encoding.MailBase.
278 lamson.encoding.MailBase.
279 """
279 """
280 del self.base.parts[:]
280 del self.base.parts[:]
281
281
282 if self.Body and self.Html:
282 if self.Body and self.Html:
283 self.multipart = True
283 self.multipart = True
284 self.base.content_encoding['Content-Type'] = (
284 self.base.content_encoding['Content-Type'] = (
285 'multipart/alternative', {})
285 'multipart/alternative', {})
286
286
287 if self.multipart:
287 if self.multipart:
288 self.base.body = None
288 self.base.body = None
289 if self.Body:
289 if self.Body:
290 self.base.attach_text(self.Body, 'text/plain')
290 self.base.attach_text(self.Body, 'text/plain')
291
291
292 if self.Html:
292 if self.Html:
293 self.base.attach_text(self.Html, 'text/html')
293 self.base.attach_text(self.Html, 'text/html')
294
294
295 for args in self.attachments:
295 for args in self.attachments:
296 self._encode_attachment(**args)
296 self._encode_attachment(**args)
297
297
298 elif self.Body:
298 elif self.Body:
299 self.base.body = self.Body
299 self.base.body = self.Body
300 self.base.content_encoding['Content-Type'] = ('text/plain', {})
300 self.base.content_encoding['Content-Type'] = ('text/plain', {})
301
301
302 elif self.Html:
302 elif self.Html:
303 self.base.body = self.Html
303 self.base.body = self.Html
304 self.base.content_encoding['Content-Type'] = ('text/html', {})
304 self.base.content_encoding['Content-Type'] = ('text/html', {})
305
305
306 return to_message(self.base, separator=self.separator)
306 return to_message(self.base, separator=self.separator)
307
307
308 def all_parts(self):
308 def all_parts(self):
309 """
309 """
310 Returns all the encoded parts. Only useful for debugging
310 Returns all the encoded parts. Only useful for debugging
311 or inspecting after calling to_message().
311 or inspecting after calling to_message().
312 """
312 """
313 return self.base.parts
313 return self.base.parts
314
314
315 def keys(self):
315 def keys(self):
316 return self.base.keys()
316 return self.base.keys()
317
317
318
318
319 def to_message(mail, separator="; "):
319 def to_message(mail, separator="; "):
320 """
320 """
321 Given a MailBase message, this will construct a MIMEPart
321 Given a MailBase message, this will construct a MIMEPart
322 that is canonicalized for use with the Python email API.
322 that is canonicalized for use with the Python email API.
323 """
323 """
324 ctype, params = mail.content_encoding['Content-Type']
324 ctype, params = mail.content_encoding['Content-Type']
325
325
326 if not ctype:
326 if not ctype:
327 if mail.parts:
327 if mail.parts:
328 ctype = 'multipart/mixed'
328 ctype = 'multipart/mixed'
329 else:
329 else:
330 ctype = 'text/plain'
330 ctype = 'text/plain'
331 else:
331 else:
332 if mail.parts:
332 if mail.parts:
333 assert ctype.startswith(("multipart", "message")), \
333 assert ctype.startswith(("multipart", "message")), \
334 "Content type should be multipart or message, not %r" % ctype
334 "Content type should be multipart or message, not %r" % ctype
335
335
336 # adjust the content type according to what it should be now
336 # adjust the content type according to what it should be now
337 mail.content_encoding['Content-Type'] = (ctype, params)
337 mail.content_encoding['Content-Type'] = (ctype, params)
338
338
339 try:
339 try:
340 out = MIMEPart(ctype, **params)
340 out = MIMEPart(ctype, **params)
341 except TypeError, exc: # pragma: no cover
341 except TypeError, exc: # pragma: no cover
342 raise EncodingError("Content-Type malformed, not allowed: %r; "
342 raise EncodingError("Content-Type malformed, not allowed: %r; "
343 "%r (Python ERROR: %s" %
343 "%r (Python ERROR: %s" %
344 (ctype, params, exc.message))
344 (ctype, params, exc.message))
345
345
346 for k in mail.keys():
346 for k in mail.keys():
347 if k in ADDRESS_HEADERS_WHITELIST:
347 if k in ADDRESS_HEADERS_WHITELIST:
348 out[k.encode('ascii')] = header_to_mime_encoding(
348 out[k.encode('ascii')] = header_to_mime_encoding(
349 mail[k],
349 mail[k],
350 not_email=False,
350 not_email=False,
351 separator=separator
351 separator=separator
352 )
352 )
353 else:
353 else:
354 out[k.encode('ascii')] = header_to_mime_encoding(
354 out[k.encode('ascii')] = header_to_mime_encoding(
355 mail[k],
355 mail[k],
356 not_email=True
356 not_email=True
357 )
357 )
358
358
359 out.extract_payload(mail)
359 out.extract_payload(mail)
360
360
361 # go through the children
361 # go through the children
362 for part in mail.parts:
362 for part in mail.parts:
363 out.attach(to_message(part))
363 out.attach(to_message(part))
364
364
365 return out
365 return out
366
366
367 class MIMEPart(MIMEBase):
367 class MIMEPart(MIMEBase):
368 """
368 """
369 A reimplementation of nearly everything in email.mime to be more useful
369 A reimplementation of nearly everything in email.mime to be more useful
370 for actually attaching things. Rather than one class for every type of
370 for actually attaching things. Rather than one class for every type of
371 thing you'd encode, there's just this one, and it figures out how to
371 thing you'd encode, there's just this one, and it figures out how to
372 encode what you ask it.
372 encode what you ask it.
373 """
373 """
374 def __init__(self, type, **params):
374 def __init__(self, type, **params):
375 self.maintype, self.subtype = type.split('/')
375 self.maintype, self.subtype = type.split('/')
376 MIMEBase.__init__(self, self.maintype, self.subtype, **params)
376 MIMEBase.__init__(self, self.maintype, self.subtype, **params)
377
377
378 def add_text(self, content):
378 def add_text(self, content):
379 # this is text, so encode it in canonical form
379 # this is text, so encode it in canonical form
380 try:
380 try:
381 encoded = content.encode('ascii')
381 encoded = content.encode('ascii')
382 charset = 'ascii'
382 charset = 'ascii'
383 except UnicodeError:
383 except UnicodeError:
384 encoded = content.encode('utf-8')
384 encoded = content.encode('utf-8')
385 charset = 'utf-8'
385 charset = 'utf-8'
386
386
387 self.set_payload(encoded, charset=charset)
387 self.set_payload(encoded, charset=charset)
388
388
389 def extract_payload(self, mail):
389 def extract_payload(self, mail):
390 if mail.body == None: return # only None, '' is still ok
390 if mail.body == None: return # only None, '' is still ok
391
391
392 ctype, ctype_params = mail.content_encoding['Content-Type']
392 ctype, ctype_params = mail.content_encoding['Content-Type']
393 cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
393 cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
394
394
395 assert ctype, ("Extract payload requires that mail.content_encoding "
395 assert ctype, ("Extract payload requires that mail.content_encoding "
396 "have a valid Content-Type.")
396 "have a valid Content-Type.")
397
397
398 if ctype.startswith("text/"):
398 if ctype.startswith("text/"):
399 self.add_text(mail.body)
399 self.add_text(mail.body)
400 else:
400 else:
401 if cdisp:
401 if cdisp:
402 # replicate the content-disposition settings
402 # replicate the content-disposition settings
403 self.add_header('Content-Disposition', cdisp, **cdisp_params)
403 self.add_header('Content-Disposition', cdisp, **cdisp_params)
404
404
405 self.set_payload(mail.body)
405 self.set_payload(mail.body)
406 encoders.encode_base64(self)
406 encoders.encode_base64(self)
407
407
408 def __repr__(self):
408 def __repr__(self):
409 return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
409 return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
410 self.subtype,
410 self.subtype,
411 self.maintype,
411 self.maintype,
412 self['Content-Type'],
412 self['Content-Type'],
413 self['Content-Disposition'],
413 self['Content-Disposition'],
414 self.is_multipart())
414 self.is_multipart())
415
415
416
416
417 def header_to_mime_encoding(value, not_email=False, separator=", "):
417 def header_to_mime_encoding(value, not_email=False, separator=", "):
418 if not value: return ""
418 if not value: return ""
419
419
420 encoder = Charset(DEFAULT_ENCODING)
420 encoder = Charset(DEFAULT_ENCODING)
421 if type(value) == list:
421 if type(value) == list:
422 return separator.join(properly_encode_header(
422 return separator.join(properly_encode_header(
423 v, encoder, not_email) for v in value)
423 v, encoder, not_email) for v in value)
424 else:
424 else:
425 return properly_encode_header(value, encoder, not_email)
425 return properly_encode_header(value, encoder, not_email)
426
426
427 def properly_encode_header(value, encoder, not_email):
427 def properly_encode_header(value, encoder, not_email):
428 """
428 """
429 The only thing special (weird) about this function is that it tries
429 The only thing special (weird) about this function is that it tries
430 to do a fast check to see if the header value has an email address in
430 to do a fast check to see if the header value has an email address in
431 it. Since random headers could have an email address, and email addresses
431 it. Since random headers could have an email address, and email addresses
432 have weird special formatting rules, we have to check for it.
432 have weird special formatting rules, we have to check for it.
433
433
434 Normally this works fine, but in Librelist, we need to "obfuscate" email
434 Normally this works fine, but in Librelist, we need to "obfuscate" email
435 addresses by changing the '@' to '-AT-'. This is where
435 addresses by changing the '@' to '-AT-'. This is where
436 VALUE_IS_EMAIL_ADDRESS exists. It's a simple lambda returning True/False
436 VALUE_IS_EMAIL_ADDRESS exists. It's a simple lambda returning True/False
437 to check if a header value has an email address. If you need to make this
437 to check if a header value has an email address. If you need to make this
438 check different, then change this.
438 check different, then change this.
439 """
439 """
440 try:
440 try:
441 return value.encode("ascii")
441 return value.encode("ascii")
442 except UnicodeEncodeError:
442 except UnicodeEncodeError:
443 if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
443 if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
444 # this could have an email address, make sure we don't screw it up
444 # this could have an email address, make sure we don't screw it up
445 name, address = parseaddr(value)
445 name, address = parseaddr(value)
446 return '"%s" <%s>' % (
446 return '"%s" <%s>' % (
447 encoder.header_encode(name.encode("utf-8")), address)
447 encoder.header_encode(name.encode("utf-8")), address)
448
448
449 return encoder.header_encode(value.encode("utf-8"))
449 return encoder.header_encode(value.encode("utf-8"))
@@ -1,697 +1,697 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('summary')}
12 ${_('summary')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <%
20 <%
21 summary = lambda n:{False:'summary-short'}.get(n)
21 summary = lambda n:{False:'summary-short'}.get(n)
22 %>
22 %>
23 %if c.show_stats:
23 %if c.show_stats:
24 <div class="box box-left">
24 <div class="box box-left">
25 %else:
25 %else:
26 <div class="box">
26 <div class="box">
27 %endif
27 %endif
28 <!-- box / title -->
28 <!-- box / title -->
29 <div class="title">
29 <div class="title">
30 ${self.breadcrumbs()}
30 ${self.breadcrumbs()}
31 </div>
31 </div>
32 <!-- end box / title -->
32 <!-- end box / title -->
33 <div class="form">
33 <div class="form">
34 <div id="summary" class="fields">
34 <div id="summary" class="fields">
35
35
36 <div class="field">
36 <div class="field">
37 <div class="label-summary">
37 <div class="label-summary">
38 <label>${_('Name')}:</label>
38 <label>${_('Name')}:</label>
39 </div>
39 </div>
40 <div class="input ${summary(c.show_stats)}">
40 <div class="input ${summary(c.show_stats)}">
41 <div style="float:right;padding:5px 0px 0px 5px">
41 <div style="float:right;padding:5px 0px 0px 5px">
42 %if c.rhodecode_user.username != 'default':
42 %if c.rhodecode_user.username != 'default':
43 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
43 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
44 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
44 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
45 %else:
45 %else:
46 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
46 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
47 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
47 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
48 %endif
48 %endif
49 </div>
49 </div>
50 %if c.rhodecode_user.username != 'default':
50 %if c.rhodecode_user.username != 'default':
51 %if c.following:
51 %if c.following:
52 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
52 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
53 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
53 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
54 </span>
54 </span>
55 %else:
55 %else:
56 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
56 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
57 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
57 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
58 </span>
58 </span>
59 %endif
59 %endif
60 %endif:
60 %endif:
61 ##REPO TYPE
61 ##REPO TYPE
62 %if c.dbrepo.repo_type =='hg':
62 %if c.dbrepo.repo_type =='hg':
63 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
63 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
64 %endif
64 %endif
65 %if c.dbrepo.repo_type =='git':
65 %if c.dbrepo.repo_type =='git':
66 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
66 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
67 %endif
67 %endif
68
68
69 ##PUBLIC/PRIVATE
69 ##PUBLIC/PRIVATE
70 %if c.dbrepo.private:
70 %if c.dbrepo.private:
71 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
71 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
72 %else:
72 %else:
73 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
73 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
74 %endif
74 %endif
75
75
76 ##REPO NAME
76 ##REPO NAME
77 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
77 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
78
78
79 ##FORK
79 ##FORK
80 %if c.dbrepo.fork:
80 %if c.dbrepo.fork:
81 <div style="margin-top:5px;clear:both"">
81 <div style="margin-top:5px;clear:both"">
82 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
82 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
83 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
83 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
84 </a>
84 </a>
85 </div>
85 </div>
86 %endif
86 %endif
87 ##REMOTE
87 ##REMOTE
88 %if c.dbrepo.clone_uri:
88 %if c.dbrepo.clone_uri:
89 <div style="margin-top:5px;clear:both">
89 <div style="margin-top:5px;clear:both">
90 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
90 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
91 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
91 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
92 </a>
92 </a>
93 </div>
93 </div>
94 %endif
94 %endif
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 <div class="field">
98 <div class="field">
99 <div class="label-summary">
99 <div class="label-summary">
100 <label>${_('Description')}:</label>
100 <label>${_('Description')}:</label>
101 </div>
101 </div>
102 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
102 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
103 </div>
103 </div>
104
104
105 <div class="field">
105 <div class="field">
106 <div class="label-summary">
106 <div class="label-summary">
107 <label>${_('Contact')}:</label>
107 <label>${_('Contact')}:</label>
108 </div>
108 </div>
109 <div class="input ${summary(c.show_stats)}">
109 <div class="input ${summary(c.show_stats)}">
110 <div class="gravatar">
110 <div class="gravatar">
111 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
111 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
112 </div>
112 </div>
113 ${_('Username')}: ${c.dbrepo.user.username}<br/>
113 ${_('Username')}: ${c.dbrepo.user.username}<br/>
114 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
114 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
115 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
115 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
116 </div>
116 </div>
117 </div>
117 </div>
118
118
119 <div class="field">
119 <div class="field">
120 <div class="label-summary">
120 <div class="label-summary">
121 <label>${_('Clone url')}:</label>
121 <label>${_('Clone url')}:</label>
122 </div>
122 </div>
123 <div class="input ${summary(c.show_stats)}">
123 <div class="input ${summary(c.show_stats)}">
124 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
124 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
125 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
125 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
126 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
126 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
127 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
127 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
128 </div>
128 </div>
129 </div>
129 </div>
130
130
131 <div class="field">
131 <div class="field">
132 <div class="label-summary">
132 <div class="label-summary">
133 <label>${_('Trending files')}:</label>
133 <label>${_('Trending files')}:</label>
134 </div>
134 </div>
135 <div class="input ${summary(c.show_stats)}">
135 <div class="input ${summary(c.show_stats)}">
136 %if c.show_stats:
136 %if c.show_stats:
137 <div id="lang_stats"></div>
137 <div id="lang_stats"></div>
138 %else:
138 %else:
139 ${_('Statistics are disabled for this repository')}
139 ${_('Statistics are disabled for this repository')}
140 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
140 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
141 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
141 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
142 %endif
142 %endif
143 %endif
143 %endif
144 </div>
144 </div>
145 </div>
145 </div>
146
146
147 <div class="field">
147 <div class="field">
148 <div class="label-summary">
148 <div class="label-summary">
149 <label>${_('Download')}:</label>
149 <label>${_('Download')}:</label>
150 </div>
150 </div>
151 <div class="input ${summary(c.show_stats)}">
151 <div class="input ${summary(c.show_stats)}">
152 %if len(c.rhodecode_repo.revisions) == 0:
152 %if len(c.rhodecode_repo.revisions) == 0:
153 ${_('There are no downloads yet')}
153 ${_('There are no downloads yet')}
154 %elif c.enable_downloads is False:
154 %elif c.enable_downloads is False:
155 ${_('Downloads are disabled for this repository')}
155 ${_('Downloads are disabled for this repository')}
156 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
156 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
157 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
157 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
158 %endif
158 %endif
159 %else:
159 %else:
160 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
160 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
161 <span id="${'zip_link'}">${h.link_to('Download as zip',h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
161 <span id="${'zip_link'}">${h.link_to('Download as zip',h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
162 <span style="vertical-align: bottom">
162 <span style="vertical-align: bottom">
163 <input id="archive_subrepos" type="checkbox" name="subrepos"/> <span class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</span>
163 <input id="archive_subrepos" type="checkbox" name="subrepos"/> <span class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</span>
164 </span>
164 </span>
165 %endif
165 %endif
166 </div>
166 </div>
167 </div>
167 </div>
168 </div>
168 </div>
169 </div>
169 </div>
170 </div>
170 </div>
171
171
172 %if c.show_stats:
172 %if c.show_stats:
173 <div class="box box-right" style="min-height:455px">
173 <div class="box box-right" style="min-height:455px">
174 <!-- box / title -->
174 <!-- box / title -->
175 <div class="title">
175 <div class="title">
176 <h5>${_('Commit activity by day / author')}</h5>
176 <h5>${_('Commit activity by day / author')}</h5>
177 </div>
177 </div>
178
178
179 <div class="graph">
179 <div class="graph">
180 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
180 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
181 %if c.no_data:
181 %if c.no_data:
182 ${c.no_data_msg}
182 ${c.no_data_msg}
183 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
183 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
184 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
184 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
185 %endif
185 %endif
186 %else:
186 %else:
187 ${_('Loaded in')} ${c.stats_percentage} %
187 ${_('Loaded in')} ${c.stats_percentage} %
188 %endif
188 %endif
189 </div>
189 </div>
190 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
190 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
191 <div style="clear: both;height: 10px"></div>
191 <div style="clear: both;height: 10px"></div>
192 <div id="overview" style="width:450px;height:100px;float:left"></div>
192 <div id="overview" style="width:450px;height:100px;float:left"></div>
193
193
194 <div id="legend_data" style="clear:both;margin-top:10px;">
194 <div id="legend_data" style="clear:both;margin-top:10px;">
195 <div id="legend_container"></div>
195 <div id="legend_container"></div>
196 <div id="legend_choices">
196 <div id="legend_choices">
197 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
197 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
198 </div>
198 </div>
199 </div>
199 </div>
200 </div>
200 </div>
201 </div>
201 </div>
202 %endif
202 %endif
203
203
204 <div class="box">
204 <div class="box">
205 <div class="title">
205 <div class="title">
206 <div class="breadcrumbs">
206 <div class="breadcrumbs">
207 %if c.repo_changesets:
207 %if c.repo_changesets:
208 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
208 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
209 %else:
209 %else:
210 ${_('Quick start')}
210 ${_('Quick start')}
211 %endif
211 %endif
212 </div>
212 </div>
213 </div>
213 </div>
214 <div class="table">
214 <div class="table">
215 <div id="shortlog_data">
215 <div id="shortlog_data">
216 <%include file='../shortlog/shortlog_data.html'/>
216 <%include file='../shortlog/shortlog_data.html'/>
217 </div>
217 </div>
218 </div>
218 </div>
219 </div>
219 </div>
220
220
221 %if c.readme_data:
221 %if c.readme_data:
222 <div class="box" style="background-color: #FAFAFA">
222 <div class="box" style="background-color: #FAFAFA">
223 <div class="title">
223 <div class="title">
224 <div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
224 <div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
225 </div>
225 </div>
226 <div class="readme">
226 <div class="readme">
227 <div class="readme_box">
227 <div class="readme_box">
228 ${c.readme_data|n}
228 ${c.readme_data|n}
229 </div>
229 </div>
230 </div>
230 </div>
231 </div>
231 </div>
232 %endif
232 %endif
233
233
234 <script type="text/javascript">
234 <script type="text/javascript">
235 var clone_url = 'clone_url';
235 var clone_url = 'clone_url';
236 YUE.on(clone_url,'click',function(e){
236 YUE.on(clone_url,'click',function(e){
237 if(YUD.hasClass(clone_url,'selected')){
237 if(YUD.hasClass(clone_url,'selected')){
238 return
238 return
239 }
239 }
240 else{
240 else{
241 YUD.addClass(clone_url,'selected');
241 YUD.addClass(clone_url,'selected');
242 YUD.get(clone_url).select();
242 YUD.get(clone_url).select();
243 }
243 }
244 })
244 })
245
245
246 YUE.on('clone_by_name','click',function(e){
246 YUE.on('clone_by_name','click',function(e){
247 // show url by name and hide name button
247 // show url by name and hide name button
248 YUD.setStyle('clone_url','display','');
248 YUD.setStyle('clone_url','display','');
249 YUD.setStyle('clone_by_name','display','none');
249 YUD.setStyle('clone_by_name','display','none');
250
250
251 // hide url by id and show name button
251 // hide url by id and show name button
252 YUD.setStyle('clone_by_id','display','');
252 YUD.setStyle('clone_by_id','display','');
253 YUD.setStyle('clone_url_id','display','none');
253 YUD.setStyle('clone_url_id','display','none');
254
254
255 })
255 })
256 YUE.on('clone_by_id','click',function(e){
256 YUE.on('clone_by_id','click',function(e){
257
257
258 // show url by id and hide id button
258 // show url by id and hide id button
259 YUD.setStyle('clone_by_id','display','none');
259 YUD.setStyle('clone_by_id','display','none');
260 YUD.setStyle('clone_url_id','display','');
260 YUD.setStyle('clone_url_id','display','');
261
261
262 // hide url by name and show id button
262 // hide url by name and show id button
263 YUD.setStyle('clone_by_name','display','');
263 YUD.setStyle('clone_by_name','display','');
264 YUD.setStyle('clone_url','display','none');
264 YUD.setStyle('clone_url','display','none');
265 })
265 })
266
266
267
267
268 var tmpl_links = {};
268 var tmpl_links = {};
269 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
269 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
270 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
270 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
271 %endfor
271 %endfor
272
272
273 YUE.on(['download_options','archive_subrepos'],'change',function(e){
273 YUE.on(['download_options','archive_subrepos'],'change',function(e){
274 var sm = YUD.get('download_options');
274 var sm = YUD.get('download_options');
275 var new_cs = sm.options[sm.selectedIndex];
275 var new_cs = sm.options[sm.selectedIndex];
276
276
277 for(k in tmpl_links){
277 for(k in tmpl_links){
278 var s = YUD.get(k+'_link');
278 var s = YUD.get(k+'_link');
279 if(s){
279 if(s){
280 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
280 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
281 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
281 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
282 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
282 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
283
283
284 var url = tmpl_links[k].replace('__CS__',new_cs.value);
284 var url = tmpl_links[k].replace('__CS__',new_cs.value);
285 var subrepos = YUD.get('archive_subrepos').checked;
285 var subrepos = YUD.get('archive_subrepos').checked;
286 url = url.replace('__SUB__',subrepos);
286 url = url.replace('__SUB__',subrepos);
287 url = url.replace('__NAME__',title_tmpl);
287 url = url.replace('__NAME__',title_tmpl);
288 s.innerHTML = url
288 s.innerHTML = url
289 }
289 }
290 }
290 }
291 });
291 });
292 </script>
292 </script>
293 %if c.show_stats:
293 %if c.show_stats:
294 <script type="text/javascript">
294 <script type="text/javascript">
295 var data = ${c.trending_languages|n};
295 var data = ${c.trending_languages|n};
296 var total = 0;
296 var total = 0;
297 var no_data = true;
297 var no_data = true;
298 var tbl = document.createElement('table');
298 var tbl = document.createElement('table');
299 tbl.setAttribute('class','trending_language_tbl');
299 tbl.setAttribute('class','trending_language_tbl');
300 var cnt = 0;
300 var cnt = 0;
301
301
302 for (var i=0;i<data.length;i++){
302 for (var i=0;i<data.length;i++){
303 total += data[i][1].count;
303 total += data[i][1].count;
304 cnt += 1;
304 cnt += 1;
305 no_data = false;
305 no_data = false;
306
306
307 var hide = cnt>2;
307 var hide = cnt>2;
308 var tr = document.createElement('tr');
308 var tr = document.createElement('tr');
309 if (hide){
309 if (hide){
310 tr.setAttribute('style','display:none');
310 tr.setAttribute('style','display:none');
311 tr.setAttribute('class','stats_hidden');
311 tr.setAttribute('class','stats_hidden');
312 }
312 }
313 var k = data[i][0];
313 var k = data[i][0];
314 var obj = data[i][1];
314 var obj = data[i][1];
315 var percentage = Math.round((obj.count/total*100),2);
315 var percentage = Math.round((obj.count/total*100),2);
316
316
317 var td1 = document.createElement('td');
317 var td1 = document.createElement('td');
318 td1.width = 150;
318 td1.width = 150;
319 var trending_language_label = document.createElement('div');
319 var trending_language_label = document.createElement('div');
320 trending_language_label.innerHTML = obj.desc+" ("+k+")";
320 trending_language_label.innerHTML = obj.desc+" ("+k+")";
321 td1.appendChild(trending_language_label);
321 td1.appendChild(trending_language_label);
322
322
323 var td2 = document.createElement('td');
323 var td2 = document.createElement('td');
324 td2.setAttribute('style','padding-right:14px !important');
324 td2.setAttribute('style','padding-right:14px !important');
325 var trending_language = document.createElement('div');
325 var trending_language = document.createElement('div');
326 var nr_files = obj.count+" ${_('files')}";
326 var nr_files = obj.count+" ${_('files')}";
327
327
328 trending_language.title = k+" "+nr_files;
328 trending_language.title = k+" "+nr_files;
329
329
330 if (percentage>22){
330 if (percentage>22){
331 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
331 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
332 }
332 }
333 else{
333 else{
334 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
334 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
335 }
335 }
336
336
337 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
337 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
338 trending_language.style.width=percentage+"%";
338 trending_language.style.width=percentage+"%";
339 td2.appendChild(trending_language);
339 td2.appendChild(trending_language);
340
340
341 tr.appendChild(td1);
341 tr.appendChild(td1);
342 tr.appendChild(td2);
342 tr.appendChild(td2);
343 tbl.appendChild(tr);
343 tbl.appendChild(tr);
344 if(cnt == 3){
344 if(cnt == 3){
345 var show_more = document.createElement('tr');
345 var show_more = document.createElement('tr');
346 var td = document.createElement('td');
346 var td = document.createElement('td');
347 lnk = document.createElement('a');
347 lnk = document.createElement('a');
348
348
349 lnk.href='#';
349 lnk.href='#';
350 lnk.innerHTML = "${_('show more')}";
350 lnk.innerHTML = "${_('show more')}";
351 lnk.id='code_stats_show_more';
351 lnk.id='code_stats_show_more';
352 td.appendChild(lnk);
352 td.appendChild(lnk);
353
353
354 show_more.appendChild(td);
354 show_more.appendChild(td);
355 show_more.appendChild(document.createElement('td'));
355 show_more.appendChild(document.createElement('td'));
356 tbl.appendChild(show_more);
356 tbl.appendChild(show_more);
357 }
357 }
358
358
359 }
359 }
360
360
361 YUD.get('lang_stats').appendChild(tbl);
361 YUD.get('lang_stats').appendChild(tbl);
362 YUE.on('code_stats_show_more','click',function(){
362 YUE.on('code_stats_show_more','click',function(){
363 l = YUD.getElementsByClassName('stats_hidden')
363 l = YUD.getElementsByClassName('stats_hidden')
364 for (e in l){
364 for (e in l){
365 YUD.setStyle(l[e],'display','');
365 YUD.setStyle(l[e],'display','');
366 };
366 };
367 YUD.setStyle(YUD.get('code_stats_show_more'),
367 YUD.setStyle(YUD.get('code_stats_show_more'),
368 'display','none');
368 'display','none');
369 });
369 });
370 </script>
370 </script>
371 <script type="text/javascript">
371 <script type="text/javascript">
372 /**
372 /**
373 * Plots summary graph
373 * Plots summary graph
374 *
374 *
375 * @class SummaryPlot
375 * @class SummaryPlot
376 * @param {from} initial from for detailed graph
376 * @param {from} initial from for detailed graph
377 * @param {to} initial to for detailed graph
377 * @param {to} initial to for detailed graph
378 * @param {dataset}
378 * @param {dataset}
379 * @param {overview_dataset}
379 * @param {overview_dataset}
380 */
380 */
381 function SummaryPlot(from,to,dataset,overview_dataset) {
381 function SummaryPlot(from,to,dataset,overview_dataset) {
382 var initial_ranges = {
382 var initial_ranges = {
383 "xaxis":{
383 "xaxis":{
384 "from":from,
384 "from":from,
385 "to":to,
385 "to":to,
386 },
386 },
387 };
387 };
388 var dataset = dataset;
388 var dataset = dataset;
389 var overview_dataset = [overview_dataset];
389 var overview_dataset = [overview_dataset];
390 var choiceContainer = YUD.get("legend_choices");
390 var choiceContainer = YUD.get("legend_choices");
391 var choiceContainerTable = YUD.get("legend_choices_tables");
391 var choiceContainerTable = YUD.get("legend_choices_tables");
392 var plotContainer = YUD.get('commit_history');
392 var plotContainer = YUD.get('commit_history');
393 var overviewContainer = YUD.get('overview');
393 var overviewContainer = YUD.get('overview');
394
394
395 var plot_options = {
395 var plot_options = {
396 bars: {show:true,align:'center',lineWidth:4},
396 bars: {show:true,align:'center',lineWidth:4},
397 legend: {show:true, container:"legend_container"},
397 legend: {show:true, container:"legend_container"},
398 points: {show:true,radius:0,fill:false},
398 points: {show:true,radius:0,fill:false},
399 yaxis: {tickDecimals:0,},
399 yaxis: {tickDecimals:0,},
400 xaxis: {
400 xaxis: {
401 mode: "time",
401 mode: "time",
402 timeformat: "%d/%m",
402 timeformat: "%d/%m",
403 min:from,
403 min:from,
404 max:to,
404 max:to,
405 },
405 },
406 grid: {
406 grid: {
407 hoverable: true,
407 hoverable: true,
408 clickable: true,
408 clickable: true,
409 autoHighlight:true,
409 autoHighlight:true,
410 color: "#999"
410 color: "#999"
411 },
411 },
412 //selection: {mode: "x"}
412 //selection: {mode: "x"}
413 };
413 };
414 var overview_options = {
414 var overview_options = {
415 legend:{show:false},
415 legend:{show:false},
416 bars: {show:true,barWidth: 2,},
416 bars: {show:true,barWidth: 2,},
417 shadowSize: 0,
417 shadowSize: 0,
418 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
418 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
419 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
419 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
420 grid: {color: "#999",},
420 grid: {color: "#999",},
421 selection: {mode: "x"}
421 selection: {mode: "x"}
422 };
422 };
423
423
424 /**
424 /**
425 *get dummy data needed in few places
425 *get dummy data needed in few places
426 */
426 */
427 function getDummyData(label){
427 function getDummyData(label){
428 return {"label":label,
428 return {"label":label,
429 "data":[{"time":0,
429 "data":[{"time":0,
430 "commits":0,
430 "commits":0,
431 "added":0,
431 "added":0,
432 "changed":0,
432 "changed":0,
433 "removed":0,
433 "removed":0,
434 }],
434 }],
435 "schema":["commits"],
435 "schema":["commits"],
436 "color":'#ffffff',
436 "color":'#ffffff',
437 }
437 }
438 }
438 }
439
439
440 /**
440 /**
441 * generate checkboxes accordindly to data
441 * generate checkboxes accordindly to data
442 * @param keys
442 * @param keys
443 * @returns
443 * @returns
444 */
444 */
445 function generateCheckboxes(data) {
445 function generateCheckboxes(data) {
446 //append checkboxes
446 //append checkboxes
447 var i = 0;
447 var i = 0;
448 choiceContainerTable.innerHTML = '';
448 choiceContainerTable.innerHTML = '';
449 for(var pos in data) {
449 for(var pos in data) {
450
450
451 data[pos].color = i;
451 data[pos].color = i;
452 i++;
452 i++;
453 if(data[pos].label != ''){
453 if(data[pos].label != ''){
454 choiceContainerTable.innerHTML += '<tr><td>'+
454 choiceContainerTable.innerHTML += '<tr><td>'+
455 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
455 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
456 +data[pos].label+
456 +data[pos].label+
457 '</td></tr>';
457 '</td></tr>';
458 }
458 }
459 }
459 }
460 }
460 }
461
461
462 /**
462 /**
463 * ToolTip show
463 * ToolTip show
464 */
464 */
465 function showTooltip(x, y, contents) {
465 function showTooltip(x, y, contents) {
466 var div=document.getElementById('tooltip');
466 var div=document.getElementById('tooltip');
467 if(!div) {
467 if(!div) {
468 div = document.createElement('div');
468 div = document.createElement('div');
469 div.id="tooltip";
469 div.id="tooltip";
470 div.style.position="absolute";
470 div.style.position="absolute";
471 div.style.border='1px solid #fdd';
471 div.style.border='1px solid #fdd';
472 div.style.padding='2px';
472 div.style.padding='2px';
473 div.style.backgroundColor='#fee';
473 div.style.backgroundColor='#fee';
474 document.body.appendChild(div);
474 document.body.appendChild(div);
475 }
475 }
476 YUD.setStyle(div, 'opacity', 0);
476 YUD.setStyle(div, 'opacity', 0);
477 div.innerHTML = contents;
477 div.innerHTML = contents;
478 div.style.top=(y + 5) + "px";
478 div.style.top=(y + 5) + "px";
479 div.style.left=(x + 5) + "px";
479 div.style.left=(x + 5) + "px";
480
480
481 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
481 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
482 anim.animate();
482 anim.animate();
483 }
483 }
484
484
485 /**
485 /**
486 * This function will detect if selected period has some changesets
486 * This function will detect if selected period has some changesets
487 for this user if it does this data is then pushed for displaying
487 for this user if it does this data is then pushed for displaying
488 Additionally it will only display users that are selected by the checkbox
488 Additionally it will only display users that are selected by the checkbox
489 */
489 */
490 function getDataAccordingToRanges(ranges) {
490 function getDataAccordingToRanges(ranges) {
491
491
492 var data = [];
492 var data = [];
493 var new_dataset = {};
493 var new_dataset = {};
494 var keys = [];
494 var keys = [];
495 var max_commits = 0;
495 var max_commits = 0;
496 for(var key in dataset){
496 for(var key in dataset){
497
497
498 for(var ds in dataset[key].data){
498 for(var ds in dataset[key].data){
499 commit_data = dataset[key].data[ds];
499 commit_data = dataset[key].data[ds];
500 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
500 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
501
501
502 if(new_dataset[key] === undefined){
502 if(new_dataset[key] === undefined){
503 new_dataset[key] = {data:[],schema:["commits"],label:key};
503 new_dataset[key] = {data:[],schema:["commits"],label:key};
504 }
504 }
505 new_dataset[key].data.push(commit_data);
505 new_dataset[key].data.push(commit_data);
506 }
506 }
507 }
507 }
508 if (new_dataset[key] !== undefined){
508 if (new_dataset[key] !== undefined){
509 data.push(new_dataset[key]);
509 data.push(new_dataset[key]);
510 }
510 }
511 }
511 }
512
512
513 if (data.length > 0){
513 if (data.length > 0){
514 return data;
514 return data;
515 }
515 }
516 else{
516 else{
517 //just return dummy data for graph to plot itself
517 //just return dummy data for graph to plot itself
518 return [getDummyData('')];
518 return [getDummyData('')];
519 }
519 }
520 }
520 }
521
521
522 /**
522 /**
523 * redraw using new checkbox data
523 * redraw using new checkbox data
524 */
524 */
525 function plotchoiced(e,args){
525 function plotchoiced(e,args){
526 var cur_data = args[0];
526 var cur_data = args[0];
527 var cur_ranges = args[1];
527 var cur_ranges = args[1];
528
528
529 var new_data = [];
529 var new_data = [];
530 var inputs = choiceContainer.getElementsByTagName("input");
530 var inputs = choiceContainer.getElementsByTagName("input");
531
531
532 //show only checked labels
532 //show only checked labels
533 for(var i=0; i<inputs.length; i++) {
533 for(var i=0; i<inputs.length; i++) {
534 var checkbox_key = inputs[i].name;
534 var checkbox_key = inputs[i].name;
535
535
536 if(inputs[i].checked){
536 if(inputs[i].checked){
537 for(var d in cur_data){
537 for(var d in cur_data){
538 if(cur_data[d].label == checkbox_key){
538 if(cur_data[d].label == checkbox_key){
539 new_data.push(cur_data[d]);
539 new_data.push(cur_data[d]);
540 }
540 }
541 }
541 }
542 }
542 }
543 else{
543 else{
544 //push dummy data to not hide the label
544 //push dummy data to not hide the label
545 new_data.push(getDummyData(checkbox_key));
545 new_data.push(getDummyData(checkbox_key));
546 }
546 }
547 }
547 }
548
548
549 var new_options = YAHOO.lang.merge(plot_options, {
549 var new_options = YAHOO.lang.merge(plot_options, {
550 xaxis: {
550 xaxis: {
551 min: cur_ranges.xaxis.from,
551 min: cur_ranges.xaxis.from,
552 max: cur_ranges.xaxis.to,
552 max: cur_ranges.xaxis.to,
553 mode:"time",
553 mode:"time",
554 timeformat: "%d/%m",
554 timeformat: "%d/%m",
555 },
555 },
556 });
556 });
557 if (!new_data){
557 if (!new_data){
558 new_data = [[0,1]];
558 new_data = [[0,1]];
559 }
559 }
560 // do the zooming
560 // do the zooming
561 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
561 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
562
562
563 plot.subscribe("plotselected", plotselected);
563 plot.subscribe("plotselected", plotselected);
564
564
565 //resubscribe plothover
565 //resubscribe plothover
566 plot.subscribe("plothover", plothover);
566 plot.subscribe("plothover", plothover);
567
567
568 // don't fire event on the overview to prevent eternal loop
568 // don't fire event on the overview to prevent eternal loop
569 overview.setSelection(cur_ranges, true);
569 overview.setSelection(cur_ranges, true);
570
570
571 }
571 }
572
572
573 /**
573 /**
574 * plot only selected items from overview
574 * plot only selected items from overview
575 * @param ranges
575 * @param ranges
576 * @returns
576 * @returns
577 */
577 */
578 function plotselected(ranges,cur_data) {
578 function plotselected(ranges,cur_data) {
579 //updates the data for new plot
579 //updates the data for new plot
580 var data = getDataAccordingToRanges(ranges);
580 var data = getDataAccordingToRanges(ranges);
581 generateCheckboxes(data);
581 generateCheckboxes(data);
582
582
583 var new_options = YAHOO.lang.merge(plot_options, {
583 var new_options = YAHOO.lang.merge(plot_options, {
584 xaxis: {
584 xaxis: {
585 min: ranges.xaxis.from,
585 min: ranges.xaxis.from,
586 max: ranges.xaxis.to,
586 max: ranges.xaxis.to,
587 mode:"time",
587 mode:"time",
588 timeformat: "%d/%m",
588 timeformat: "%d/%m",
589 },
589 },
590 });
590 });
591 // do the zooming
591 // do the zooming
592 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
592 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
593
593
594 plot.subscribe("plotselected", plotselected);
594 plot.subscribe("plotselected", plotselected);
595
595
596 //resubscribe plothover
596 //resubscribe plothover
597 plot.subscribe("plothover", plothover);
597 plot.subscribe("plothover", plothover);
598
598
599 // don't fire event on the overview to prevent eternal loop
599 // don't fire event on the overview to prevent eternal loop
600 overview.setSelection(ranges, true);
600 overview.setSelection(ranges, true);
601
601
602 //resubscribe choiced
602 //resubscribe choiced
603 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
603 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
604 }
604 }
605
605
606 var previousPoint = null;
606 var previousPoint = null;
607
607
608 function plothover(o) {
608 function plothover(o) {
609 var pos = o.pos;
609 var pos = o.pos;
610 var item = o.item;
610 var item = o.item;
611
611
612 //YUD.get("x").innerHTML = pos.x.toFixed(2);
612 //YUD.get("x").innerHTML = pos.x.toFixed(2);
613 //YUD.get("y").innerHTML = pos.y.toFixed(2);
613 //YUD.get("y").innerHTML = pos.y.toFixed(2);
614 if (item) {
614 if (item) {
615 if (previousPoint != item.datapoint) {
615 if (previousPoint != item.datapoint) {
616 previousPoint = item.datapoint;
616 previousPoint = item.datapoint;
617
617
618 var tooltip = YUD.get("tooltip");
618 var tooltip = YUD.get("tooltip");
619 if(tooltip) {
619 if(tooltip) {
620 tooltip.parentNode.removeChild(tooltip);
620 tooltip.parentNode.removeChild(tooltip);
621 }
621 }
622 var x = item.datapoint.x.toFixed(2);
622 var x = item.datapoint.x.toFixed(2);
623 var y = item.datapoint.y.toFixed(2);
623 var y = item.datapoint.y.toFixed(2);
624
624
625 if (!item.series.label){
625 if (!item.series.label){
626 item.series.label = 'commits';
626 item.series.label = 'commits';
627 }
627 }
628 var d = new Date(x*1000);
628 var d = new Date(x*1000);
629 var fd = d.toDateString()
629 var fd = d.toDateString()
630 var nr_commits = parseInt(y);
630 var nr_commits = parseInt(y);
631
631
632 var cur_data = dataset[item.series.label].data[item.dataIndex];
632 var cur_data = dataset[item.series.label].data[item.dataIndex];
633 var added = cur_data.added;
633 var added = cur_data.added;
634 var changed = cur_data.changed;
634 var changed = cur_data.changed;
635 var removed = cur_data.removed;
635 var removed = cur_data.removed;
636
636
637 var nr_commits_suffix = " ${_('commits')} ";
637 var nr_commits_suffix = " ${_('commits')} ";
638 var added_suffix = " ${_('files added')} ";
638 var added_suffix = " ${_('files added')} ";
639 var changed_suffix = " ${_('files changed')} ";
639 var changed_suffix = " ${_('files changed')} ";
640 var removed_suffix = " ${_('files removed')} ";
640 var removed_suffix = " ${_('files removed')} ";
641
641
642
642
643 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
643 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
644 if(added==1){added_suffix=" ${_('file added')} ";}
644 if(added==1){added_suffix=" ${_('file added')} ";}
645 if(changed==1){changed_suffix=" ${_('file changed')} ";}
645 if(changed==1){changed_suffix=" ${_('file changed')} ";}
646 if(removed==1){removed_suffix=" ${_('file removed')} ";}
646 if(removed==1){removed_suffix=" ${_('file removed')} ";}
647
647
648 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
648 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
649 +'<br/>'+
649 +'<br/>'+
650 nr_commits + nr_commits_suffix+'<br/>'+
650 nr_commits + nr_commits_suffix+'<br/>'+
651 added + added_suffix +'<br/>'+
651 added + added_suffix +'<br/>'+
652 changed + changed_suffix + '<br/>'+
652 changed + changed_suffix + '<br/>'+
653 removed + removed_suffix + '<br/>');
653 removed + removed_suffix + '<br/>');
654 }
654 }
655 }
655 }
656 else {
656 else {
657 var tooltip = YUD.get("tooltip");
657 var tooltip = YUD.get("tooltip");
658
658
659 if(tooltip) {
659 if(tooltip) {
660 tooltip.parentNode.removeChild(tooltip);
660 tooltip.parentNode.removeChild(tooltip);
661 }
661 }
662 previousPoint = null;
662 previousPoint = null;
663 }
663 }
664 }
664 }
665
665
666 /**
666 /**
667 * MAIN EXECUTION
667 * MAIN EXECUTION
668 */
668 */
669
669
670 var data = getDataAccordingToRanges(initial_ranges);
670 var data = getDataAccordingToRanges(initial_ranges);
671 generateCheckboxes(data);
671 generateCheckboxes(data);
672
672
673 //main plot
673 //main plot
674 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
674 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
675
675
676 //overview
676 //overview
677 var overview = YAHOO.widget.Flot(overviewContainer,
677 var overview = YAHOO.widget.Flot(overviewContainer,
678 overview_dataset, overview_options);
678 overview_dataset, overview_options);
679
679
680 //show initial selection on overview
680 //show initial selection on overview
681 overview.setSelection(initial_ranges);
681 overview.setSelection(initial_ranges);
682
682
683 plot.subscribe("plotselected", plotselected);
683 plot.subscribe("plotselected", plotselected);
684 plot.subscribe("plothover", plothover)
684 plot.subscribe("plothover", plothover)
685
685
686 overview.subscribe("plotselected", function (ranges) {
686 overview.subscribe("plotselected", function (ranges) {
687 plot.setSelection(ranges);
687 plot.setSelection(ranges);
688 });
688 });
689
689
690 // user choices on overview
690 // user choices on overview
691 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
691 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
692 }
692 }
693 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
693 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
694 </script>
694 </script>
695 %endif
695 %endif
696
696
697 </%def>
697 </%def>
General Comments 0
You need to be logged in to leave comments. Login now