##// END OF EJS Templates
Removed all string concat for exchange of ''.join()
marcink -
r1359:54fc83f2 beta
parent child Browse files
Show More
@@ -1,116 +1,116 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.feed
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Feed controller for rhodecode
7 7
8 8 :created_on: Apr 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27
28 28 from pylons import url, response, tmpl_context as c
29 29 from pylons.i18n.translation import _
30 30
31 31 from rhodecode.lib import safe_unicode
32 32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 33 from rhodecode.lib.base import BaseRepoController
34 34
35 35 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class FeedController(BaseRepoController):
41 41
42 42 @LoginRequired(api_access=True)
43 43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 44 'repository.admin')
45 45 def __before__(self):
46 46 super(FeedController, self).__before__()
47 47 #common values for feeds
48 48 self.description = _('Changes on %s repository')
49 49 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
50 50 self.language = 'en-us'
51 51 self.ttl = "5"
52 52 self.feed_nr = 10
53 53
54 54 def __changes(self, cs):
55 changes = ''
55 changes = []
56 56
57 57 a = [safe_unicode(n.path) for n in cs.added]
58 58 if a:
59 changes += '\nA ' + '\nA '.join(a)
59 changes.append('\nA ' + '\nA '.join(a))
60 60
61 61 m = [safe_unicode(n.path) for n in cs.changed]
62 62 if m:
63 changes += '\nM ' + '\nM '.join(m)
63 changes.append('\nM ' + '\nM '.join(m))
64 64
65 65 d = [safe_unicode(n.path) for n in cs.removed]
66 66 if d:
67 changes += '\nD ' + '\nD '.join(d)
67 changes.append('\nD ' + '\nD '.join(d))
68 68
69 changes += '</pre>'
69 changes.append('</pre>')
70 70
71 return changes
71 return ''.join(changes)
72 72
73 73 def atom(self, repo_name):
74 74 """Produce an atom-1.0 feed via feedgenerator module"""
75 75 feed = Atom1Feed(title=self.title % repo_name,
76 76 link=url('summary_home', repo_name=repo_name,
77 77 qualified=True),
78 78 description=self.description % repo_name,
79 79 language=self.language,
80 80 ttl=self.ttl)
81
81 desc_msg = []
82 82 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
83 desc = '%s - %s<br/><pre>' % (cs.author, cs.date)
84 desc += self.__changes(cs)
83 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
84 desc_msg.append(self.__changes(cs))
85 85
86 86 feed.add_item(title=cs.message,
87 87 link=url('changeset_home', repo_name=repo_name,
88 88 revision=cs.raw_id, qualified=True),
89 89 author_name=cs.author,
90 description=desc)
90 description=''.join(desc_msg))
91 91
92 92 response.content_type = feed.mime_type
93 93 return feed.writeString('utf-8')
94 94
95 95 def rss(self, repo_name):
96 96 """Produce an rss2 feed via feedgenerator module"""
97 97 feed = Rss201rev2Feed(title=self.title % repo_name,
98 98 link=url('summary_home', repo_name=repo_name,
99 99 qualified=True),
100 100 description=self.description % repo_name,
101 101 language=self.language,
102 102 ttl=self.ttl)
103
103 desc_msg = []
104 104 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
105 desc = '%s - %s<br/><pre>' % (cs.author, cs.date)
106 desc += self.__changes(cs)
105 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
106 desc_msg.append(self.__changes(cs))
107 107
108 108 feed.add_item(title=cs.message,
109 109 link=url('changeset_home', repo_name=repo_name,
110 110 revision=cs.raw_id, qualified=True),
111 111 author_name=cs.author,
112 description=desc,
112 description=''.join(desc_msg),
113 113 )
114 114
115 115 response.content_type = feed.mime_type
116 116 return feed.writeString('utf-8')
@@ -1,695 +1,695 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10
11 11 from datetime import datetime
12 12 from pygments.formatters import HtmlFormatter
13 13 from pygments import highlight as code_highlight
14 14 from pylons import url, request, config
15 15 from pylons.i18n.translation import _, ungettext
16 16
17 17 from webhelpers.html import literal, HTML, escape
18 18 from webhelpers.html.tools import *
19 19 from webhelpers.html.builder import make_tag
20 20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 23 password, textarea, title, ul, xml_declaration, radio
24 24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 25 mail_to, strip_links, strip_tags, tag_re
26 26 from webhelpers.number import format_byte_size, format_bit_size
27 27 from webhelpers.pylonslib import Flash as _Flash
28 28 from webhelpers.pylonslib.secure_form import secure_form
29 29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 32 from webhelpers.date import time_ago_in_words
33 33 from webhelpers.paginate import Page
34 34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 35 convert_boolean_attrs, NotGiven
36 36
37 37 from vcs.utils.annotate import annotate_highlight
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib import str2bool, safe_unicode
40 40
41 41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 42 """
43 43 Reset button
44 44 """
45 45 _set_input_attrs(attrs, type, name, value)
46 46 _set_id_attr(attrs, id, name)
47 47 convert_boolean_attrs(attrs, ["disabled"])
48 48 return HTML.input(**attrs)
49 49
50 50 reset = _reset
51 51
52 52
53 53 def get_token():
54 54 """Return the current authentication token, creating one if one doesn't
55 55 already exist.
56 56 """
57 57 token_key = "_authentication_token"
58 58 from pylons import session
59 59 if not token_key in session:
60 60 try:
61 61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 62 except AttributeError: # Python < 2.4
63 63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 64 session[token_key] = token
65 65 if hasattr(session, 'save'):
66 66 session.save()
67 67 return session[token_key]
68 68
69 69 class _GetError(object):
70 70 """Get error from form_errors, and represent it as span wrapped error
71 71 message
72 72
73 73 :param field_name: field to fetch errors for
74 74 :param form_errors: form errors dict
75 75 """
76 76
77 77 def __call__(self, field_name, form_errors):
78 78 tmpl = """<span class="error_msg">%s</span>"""
79 79 if form_errors and form_errors.has_key(field_name):
80 80 return literal(tmpl % form_errors.get(field_name))
81 81
82 82 get_error = _GetError()
83 83
84 84 class _ToolTip(object):
85 85
86 86 def __call__(self, tooltip_title, trim_at=50):
87 87 """Special function just to wrap our text into nice formatted
88 88 autowrapped text
89 89
90 90 :param tooltip_title:
91 91 """
92 92
93 93 return escape(tooltip_title)
94 94
95 95 def activate(self):
96 96 """Adds tooltip mechanism to the given Html all tooltips have to have
97 97 set class `tooltip` and set attribute `tooltip_title`.
98 98 Then a tooltip will be generated based on that. All with yui js tooltip
99 99 """
100 100
101 101 js = '''
102 102 YAHOO.util.Event.onDOMReady(function(){
103 103 function toolTipsId(){
104 104 var ids = [];
105 105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106 106
107 107 for (var i = 0; i < tts.length; i++) {
108 108 //if element doesn't not have and id autogenerate one for tooltip
109 109
110 110 if (!tts[i].id){
111 111 tts[i].id='tt'+i*100;
112 112 }
113 113 ids.push(tts[i].id);
114 114 }
115 115 return ids
116 116 };
117 117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
119 119 monitorresize:false,
120 120 xyoffset :[0,0],
121 121 autodismissdelay:300000,
122 122 hidedelay:5,
123 123 showdelay:20,
124 124 });
125 125
126 126 });
127 127 '''
128 128 return literal(js)
129 129
130 130 tooltip = _ToolTip()
131 131
132 132 class _FilesBreadCrumbs(object):
133 133
134 134 def __call__(self, repo_name, rev, paths):
135 135 if isinstance(paths, str):
136 136 paths = safe_unicode(paths)
137 137 url_l = [link_to(repo_name, url('files_home',
138 138 repo_name=repo_name,
139 139 revision=rev, f_path=''))]
140 140 paths_l = paths.split('/')
141 141 for cnt, p in enumerate(paths_l):
142 142 if p != '':
143 143 url_l.append(link_to(p, url('files_home',
144 144 repo_name=repo_name,
145 145 revision=rev,
146 146 f_path='/'.join(paths_l[:cnt + 1]))))
147 147
148 148 return literal('/'.join(url_l))
149 149
150 150 files_breadcrumbs = _FilesBreadCrumbs()
151 151
152 152 class CodeHtmlFormatter(HtmlFormatter):
153 153 """My code Html Formatter for source codes
154 154 """
155 155
156 156 def wrap(self, source, outfile):
157 157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158 158
159 159 def _wrap_code(self, source):
160 160 for cnt, it in enumerate(source):
161 161 i, t = it
162 162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 163 yield i, t
164 164
165 165 def _wrap_tablelinenos(self, inner):
166 166 dummyoutfile = StringIO.StringIO()
167 167 lncount = 0
168 168 for t, line in inner:
169 169 if t:
170 170 lncount += 1
171 171 dummyoutfile.write(line)
172 172
173 173 fl = self.linenostart
174 174 mw = len(str(lncount + fl - 1))
175 175 sp = self.linenospecial
176 176 st = self.linenostep
177 177 la = self.lineanchors
178 178 aln = self.anchorlinenos
179 179 nocls = self.noclasses
180 180 if sp:
181 181 lines = []
182 182
183 183 for i in range(fl, fl + lncount):
184 184 if i % st == 0:
185 185 if i % sp == 0:
186 186 if aln:
187 187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 188 (la, i, mw, i))
189 189 else:
190 190 lines.append('<span class="special">%*d</span>' % (mw, i))
191 191 else:
192 192 if aln:
193 193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 194 else:
195 195 lines.append('%*d' % (mw, i))
196 196 else:
197 197 lines.append('')
198 198 ls = '\n'.join(lines)
199 199 else:
200 200 lines = []
201 201 for i in range(fl, fl + lncount):
202 202 if i % st == 0:
203 203 if aln:
204 204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 205 else:
206 206 lines.append('%*d' % (mw, i))
207 207 else:
208 208 lines.append('')
209 209 ls = '\n'.join(lines)
210 210
211 211 # in case you wonder about the seemingly redundant <div> here: since the
212 212 # content in the other cell also is wrapped in a div, some browsers in
213 213 # some configurations seem to mess up the formatting...
214 214 if nocls:
215 215 yield 0, ('<table class="%stable">' % self.cssclass +
216 216 '<tr><td><div class="linenodiv" '
217 217 'style="background-color: #f0f0f0; padding-right: 10px">'
218 218 '<pre style="line-height: 125%">' +
219 219 ls + '</pre></div></td><td id="hlcode" class="code">')
220 220 else:
221 221 yield 0, ('<table class="%stable">' % self.cssclass +
222 222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 223 ls + '</pre></div></td><td id="hlcode" class="code">')
224 224 yield 0, dummyoutfile.getvalue()
225 225 yield 0, '</td></tr></table>'
226 226
227 227
228 228 def pygmentize(filenode, **kwargs):
229 229 """pygmentize function using pygments
230 230
231 231 :param filenode:
232 232 """
233 233
234 234 return literal(code_highlight(filenode.content,
235 235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236 236
237 237 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 238 """pygmentize function for annotation
239 239
240 240 :param filenode:
241 241 """
242 242
243 243 color_dict = {}
244 244 def gen_color(n=10000):
245 245 """generator for getting n of evenly distributed colors using
246 246 hsv color and golden ratio. It always return same order of colors
247 247
248 248 :returns: RGB tuple
249 249 """
250 250 import colorsys
251 251 golden_ratio = 0.618033988749895
252 252 h = 0.22717784590367374
253 253
254 254 for _ in xrange(n):
255 255 h += golden_ratio
256 256 h %= 1
257 257 HSV_tuple = [h, 0.95, 0.95]
258 258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260 260
261 261 cgenerator = gen_color()
262 262
263 263 def get_color_string(cs):
264 264 if color_dict.has_key(cs):
265 265 col = color_dict[cs]
266 266 else:
267 267 col = color_dict[cs] = cgenerator.next()
268 268 return "color: rgb(%s)! important;" % (', '.join(col))
269 269
270 270 def url_func(repo_name):
271 271
272 272 def _url_func(changeset):
273 273 author = changeset.author
274 274 date = changeset.date
275 275 message = tooltip(changeset.message)
276 276
277 277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
278 278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
279 279 "</b> %s<br/></div>")
280 280
281 281 tooltip_html = tooltip_html % (author, date, message)
282 282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
283 283 short_id(changeset.raw_id))
284 284 uri = link_to(
285 285 lnk_format,
286 286 url('changeset_home', repo_name=repo_name,
287 287 revision=changeset.raw_id),
288 288 style=get_color_string(changeset.raw_id),
289 289 class_='tooltip',
290 290 title=tooltip_html
291 291 )
292 292
293 293 uri += '\n'
294 294 return uri
295 295 return _url_func
296 296
297 297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
298 298
299 299 def get_changeset_safe(repo, rev):
300 300 from vcs.backends.base import BaseRepository
301 301 from vcs.exceptions import RepositoryError
302 302 if not isinstance(repo, BaseRepository):
303 303 raise Exception('You must pass an Repository '
304 304 'object as first argument got %s', type(repo))
305 305
306 306 try:
307 307 cs = repo.get_changeset(rev)
308 308 except RepositoryError:
309 309 from rhodecode.lib.utils import EmptyChangeset
310 310 cs = EmptyChangeset()
311 311 return cs
312 312
313 313
314 314 def is_following_repo(repo_name, user_id):
315 315 from rhodecode.model.scm import ScmModel
316 316 return ScmModel().is_following_repo(repo_name, user_id)
317 317
318 318 flash = _Flash()
319 319
320 320 #==============================================================================
321 321 # SCM FILTERS available via h.
322 322 #==============================================================================
323 323 from vcs.utils import author_name, author_email
324 324 from rhodecode.lib import credentials_hidder, age as _age
325 325
326 326 age = lambda x:_age(x)
327 327 capitalize = lambda x: x.capitalize()
328 328 email = author_email
329 329 email_or_none = lambda x: email(x) if email(x) != x else None
330 330 person = lambda x: author_name(x)
331 331 short_id = lambda x: x[:12]
332 332 hide_credentials = lambda x: ''.join(credentials_hidder(x))
333 333
334 334 def bool2icon(value):
335 335 """Returns True/False values represented as small html image of true/false
336 336 icons
337 337
338 338 :param value: bool value
339 339 """
340 340
341 341 if value is True:
342 342 return HTML.tag('img', src=url("/images/icons/accept.png"),
343 343 alt=_('True'))
344 344
345 345 if value is False:
346 346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
347 347 alt=_('False'))
348 348
349 349 return value
350 350
351 351
352 352 def action_parser(user_log, feed=False):
353 353 """This helper will action_map the specified string action into translated
354 354 fancy names with icons and links
355 355
356 356 :param user_log: user log instance
357 357 :param feed: use output for feeds (no html and fancy icons)
358 358 """
359 359
360 360 action = user_log.action
361 361 action_params = ' '
362 362
363 363 x = action.split(':')
364 364
365 365 if len(x) > 1:
366 366 action, action_params = x
367 367
368 368 def get_cs_links():
369 369 revs_limit = 5 #display this amount always
370 370 revs_top_limit = 50 #show upto this amount of changesets hidden
371 371 revs = action_params.split(',')
372 372 repo_name = user_log.repository.repo_name
373 373
374 374 from rhodecode.model.scm import ScmModel
375 375 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
376 376 invalidation_list=[])
377 377
378 378 message = lambda rev: get_changeset_safe(repo, rev).message
379
380 cs_links = " " + ', '.join ([link_to(rev,
379 cs_links = []
380 cs_links.append(" " + ', '.join ([link_to(rev,
381 381 url('changeset_home',
382 382 repo_name=repo_name,
383 383 revision=rev), title=tooltip(message(rev)),
384 class_='tooltip') for rev in revs[:revs_limit] ])
384 class_='tooltip') for rev in revs[:revs_limit] ]))
385 385
386 386 compare_view = (' <div class="compare_view tooltip" title="%s">'
387 387 '<a href="%s">%s</a> '
388 388 '</div>' % (_('Show all combined changesets %s->%s' \
389 389 % (revs[0], revs[-1])),
390 390 url('changeset_home', repo_name=repo_name,
391 391 revision='%s...%s' % (revs[0], revs[-1])
392 392 ),
393 393 _('compare view'))
394 394 )
395 395
396 396 if len(revs) > revs_limit:
397 397 uniq_id = revs[0]
398 398 html_tmpl = ('<span> %s '
399 399 '<a class="show_more" id="_%s" href="#more">%s</a> '
400 400 '%s</span>')
401 401 if not feed:
402 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
402 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
403 403 % (len(revs) - revs_limit),
404 _('revisions'))
404 _('revisions')))
405 405
406 406 if not feed:
407 407 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
408 408 else:
409 409 html_tmpl = '<span id="%s"> %s </span>'
410 410
411 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
411 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
412 412 url('changeset_home',
413 413 repo_name=repo_name, revision=rev),
414 414 title=message(rev), class_='tooltip')
415 for rev in revs[revs_limit:revs_top_limit]]))
415 for rev in revs[revs_limit:revs_top_limit]])))
416 416 if len(revs) > 1:
417 cs_links += compare_view
418 return cs_links
417 cs_links.append(compare_view)
418 return ''.join(cs_links)
419 419
420 420 def get_fork_name():
421 421 repo_name = action_params
422 422 return _('fork name ') + str(link_to(action_params, url('summary_home',
423 423 repo_name=repo_name,)))
424 424
425 425 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
426 426 'user_created_repo':(_('[created] repository'), None),
427 427 'user_forked_repo':(_('[forked] repository'), get_fork_name),
428 428 'user_updated_repo':(_('[updated] repository'), None),
429 429 'admin_deleted_repo':(_('[delete] repository'), None),
430 430 'admin_created_repo':(_('[created] repository'), None),
431 431 'admin_forked_repo':(_('[forked] repository'), None),
432 432 'admin_updated_repo':(_('[updated] repository'), None),
433 433 'push':(_('[pushed] into'), get_cs_links),
434 434 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
435 435 'push_remote':(_('[pulled from remote] into'), get_cs_links),
436 436 'pull':(_('[pulled] from'), None),
437 437 'started_following_repo':(_('[started following] repository'), None),
438 438 'stopped_following_repo':(_('[stopped following] repository'), None),
439 439 }
440 440
441 441 action_str = action_map.get(action, action)
442 442 if feed:
443 443 action = action_str[0].replace('[', '').replace(']', '')
444 444 else:
445 445 action = action_str[0].replace('[', '<span class="journal_highlight">')\
446 446 .replace(']', '</span>')
447 447
448 448 action_params_func = lambda :""
449 449
450 450 if callable(action_str[1]):
451 451 action_params_func = action_str[1]
452 452
453 453 return [literal(action), action_params_func]
454 454
455 455 def action_parser_icon(user_log):
456 456 action = user_log.action
457 457 action_params = None
458 458 x = action.split(':')
459 459
460 460 if len(x) > 1:
461 461 action, action_params = x
462 462
463 463 tmpl = """<img src="%s%s" alt="%s"/>"""
464 464 map = {'user_deleted_repo':'database_delete.png',
465 465 'user_created_repo':'database_add.png',
466 466 'user_forked_repo':'arrow_divide.png',
467 467 'user_updated_repo':'database_edit.png',
468 468 'admin_deleted_repo':'database_delete.png',
469 469 'admin_created_repo':'database_add.png',
470 470 'admin_forked_repo':'arrow_divide.png',
471 471 'admin_updated_repo':'database_edit.png',
472 472 'push':'script_add.png',
473 473 'push_local':'script_edit.png',
474 474 'push_remote':'connect.png',
475 475 'pull':'down_16.png',
476 476 'started_following_repo':'heart_add.png',
477 477 'stopped_following_repo':'heart_delete.png',
478 478 }
479 479 return literal(tmpl % ((url('/images/icons/')),
480 480 map.get(action, action), action))
481 481
482 482
483 483 #==============================================================================
484 484 # PERMS
485 485 #==============================================================================
486 486 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
487 487 HasRepoPermissionAny, HasRepoPermissionAll
488 488
489 489 #==============================================================================
490 490 # GRAVATAR URL
491 491 #==============================================================================
492 492
493 493 def gravatar_url(email_address, size=30):
494 494 if not str2bool(config['app_conf'].get('use_gravatar')) or \
495 495 email_address == 'anonymous@rhodecode.org':
496 496 return "/images/user%s.png" % size
497 497
498 498 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
499 499 default = 'identicon'
500 500 baseurl_nossl = "http://www.gravatar.com/avatar/"
501 501 baseurl_ssl = "https://secure.gravatar.com/avatar/"
502 502 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
503 503
504 504 if isinstance(email_address, unicode):
505 505 #hashlib crashes on unicode items
506 506 email_address = email_address.encode('utf8', 'replace')
507 507 # construct the url
508 508 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
509 509 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
510 510
511 511 return gravatar_url
512 512
513 513
514 514 #==============================================================================
515 515 # REPO PAGER, PAGER FOR REPOSITORY
516 516 #==============================================================================
517 517 class RepoPage(Page):
518 518
519 519 def __init__(self, collection, page=1, items_per_page=20,
520 520 item_count=None, url=None, branch_name=None, **kwargs):
521 521
522 522 """Create a "RepoPage" instance. special pager for paging
523 523 repository
524 524 """
525 525 self._url_generator = url
526 526
527 527 # Safe the kwargs class-wide so they can be used in the pager() method
528 528 self.kwargs = kwargs
529 529
530 530 # Save a reference to the collection
531 531 self.original_collection = collection
532 532
533 533 self.collection = collection
534 534
535 535 # The self.page is the number of the current page.
536 536 # The first page has the number 1!
537 537 try:
538 538 self.page = int(page) # make it int() if we get it as a string
539 539 except (ValueError, TypeError):
540 540 self.page = 1
541 541
542 542 self.items_per_page = items_per_page
543 543
544 544 # Unless the user tells us how many items the collections has
545 545 # we calculate that ourselves.
546 546 if item_count is not None:
547 547 self.item_count = item_count
548 548 else:
549 549 self.item_count = len(self.collection)
550 550
551 551 # Compute the number of the first and last available page
552 552 if self.item_count > 0:
553 553 self.first_page = 1
554 554 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
555 555 self.last_page = self.first_page + self.page_count - 1
556 556
557 557 # Make sure that the requested page number is the range of valid pages
558 558 if self.page > self.last_page:
559 559 self.page = self.last_page
560 560 elif self.page < self.first_page:
561 561 self.page = self.first_page
562 562
563 563 # Note: the number of items on this page can be less than
564 564 # items_per_page if the last page is not full
565 565 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
566 566 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
567 567
568 568 iterator = self.collection.get_changesets(start=self.first_item,
569 569 end=self.last_item,
570 570 reverse=True,
571 571 branch_name=branch_name)
572 572 self.items = list(iterator)
573 573
574 574 # Links to previous and next page
575 575 if self.page > self.first_page:
576 576 self.previous_page = self.page - 1
577 577 else:
578 578 self.previous_page = None
579 579
580 580 if self.page < self.last_page:
581 581 self.next_page = self.page + 1
582 582 else:
583 583 self.next_page = None
584 584
585 585 # No items available
586 586 else:
587 587 self.first_page = None
588 588 self.page_count = 0
589 589 self.last_page = None
590 590 self.first_item = None
591 591 self.last_item = None
592 592 self.previous_page = None
593 593 self.next_page = None
594 594 self.items = []
595 595
596 596 # This is a subclass of the 'list' type. Initialise the list now.
597 597 list.__init__(self, self.items)
598 598
599 599
600 600 def changed_tooltip(nodes):
601 601 """
602 602 Generates a html string for changed nodes in changeset page.
603 603 It limits the output to 30 entries
604 604
605 605 :param nodes: LazyNodesGenerator
606 606 """
607 607 if nodes:
608 608 pref = ': <br/> '
609 609 suf = ''
610 610 if len(nodes) > 30:
611 611 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
612 612 return literal(pref + '<br/> '.join([safe_unicode(x.path)
613 613 for x in nodes[:30]]) + suf)
614 614 else:
615 615 return ': ' + _('No Files')
616 616
617 617
618 618
619 619 def repo_link(groups_and_repos):
620 620 """
621 621 Makes a breadcrumbs link to repo within a group
622 622 joins &raquo; on each group to create a fancy link
623 623
624 624 ex::
625 625 group >> subgroup >> repo
626 626
627 627 :param groups_and_repos:
628 628 """
629 629 groups, repo_name = groups_and_repos
630 630
631 631 if not groups:
632 632 return repo_name
633 633 else:
634 634 def make_link(group):
635 635 return link_to(group.group_name, url('repos_group',
636 636 id=group.group_id))
637 637 return literal(' &raquo; '.join(map(make_link, groups)) + \
638 638 " &raquo; " + repo_name)
639 639
640 640
641 641 def fancy_file_stats(stats):
642 642 """
643 643 Displays a fancy two colored bar for number of added/deleted
644 644 lines of code on file
645 645
646 646 :param stats: two element list of added/deleted lines of code
647 647 """
648 648
649 649 a, d, t = stats[0], stats[1], stats[0] + stats[1]
650 650 width = 100
651 651 unit = float(width) / (t or 1)
652 652
653 653 # needs > 9% of width to be visible or 0 to be hidden
654 654 a_p = max(9, unit * a) if a > 0 else 0
655 655 d_p = max(9, unit * d) if d > 0 else 0
656 656 p_sum = a_p + d_p
657 657
658 658 if p_sum > width:
659 659 #adjust the percentage to be == 100% since we adjusted to 9
660 660 if a_p > d_p:
661 661 a_p = a_p - (p_sum - width)
662 662 else:
663 663 d_p = d_p - (p_sum - width)
664 664
665 665 a_v = a if a > 0 else ''
666 666 d_v = d if d > 0 else ''
667 667
668 668
669 669 def cgen(l_type):
670 670 mapping = {'tr':'top-right-rounded-corner',
671 671 'tl':'top-left-rounded-corner',
672 672 'br':'bottom-right-rounded-corner',
673 673 'bl':'bottom-left-rounded-corner'}
674 674 map_getter = lambda x:mapping[x]
675 675
676 676 if l_type == 'a' and d_v:
677 677 #case when added and deleted are present
678 678 return ' '.join(map(map_getter, ['tl', 'bl']))
679 679
680 680 if l_type == 'a' and not d_v:
681 681 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
682 682
683 683 if l_type == 'd' and a_v:
684 684 return ' '.join(map(map_getter, ['tr', 'br']))
685 685
686 686 if l_type == 'd' and not a_v:
687 687 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
688 688
689 689
690 690
691 691 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
692 692 a_p, a_v)
693 693 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
694 694 d_p, d_v)
695 695 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
General Comments 0
You need to be logged in to leave comments. Login now