##// END OF EJS Templates
formatting change on helper, remove str() call
marcink -
r2914:1cd1cbe6 beta
parent child Browse files
Show More
@@ -1,1096 +1,1096 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14
14
15 from datetime import datetime
15 from datetime import datetime
16 from pygments.formatters.html import HtmlFormatter
16 from pygments.formatters.html import HtmlFormatter
17 from pygments import highlight as code_highlight
17 from pygments import highlight as code_highlight
18 from pylons import url, request, config
18 from pylons import url, request, config
19 from pylons.i18n.translation import _, ungettext
19 from pylons.i18n.translation import _, ungettext
20 from hashlib import md5
20 from hashlib import md5
21
21
22 from webhelpers.html import literal, HTML, escape
22 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html.tools import *
23 from webhelpers.html.tools import *
24 from webhelpers.html.builder import make_tag
24 from webhelpers.html.builder import make_tag
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 submit, text, password, textarea, title, ul, xml_declaration, radio
28 submit, text, password, textarea, title, ul, xml_declaration, radio
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 from webhelpers.number import format_byte_size, format_bit_size
31 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.pylonslib import Flash as _Flash
32 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib.secure_form import secure_form
33 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 replace_whitespace, urlify, truncate, wrap_paragraphs
36 replace_whitespace, urlify, truncate, wrap_paragraphs
37 from webhelpers.date import time_ago_in_words
37 from webhelpers.date import time_ago_in_words
38 from webhelpers.paginate import Page
38 from webhelpers.paginate import Page
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
41
41
42 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 get_changeset_safe, datetime_to_time, time_to_datetime
45 get_changeset_safe, datetime_to_time, time_to_datetime
46 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.db import URL_SEP, Permission
51 from rhodecode.model.db import URL_SEP, Permission
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 html_escape_table = {
56 html_escape_table = {
57 "&": "&",
57 "&": "&",
58 '"': """,
58 '"': """,
59 "'": "'",
59 "'": "'",
60 ">": ">",
60 ">": ">",
61 "<": "&lt;",
61 "<": "&lt;",
62 }
62 }
63
63
64
64
65 def html_escape(text):
65 def html_escape(text):
66 """Produce entities within text."""
66 """Produce entities within text."""
67 return "".join(html_escape_table.get(c,c) for c in text)
67 return "".join(html_escape_table.get(c,c) for c in text)
68
68
69
69
70 def shorter(text, size=20):
70 def shorter(text, size=20):
71 postfix = '...'
71 postfix = '...'
72 if len(text) > size:
72 if len(text) > size:
73 return text[:size - len(postfix)] + postfix
73 return text[:size - len(postfix)] + postfix
74 return text
74 return text
75
75
76
76
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 """
78 """
79 Reset button
79 Reset button
80 """
80 """
81 _set_input_attrs(attrs, type, name, value)
81 _set_input_attrs(attrs, type, name, value)
82 _set_id_attr(attrs, id, name)
82 _set_id_attr(attrs, id, name)
83 convert_boolean_attrs(attrs, ["disabled"])
83 convert_boolean_attrs(attrs, ["disabled"])
84 return HTML.input(**attrs)
84 return HTML.input(**attrs)
85
85
86 reset = _reset
86 reset = _reset
87 safeid = _make_safe_id_component
87 safeid = _make_safe_id_component
88
88
89
89
90 def FID(raw_id, path):
90 def FID(raw_id, path):
91 """
91 """
92 Creates a uniqe ID for filenode based on it's hash of path and revision
92 Creates a uniqe ID for filenode based on it's hash of path and revision
93 it's safe to use in urls
93 it's safe to use in urls
94
94
95 :param raw_id:
95 :param raw_id:
96 :param path:
96 :param path:
97 """
97 """
98
98
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100
100
101
101
102 def get_token():
102 def get_token():
103 """Return the current authentication token, creating one if one doesn't
103 """Return the current authentication token, creating one if one doesn't
104 already exist.
104 already exist.
105 """
105 """
106 token_key = "_authentication_token"
106 token_key = "_authentication_token"
107 from pylons import session
107 from pylons import session
108 if not token_key in session:
108 if not token_key in session:
109 try:
109 try:
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 except AttributeError: # Python < 2.4
111 except AttributeError: # Python < 2.4
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 session[token_key] = token
113 session[token_key] = token
114 if hasattr(session, 'save'):
114 if hasattr(session, 'save'):
115 session.save()
115 session.save()
116 return session[token_key]
116 return session[token_key]
117
117
118
118
119 class _GetError(object):
119 class _GetError(object):
120 """Get error from form_errors, and represent it as span wrapped error
120 """Get error from form_errors, and represent it as span wrapped error
121 message
121 message
122
122
123 :param field_name: field to fetch errors for
123 :param field_name: field to fetch errors for
124 :param form_errors: form errors dict
124 :param form_errors: form errors dict
125 """
125 """
126
126
127 def __call__(self, field_name, form_errors):
127 def __call__(self, field_name, form_errors):
128 tmpl = """<span class="error_msg">%s</span>"""
128 tmpl = """<span class="error_msg">%s</span>"""
129 if form_errors and field_name in form_errors:
129 if form_errors and field_name in form_errors:
130 return literal(tmpl % form_errors.get(field_name))
130 return literal(tmpl % form_errors.get(field_name))
131
131
132 get_error = _GetError()
132 get_error = _GetError()
133
133
134
134
135 class _ToolTip(object):
135 class _ToolTip(object):
136
136
137 def __call__(self, tooltip_title, trim_at=50):
137 def __call__(self, tooltip_title, trim_at=50):
138 """
138 """
139 Special function just to wrap our text into nice formatted
139 Special function just to wrap our text into nice formatted
140 autowrapped text
140 autowrapped text
141
141
142 :param tooltip_title:
142 :param tooltip_title:
143 """
143 """
144 tooltip_title = escape(tooltip_title)
144 tooltip_title = escape(tooltip_title)
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 return tooltip_title
146 return tooltip_title
147 tooltip = _ToolTip()
147 tooltip = _ToolTip()
148
148
149
149
150 class _FilesBreadCrumbs(object):
150 class _FilesBreadCrumbs(object):
151
151
152 def __call__(self, repo_name, rev, paths):
152 def __call__(self, repo_name, rev, paths):
153 if isinstance(paths, str):
153 if isinstance(paths, str):
154 paths = safe_unicode(paths)
154 paths = safe_unicode(paths)
155 url_l = [link_to(repo_name, url('files_home',
155 url_l = [link_to(repo_name, url('files_home',
156 repo_name=repo_name,
156 repo_name=repo_name,
157 revision=rev, f_path=''),
157 revision=rev, f_path=''),
158 class_='ypjax-link')]
158 class_='ypjax-link')]
159 paths_l = paths.split('/')
159 paths_l = paths.split('/')
160 for cnt, p in enumerate(paths_l):
160 for cnt, p in enumerate(paths_l):
161 if p != '':
161 if p != '':
162 url_l.append(link_to(p,
162 url_l.append(link_to(p,
163 url('files_home',
163 url('files_home',
164 repo_name=repo_name,
164 repo_name=repo_name,
165 revision=rev,
165 revision=rev,
166 f_path='/'.join(paths_l[:cnt + 1])
166 f_path='/'.join(paths_l[:cnt + 1])
167 ),
167 ),
168 class_='ypjax-link'
168 class_='ypjax-link'
169 )
169 )
170 )
170 )
171
171
172 return literal('/'.join(url_l))
172 return literal('/'.join(url_l))
173
173
174 files_breadcrumbs = _FilesBreadCrumbs()
174 files_breadcrumbs = _FilesBreadCrumbs()
175
175
176
176
177 class CodeHtmlFormatter(HtmlFormatter):
177 class CodeHtmlFormatter(HtmlFormatter):
178 """
178 """
179 My code Html Formatter for source codes
179 My code Html Formatter for source codes
180 """
180 """
181
181
182 def wrap(self, source, outfile):
182 def wrap(self, source, outfile):
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184
184
185 def _wrap_code(self, source):
185 def _wrap_code(self, source):
186 for cnt, it in enumerate(source):
186 for cnt, it in enumerate(source):
187 i, t = it
187 i, t = it
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 yield i, t
189 yield i, t
190
190
191 def _wrap_tablelinenos(self, inner):
191 def _wrap_tablelinenos(self, inner):
192 dummyoutfile = StringIO.StringIO()
192 dummyoutfile = StringIO.StringIO()
193 lncount = 0
193 lncount = 0
194 for t, line in inner:
194 for t, line in inner:
195 if t:
195 if t:
196 lncount += 1
196 lncount += 1
197 dummyoutfile.write(line)
197 dummyoutfile.write(line)
198
198
199 fl = self.linenostart
199 fl = self.linenostart
200 mw = len(str(lncount + fl - 1))
200 mw = len(str(lncount + fl - 1))
201 sp = self.linenospecial
201 sp = self.linenospecial
202 st = self.linenostep
202 st = self.linenostep
203 la = self.lineanchors
203 la = self.lineanchors
204 aln = self.anchorlinenos
204 aln = self.anchorlinenos
205 nocls = self.noclasses
205 nocls = self.noclasses
206 if sp:
206 if sp:
207 lines = []
207 lines = []
208
208
209 for i in range(fl, fl + lncount):
209 for i in range(fl, fl + lncount):
210 if i % st == 0:
210 if i % st == 0:
211 if i % sp == 0:
211 if i % sp == 0:
212 if aln:
212 if aln:
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 (la, i, mw, i))
214 (la, i, mw, i))
215 else:
215 else:
216 lines.append('<span class="special">%*d</span>' % (mw, i))
216 lines.append('<span class="special">%*d</span>' % (mw, i))
217 else:
217 else:
218 if aln:
218 if aln:
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 else:
220 else:
221 lines.append('%*d' % (mw, i))
221 lines.append('%*d' % (mw, i))
222 else:
222 else:
223 lines.append('')
223 lines.append('')
224 ls = '\n'.join(lines)
224 ls = '\n'.join(lines)
225 else:
225 else:
226 lines = []
226 lines = []
227 for i in range(fl, fl + lncount):
227 for i in range(fl, fl + lncount):
228 if i % st == 0:
228 if i % st == 0:
229 if aln:
229 if aln:
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 else:
231 else:
232 lines.append('%*d' % (mw, i))
232 lines.append('%*d' % (mw, i))
233 else:
233 else:
234 lines.append('')
234 lines.append('')
235 ls = '\n'.join(lines)
235 ls = '\n'.join(lines)
236
236
237 # in case you wonder about the seemingly redundant <div> here: since the
237 # in case you wonder about the seemingly redundant <div> here: since the
238 # content in the other cell also is wrapped in a div, some browsers in
238 # content in the other cell also is wrapped in a div, some browsers in
239 # some configurations seem to mess up the formatting...
239 # some configurations seem to mess up the formatting...
240 if nocls:
240 if nocls:
241 yield 0, ('<table class="%stable">' % self.cssclass +
241 yield 0, ('<table class="%stable">' % self.cssclass +
242 '<tr><td><div class="linenodiv" '
242 '<tr><td><div class="linenodiv" '
243 'style="background-color: #f0f0f0; padding-right: 10px">'
243 'style="background-color: #f0f0f0; padding-right: 10px">'
244 '<pre style="line-height: 125%">' +
244 '<pre style="line-height: 125%">' +
245 ls + '</pre></div></td><td id="hlcode" class="code">')
245 ls + '</pre></div></td><td id="hlcode" class="code">')
246 else:
246 else:
247 yield 0, ('<table class="%stable">' % self.cssclass +
247 yield 0, ('<table class="%stable">' % self.cssclass +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 ls + '</pre></div></td><td id="hlcode" class="code">')
249 ls + '</pre></div></td><td id="hlcode" class="code">')
250 yield 0, dummyoutfile.getvalue()
250 yield 0, dummyoutfile.getvalue()
251 yield 0, '</td></tr></table>'
251 yield 0, '</td></tr></table>'
252
252
253
253
254 def pygmentize(filenode, **kwargs):
254 def pygmentize(filenode, **kwargs):
255 """pygmentize function using pygments
255 """pygmentize function using pygments
256
256
257 :param filenode:
257 :param filenode:
258 """
258 """
259
259
260 return literal(code_highlight(filenode.content,
260 return literal(code_highlight(filenode.content,
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262
262
263
263
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 """
265 """
266 pygmentize function for annotation
266 pygmentize function for annotation
267
267
268 :param filenode:
268 :param filenode:
269 """
269 """
270
270
271 color_dict = {}
271 color_dict = {}
272
272
273 def gen_color(n=10000):
273 def gen_color(n=10000):
274 """generator for getting n of evenly distributed colors using
274 """generator for getting n of evenly distributed colors using
275 hsv color and golden ratio. It always return same order of colors
275 hsv color and golden ratio. It always return same order of colors
276
276
277 :returns: RGB tuple
277 :returns: RGB tuple
278 """
278 """
279
279
280 def hsv_to_rgb(h, s, v):
280 def hsv_to_rgb(h, s, v):
281 if s == 0.0:
281 if s == 0.0:
282 return v, v, v
282 return v, v, v
283 i = int(h * 6.0) # XXX assume int() truncates!
283 i = int(h * 6.0) # XXX assume int() truncates!
284 f = (h * 6.0) - i
284 f = (h * 6.0) - i
285 p = v * (1.0 - s)
285 p = v * (1.0 - s)
286 q = v * (1.0 - s * f)
286 q = v * (1.0 - s * f)
287 t = v * (1.0 - s * (1.0 - f))
287 t = v * (1.0 - s * (1.0 - f))
288 i = i % 6
288 i = i % 6
289 if i == 0:
289 if i == 0:
290 return v, t, p
290 return v, t, p
291 if i == 1:
291 if i == 1:
292 return q, v, p
292 return q, v, p
293 if i == 2:
293 if i == 2:
294 return p, v, t
294 return p, v, t
295 if i == 3:
295 if i == 3:
296 return p, q, v
296 return p, q, v
297 if i == 4:
297 if i == 4:
298 return t, p, v
298 return t, p, v
299 if i == 5:
299 if i == 5:
300 return v, p, q
300 return v, p, q
301
301
302 golden_ratio = 0.618033988749895
302 golden_ratio = 0.618033988749895
303 h = 0.22717784590367374
303 h = 0.22717784590367374
304
304
305 for _ in xrange(n):
305 for _ in xrange(n):
306 h += golden_ratio
306 h += golden_ratio
307 h %= 1
307 h %= 1
308 HSV_tuple = [h, 0.95, 0.95]
308 HSV_tuple = [h, 0.95, 0.95]
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311
311
312 cgenerator = gen_color()
312 cgenerator = gen_color()
313
313
314 def get_color_string(cs):
314 def get_color_string(cs):
315 if cs in color_dict:
315 if cs in color_dict:
316 col = color_dict[cs]
316 col = color_dict[cs]
317 else:
317 else:
318 col = color_dict[cs] = cgenerator.next()
318 col = color_dict[cs] = cgenerator.next()
319 return "color: rgb(%s)! important;" % (', '.join(col))
319 return "color: rgb(%s)! important;" % (', '.join(col))
320
320
321 def url_func(repo_name):
321 def url_func(repo_name):
322
322
323 def _url_func(changeset):
323 def _url_func(changeset):
324 author = changeset.author
324 author = changeset.author
325 date = changeset.date
325 date = changeset.date
326 message = tooltip(changeset.message)
326 message = tooltip(changeset.message)
327
327
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 "</b> %s<br/></div>")
330 "</b> %s<br/></div>")
331
331
332 tooltip_html = tooltip_html % (author, date, message)
332 tooltip_html = tooltip_html % (author, date, message)
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 short_id(changeset.raw_id))
334 short_id(changeset.raw_id))
335 uri = link_to(
335 uri = link_to(
336 lnk_format,
336 lnk_format,
337 url('changeset_home', repo_name=repo_name,
337 url('changeset_home', repo_name=repo_name,
338 revision=changeset.raw_id),
338 revision=changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
340 class_='tooltip',
340 class_='tooltip',
341 title=tooltip_html
341 title=tooltip_html
342 )
342 )
343
343
344 uri += '\n'
344 uri += '\n'
345 return uri
345 return uri
346 return _url_func
346 return _url_func
347
347
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349
349
350
350
351 def is_following_repo(repo_name, user_id):
351 def is_following_repo(repo_name, user_id):
352 from rhodecode.model.scm import ScmModel
352 from rhodecode.model.scm import ScmModel
353 return ScmModel().is_following_repo(repo_name, user_id)
353 return ScmModel().is_following_repo(repo_name, user_id)
354
354
355 flash = _Flash()
355 flash = _Flash()
356
356
357 #==============================================================================
357 #==============================================================================
358 # SCM FILTERS available via h.
358 # SCM FILTERS available via h.
359 #==============================================================================
359 #==============================================================================
360 from rhodecode.lib.vcs.utils import author_name, author_email
360 from rhodecode.lib.vcs.utils import author_name, author_email
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 from rhodecode.model.db import User, ChangesetStatus
362 from rhodecode.model.db import User, ChangesetStatus
363
363
364 age = lambda x: _age(x)
364 age = lambda x: _age(x)
365 capitalize = lambda x: x.capitalize()
365 capitalize = lambda x: x.capitalize()
366 email = author_email
366 email = author_email
367 short_id = lambda x: x[:12]
367 short_id = lambda x: x[:12]
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
369
369
370
370
371 def fmt_date(date):
371 def fmt_date(date):
372 if date:
372 if date:
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 return date.strftime(_fmt).decode('utf8')
374 return date.strftime(_fmt).decode('utf8')
375
375
376 return ""
376 return ""
377
377
378
378
379 def is_git(repository):
379 def is_git(repository):
380 if hasattr(repository, 'alias'):
380 if hasattr(repository, 'alias'):
381 _type = repository.alias
381 _type = repository.alias
382 elif hasattr(repository, 'repo_type'):
382 elif hasattr(repository, 'repo_type'):
383 _type = repository.repo_type
383 _type = repository.repo_type
384 else:
384 else:
385 _type = repository
385 _type = repository
386 return _type == 'git'
386 return _type == 'git'
387
387
388
388
389 def is_hg(repository):
389 def is_hg(repository):
390 if hasattr(repository, 'alias'):
390 if hasattr(repository, 'alias'):
391 _type = repository.alias
391 _type = repository.alias
392 elif hasattr(repository, 'repo_type'):
392 elif hasattr(repository, 'repo_type'):
393 _type = repository.repo_type
393 _type = repository.repo_type
394 else:
394 else:
395 _type = repository
395 _type = repository
396 return _type == 'hg'
396 return _type == 'hg'
397
397
398
398
399 def email_or_none(author):
399 def email_or_none(author):
400 # extract email from the commit string
400 # extract email from the commit string
401 _email = email(author)
401 _email = email(author)
402 if _email != '':
402 if _email != '':
403 # check it against RhodeCode database, and use the MAIN email for this
403 # check it against RhodeCode database, and use the MAIN email for this
404 # user
404 # user
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 if user is not None:
406 if user is not None:
407 return user.email
407 return user.email
408 return _email
408 return _email
409
409
410 # See if it contains a username we can get an email from
410 # See if it contains a username we can get an email from
411 user = User.get_by_username(author_name(author), case_insensitive=True,
411 user = User.get_by_username(author_name(author), case_insensitive=True,
412 cache=True)
412 cache=True)
413 if user is not None:
413 if user is not None:
414 return user.email
414 return user.email
415
415
416 # No valid email, not a valid user in the system, none!
416 # No valid email, not a valid user in the system, none!
417 return None
417 return None
418
418
419
419
420 def person(author, show_attr="username_and_name"):
420 def person(author, show_attr="username_and_name"):
421 # attr to return from fetched user
421 # attr to return from fetched user
422 person_getter = lambda usr: getattr(usr, show_attr)
422 person_getter = lambda usr: getattr(usr, show_attr)
423
423
424 # Valid email in the attribute passed, see if they're in the system
424 # Valid email in the attribute passed, see if they're in the system
425 _email = email(author)
425 _email = email(author)
426 if _email != '':
426 if _email != '':
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 if user is not None:
428 if user is not None:
429 return person_getter(user)
429 return person_getter(user)
430 return _email
430 return _email
431
431
432 # Maybe it's a username?
432 # Maybe it's a username?
433 _author = author_name(author)
433 _author = author_name(author)
434 user = User.get_by_username(_author, case_insensitive=True,
434 user = User.get_by_username(_author, case_insensitive=True,
435 cache=True)
435 cache=True)
436 if user is not None:
436 if user is not None:
437 return person_getter(user)
437 return person_getter(user)
438
438
439 # Still nothing? Just pass back the author name then
439 # Still nothing? Just pass back the author name then
440 return _author
440 return _author
441
441
442
442
443 def person_by_id(id_, show_attr="username_and_name"):
443 def person_by_id(id_, show_attr="username_and_name"):
444 # attr to return from fetched user
444 # attr to return from fetched user
445 person_getter = lambda usr: getattr(usr, show_attr)
445 person_getter = lambda usr: getattr(usr, show_attr)
446
446
447 #maybe it's an ID ?
447 #maybe it's an ID ?
448 if str(id_).isdigit() or isinstance(id_, int):
448 if str(id_).isdigit() or isinstance(id_, int):
449 id_ = int(id_)
449 id_ = int(id_)
450 user = User.get(id_)
450 user = User.get(id_)
451 if user is not None:
451 if user is not None:
452 return person_getter(user)
452 return person_getter(user)
453 return id_
453 return id_
454
454
455
455
456 def desc_stylize(value):
456 def desc_stylize(value):
457 """
457 """
458 converts tags from value into html equivalent
458 converts tags from value into html equivalent
459
459
460 :param value:
460 :param value:
461 """
461 """
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
469 '<div class="metatag" tag="lang">\\2</div>', value)
469 '<div class="metatag" tag="lang">\\2</div>', value)
470 value = re.sub(r'\[([a-z]+)\]',
470 value = re.sub(r'\[([a-z]+)\]',
471 '<div class="metatag" tag="\\1">\\1</div>', value)
471 '<div class="metatag" tag="\\1">\\1</div>', value)
472
472
473 return value
473 return value
474
474
475
475
476 def bool2icon(value):
476 def bool2icon(value):
477 """Returns True/False values represented as small html image of true/false
477 """Returns True/False values represented as small html image of true/false
478 icons
478 icons
479
479
480 :param value: bool value
480 :param value: bool value
481 """
481 """
482
482
483 if value is True:
483 if value is True:
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 alt=_('True'))
485 alt=_('True'))
486
486
487 if value is False:
487 if value is False:
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 alt=_('False'))
489 alt=_('False'))
490
490
491 return value
491 return value
492
492
493
493
494 def action_parser(user_log, feed=False):
494 def action_parser(user_log, feed=False):
495 """
495 """
496 This helper will action_map the specified string action into translated
496 This helper will action_map the specified string action into translated
497 fancy names with icons and links
497 fancy names with icons and links
498
498
499 :param user_log: user log instance
499 :param user_log: user log instance
500 :param feed: use output for feeds (no html and fancy icons)
500 :param feed: use output for feeds (no html and fancy icons)
501 """
501 """
502
502
503 action = user_log.action
503 action = user_log.action
504 action_params = ' '
504 action_params = ' '
505
505
506 x = action.split(':')
506 x = action.split(':')
507
507
508 if len(x) > 1:
508 if len(x) > 1:
509 action, action_params = x
509 action, action_params = x
510
510
511 def get_cs_links():
511 def get_cs_links():
512 revs_limit = 3 # display this amount always
512 revs_limit = 3 # display this amount always
513 revs_top_limit = 50 # show upto this amount of changesets hidden
513 revs_top_limit = 50 # show upto this amount of changesets hidden
514 revs_ids = action_params.split(',')
514 revs_ids = action_params.split(',')
515 deleted = user_log.repository is None
515 deleted = user_log.repository is None
516 if deleted:
516 if deleted:
517 return ','.join(revs_ids)
517 return ','.join(revs_ids)
518
518
519 repo_name = user_log.repository.repo_name
519 repo_name = user_log.repository.repo_name
520
520
521 repo = user_log.repository.scm_instance
521 repo = user_log.repository.scm_instance
522
522
523 def lnk(rev, repo_name):
523 def lnk(rev, repo_name):
524
524
525 if isinstance(rev, BaseChangeset):
525 if isinstance(rev, BaseChangeset):
526 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
526 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
527 _url = url('changeset_home', repo_name=repo_name,
527 _url = url('changeset_home', repo_name=repo_name,
528 revision=rev.raw_id)
528 revision=rev.raw_id)
529 title = tooltip(rev.message)
529 title = tooltip(rev.message)
530 else:
530 else:
531 lbl = '%s' % rev
531 lbl = '%s' % rev
532 _url = '#'
532 _url = '#'
533 title = _('Changeset not found')
533 title = _('Changeset not found')
534
534
535 return link_to(lbl, _url, title=title, class_='tooltip',)
535 return link_to(lbl, _url, title=title, class_='tooltip',)
536
536
537 revs = []
537 revs = []
538 if len(filter(lambda v: v != '', revs_ids)) > 0:
538 if len(filter(lambda v: v != '', revs_ids)) > 0:
539 for rev in revs_ids[:revs_top_limit]:
539 for rev in revs_ids[:revs_top_limit]:
540 try:
540 try:
541 rev = repo.get_changeset(rev)
541 rev = repo.get_changeset(rev)
542 revs.append(rev)
542 revs.append(rev)
543 except ChangesetDoesNotExistError:
543 except ChangesetDoesNotExistError:
544 log.error('cannot find revision %s in this repo' % rev)
544 log.error('cannot find revision %s in this repo' % rev)
545 revs.append(rev)
545 revs.append(rev)
546 continue
546 continue
547 cs_links = []
547 cs_links = []
548 cs_links.append(" " + ', '.join(
548 cs_links.append(" " + ', '.join(
549 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
549 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
550 )
550 )
551 )
551 )
552
552
553 compare_view = (
553 compare_view = (
554 ' <div class="compare_view tooltip" title="%s">'
554 ' <div class="compare_view tooltip" title="%s">'
555 '<a href="%s">%s</a> </div>' % (
555 '<a href="%s">%s</a> </div>' % (
556 _('Show all combined changesets %s->%s') % (
556 _('Show all combined changesets %s->%s') % (
557 revs_ids[0], revs_ids[-1]
557 revs_ids[0], revs_ids[-1]
558 ),
558 ),
559 url('changeset_home', repo_name=repo_name,
559 url('changeset_home', repo_name=repo_name,
560 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
560 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
561 ),
561 ),
562 _('compare view')
562 _('compare view')
563 )
563 )
564 )
564 )
565
565
566 # if we have exactly one more than normally displayed
566 # if we have exactly one more than normally displayed
567 # just display it, takes less space than displaying
567 # just display it, takes less space than displaying
568 # "and 1 more revisions"
568 # "and 1 more revisions"
569 if len(revs_ids) == revs_limit + 1:
569 if len(revs_ids) == revs_limit + 1:
570 rev = revs[revs_limit]
570 rev = revs[revs_limit]
571 cs_links.append(", " + lnk(rev, repo_name))
571 cs_links.append(", " + lnk(rev, repo_name))
572
572
573 # hidden-by-default ones
573 # hidden-by-default ones
574 if len(revs_ids) > revs_limit + 1:
574 if len(revs_ids) > revs_limit + 1:
575 uniq_id = revs_ids[0]
575 uniq_id = revs_ids[0]
576 html_tmpl = (
576 html_tmpl = (
577 '<span> %s <a class="show_more" id="_%s" '
577 '<span> %s <a class="show_more" id="_%s" '
578 'href="#more">%s</a> %s</span>'
578 'href="#more">%s</a> %s</span>'
579 )
579 )
580 if not feed:
580 if not feed:
581 cs_links.append(html_tmpl % (
581 cs_links.append(html_tmpl % (
582 _('and'),
582 _('and'),
583 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
583 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
584 _('revisions')
584 _('revisions')
585 )
585 )
586 )
586 )
587
587
588 if not feed:
588 if not feed:
589 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
589 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
590 else:
590 else:
591 html_tmpl = '<span id="%s"> %s </span>'
591 html_tmpl = '<span id="%s"> %s </span>'
592
592
593 morelinks = ', '.join(
593 morelinks = ', '.join(
594 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
594 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
595 )
595 )
596
596
597 if len(revs_ids) > revs_top_limit:
597 if len(revs_ids) > revs_top_limit:
598 morelinks += ', ...'
598 morelinks += ', ...'
599
599
600 cs_links.append(html_tmpl % (uniq_id, morelinks))
600 cs_links.append(html_tmpl % (uniq_id, morelinks))
601 if len(revs) > 1:
601 if len(revs) > 1:
602 cs_links.append(compare_view)
602 cs_links.append(compare_view)
603 return ''.join(cs_links)
603 return ''.join(cs_links)
604
604
605 def get_fork_name():
605 def get_fork_name():
606 repo_name = action_params
606 repo_name = action_params
607 return _('fork name ') + str(link_to(action_params, url('summary_home',
607 _url = url('summary_home', repo_name=repo_name)
608 repo_name=repo_name,)))
608 return _('fork name %s') % link_to(action_params, _url)
609
609
610 def get_user_name():
610 def get_user_name():
611 user_name = action_params
611 user_name = action_params
612 return user_name
612 return user_name
613
613
614 def get_users_group():
614 def get_users_group():
615 group_name = action_params
615 group_name = action_params
616 return group_name
616 return group_name
617
617
618 def get_pull_request():
618 def get_pull_request():
619 pull_request_id = action_params
619 pull_request_id = action_params
620 repo_name = user_log.repository.repo_name
620 repo_name = user_log.repository.repo_name
621 return link_to(_('Pull request #%s') % pull_request_id,
621 return link_to(_('Pull request #%s') % pull_request_id,
622 url('pullrequest_show', repo_name=repo_name,
622 url('pullrequest_show', repo_name=repo_name,
623 pull_request_id=pull_request_id))
623 pull_request_id=pull_request_id))
624
624
625 # action : translated str, callback(extractor), icon
625 # action : translated str, callback(extractor), icon
626 action_map = {
626 action_map = {
627 'user_deleted_repo': (_('[deleted] repository'),
627 'user_deleted_repo': (_('[deleted] repository'),
628 None, 'database_delete.png'),
628 None, 'database_delete.png'),
629 'user_created_repo': (_('[created] repository'),
629 'user_created_repo': (_('[created] repository'),
630 None, 'database_add.png'),
630 None, 'database_add.png'),
631 'user_created_fork': (_('[created] repository as fork'),
631 'user_created_fork': (_('[created] repository as fork'),
632 None, 'arrow_divide.png'),
632 None, 'arrow_divide.png'),
633 'user_forked_repo': (_('[forked] repository'),
633 'user_forked_repo': (_('[forked] repository'),
634 get_fork_name, 'arrow_divide.png'),
634 get_fork_name, 'arrow_divide.png'),
635 'user_updated_repo': (_('[updated] repository'),
635 'user_updated_repo': (_('[updated] repository'),
636 None, 'database_edit.png'),
636 None, 'database_edit.png'),
637 'admin_deleted_repo': (_('[delete] repository'),
637 'admin_deleted_repo': (_('[delete] repository'),
638 None, 'database_delete.png'),
638 None, 'database_delete.png'),
639 'admin_created_repo': (_('[created] repository'),
639 'admin_created_repo': (_('[created] repository'),
640 None, 'database_add.png'),
640 None, 'database_add.png'),
641 'admin_forked_repo': (_('[forked] repository'),
641 'admin_forked_repo': (_('[forked] repository'),
642 None, 'arrow_divide.png'),
642 None, 'arrow_divide.png'),
643 'admin_updated_repo': (_('[updated] repository'),
643 'admin_updated_repo': (_('[updated] repository'),
644 None, 'database_edit.png'),
644 None, 'database_edit.png'),
645 'admin_created_user': (_('[created] user'),
645 'admin_created_user': (_('[created] user'),
646 get_user_name, 'user_add.png'),
646 get_user_name, 'user_add.png'),
647 'admin_updated_user': (_('[updated] user'),
647 'admin_updated_user': (_('[updated] user'),
648 get_user_name, 'user_edit.png'),
648 get_user_name, 'user_edit.png'),
649 'admin_created_users_group': (_('[created] users group'),
649 'admin_created_users_group': (_('[created] users group'),
650 get_users_group, 'group_add.png'),
650 get_users_group, 'group_add.png'),
651 'admin_updated_users_group': (_('[updated] users group'),
651 'admin_updated_users_group': (_('[updated] users group'),
652 get_users_group, 'group_edit.png'),
652 get_users_group, 'group_edit.png'),
653 'user_commented_revision': (_('[commented] on revision in repository'),
653 'user_commented_revision': (_('[commented] on revision in repository'),
654 get_cs_links, 'comment_add.png'),
654 get_cs_links, 'comment_add.png'),
655 'user_commented_pull_request': (_('[commented] on pull request for'),
655 'user_commented_pull_request': (_('[commented] on pull request for'),
656 get_pull_request, 'comment_add.png'),
656 get_pull_request, 'comment_add.png'),
657 'user_closed_pull_request': (_('[closed] pull request for'),
657 'user_closed_pull_request': (_('[closed] pull request for'),
658 get_pull_request, 'tick.png'),
658 get_pull_request, 'tick.png'),
659 'push': (_('[pushed] into'),
659 'push': (_('[pushed] into'),
660 get_cs_links, 'script_add.png'),
660 get_cs_links, 'script_add.png'),
661 'push_local': (_('[committed via RhodeCode] into repository'),
661 'push_local': (_('[committed via RhodeCode] into repository'),
662 get_cs_links, 'script_edit.png'),
662 get_cs_links, 'script_edit.png'),
663 'push_remote': (_('[pulled from remote] into repository'),
663 'push_remote': (_('[pulled from remote] into repository'),
664 get_cs_links, 'connect.png'),
664 get_cs_links, 'connect.png'),
665 'pull': (_('[pulled] from'),
665 'pull': (_('[pulled] from'),
666 None, 'down_16.png'),
666 None, 'down_16.png'),
667 'started_following_repo': (_('[started following] repository'),
667 'started_following_repo': (_('[started following] repository'),
668 None, 'heart_add.png'),
668 None, 'heart_add.png'),
669 'stopped_following_repo': (_('[stopped following] repository'),
669 'stopped_following_repo': (_('[stopped following] repository'),
670 None, 'heart_delete.png'),
670 None, 'heart_delete.png'),
671 }
671 }
672
672
673 action_str = action_map.get(action, action)
673 action_str = action_map.get(action, action)
674 if feed:
674 if feed:
675 action = action_str[0].replace('[', '').replace(']', '')
675 action = action_str[0].replace('[', '').replace(']', '')
676 else:
676 else:
677 action = action_str[0]\
677 action = action_str[0]\
678 .replace('[', '<span class="journal_highlight">')\
678 .replace('[', '<span class="journal_highlight">')\
679 .replace(']', '</span>')
679 .replace(']', '</span>')
680
680
681 action_params_func = lambda: ""
681 action_params_func = lambda: ""
682
682
683 if callable(action_str[1]):
683 if callable(action_str[1]):
684 action_params_func = action_str[1]
684 action_params_func = action_str[1]
685
685
686 def action_parser_icon():
686 def action_parser_icon():
687 action = user_log.action
687 action = user_log.action
688 action_params = None
688 action_params = None
689 x = action.split(':')
689 x = action.split(':')
690
690
691 if len(x) > 1:
691 if len(x) > 1:
692 action, action_params = x
692 action, action_params = x
693
693
694 tmpl = """<img src="%s%s" alt="%s"/>"""
694 tmpl = """<img src="%s%s" alt="%s"/>"""
695 ico = action_map.get(action, ['', '', ''])[2]
695 ico = action_map.get(action, ['', '', ''])[2]
696 return literal(tmpl % ((url('/images/icons/')), ico, action))
696 return literal(tmpl % ((url('/images/icons/')), ico, action))
697
697
698 # returned callbacks we need to call to get
698 # returned callbacks we need to call to get
699 return [lambda: literal(action), action_params_func, action_parser_icon]
699 return [lambda: literal(action), action_params_func, action_parser_icon]
700
700
701
701
702
702
703 #==============================================================================
703 #==============================================================================
704 # PERMS
704 # PERMS
705 #==============================================================================
705 #==============================================================================
706 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
706 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
707 HasRepoPermissionAny, HasRepoPermissionAll
707 HasRepoPermissionAny, HasRepoPermissionAll
708
708
709
709
710 #==============================================================================
710 #==============================================================================
711 # GRAVATAR URL
711 # GRAVATAR URL
712 #==============================================================================
712 #==============================================================================
713
713
714 def gravatar_url(email_address, size=30):
714 def gravatar_url(email_address, size=30):
715 from pylons import url ## doh, we need to re-import url to mock it later
715 from pylons import url ## doh, we need to re-import url to mock it later
716 if(str2bool(config['app_conf'].get('use_gravatar')) and
716 if(str2bool(config['app_conf'].get('use_gravatar')) and
717 config['app_conf'].get('alternative_gravatar_url')):
717 config['app_conf'].get('alternative_gravatar_url')):
718 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
718 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
719 parsed_url = urlparse.urlparse(url.current(qualified=True))
719 parsed_url = urlparse.urlparse(url.current(qualified=True))
720 tmpl = tmpl.replace('{email}', email_address)\
720 tmpl = tmpl.replace('{email}', email_address)\
721 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
721 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
722 .replace('{netloc}', parsed_url.netloc)\
722 .replace('{netloc}', parsed_url.netloc)\
723 .replace('{scheme}', parsed_url.scheme)\
723 .replace('{scheme}', parsed_url.scheme)\
724 .replace('{size}', str(size))
724 .replace('{size}', str(size))
725 return tmpl
725 return tmpl
726
726
727 if (not str2bool(config['app_conf'].get('use_gravatar')) or
727 if (not str2bool(config['app_conf'].get('use_gravatar')) or
728 not email_address or email_address == 'anonymous@rhodecode.org'):
728 not email_address or email_address == 'anonymous@rhodecode.org'):
729 f = lambda a, l: min(l, key=lambda x: abs(x - a))
729 f = lambda a, l: min(l, key=lambda x: abs(x - a))
730 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
730 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
731
731
732 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
732 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
733 default = 'identicon'
733 default = 'identicon'
734 baseurl_nossl = "http://www.gravatar.com/avatar/"
734 baseurl_nossl = "http://www.gravatar.com/avatar/"
735 baseurl_ssl = "https://secure.gravatar.com/avatar/"
735 baseurl_ssl = "https://secure.gravatar.com/avatar/"
736 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
736 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
737
737
738 if isinstance(email_address, unicode):
738 if isinstance(email_address, unicode):
739 #hashlib crashes on unicode items
739 #hashlib crashes on unicode items
740 email_address = safe_str(email_address)
740 email_address = safe_str(email_address)
741 # construct the url
741 # construct the url
742 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
742 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
743 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
743 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
744
744
745 return gravatar_url
745 return gravatar_url
746
746
747
747
748 #==============================================================================
748 #==============================================================================
749 # REPO PAGER, PAGER FOR REPOSITORY
749 # REPO PAGER, PAGER FOR REPOSITORY
750 #==============================================================================
750 #==============================================================================
751 class RepoPage(Page):
751 class RepoPage(Page):
752
752
753 def __init__(self, collection, page=1, items_per_page=20,
753 def __init__(self, collection, page=1, items_per_page=20,
754 item_count=None, url=None, **kwargs):
754 item_count=None, url=None, **kwargs):
755
755
756 """Create a "RepoPage" instance. special pager for paging
756 """Create a "RepoPage" instance. special pager for paging
757 repository
757 repository
758 """
758 """
759 self._url_generator = url
759 self._url_generator = url
760
760
761 # Safe the kwargs class-wide so they can be used in the pager() method
761 # Safe the kwargs class-wide so they can be used in the pager() method
762 self.kwargs = kwargs
762 self.kwargs = kwargs
763
763
764 # Save a reference to the collection
764 # Save a reference to the collection
765 self.original_collection = collection
765 self.original_collection = collection
766
766
767 self.collection = collection
767 self.collection = collection
768
768
769 # The self.page is the number of the current page.
769 # The self.page is the number of the current page.
770 # The first page has the number 1!
770 # The first page has the number 1!
771 try:
771 try:
772 self.page = int(page) # make it int() if we get it as a string
772 self.page = int(page) # make it int() if we get it as a string
773 except (ValueError, TypeError):
773 except (ValueError, TypeError):
774 self.page = 1
774 self.page = 1
775
775
776 self.items_per_page = items_per_page
776 self.items_per_page = items_per_page
777
777
778 # Unless the user tells us how many items the collections has
778 # Unless the user tells us how many items the collections has
779 # we calculate that ourselves.
779 # we calculate that ourselves.
780 if item_count is not None:
780 if item_count is not None:
781 self.item_count = item_count
781 self.item_count = item_count
782 else:
782 else:
783 self.item_count = len(self.collection)
783 self.item_count = len(self.collection)
784
784
785 # Compute the number of the first and last available page
785 # Compute the number of the first and last available page
786 if self.item_count > 0:
786 if self.item_count > 0:
787 self.first_page = 1
787 self.first_page = 1
788 self.page_count = int(math.ceil(float(self.item_count) /
788 self.page_count = int(math.ceil(float(self.item_count) /
789 self.items_per_page))
789 self.items_per_page))
790 self.last_page = self.first_page + self.page_count - 1
790 self.last_page = self.first_page + self.page_count - 1
791
791
792 # Make sure that the requested page number is the range of
792 # Make sure that the requested page number is the range of
793 # valid pages
793 # valid pages
794 if self.page > self.last_page:
794 if self.page > self.last_page:
795 self.page = self.last_page
795 self.page = self.last_page
796 elif self.page < self.first_page:
796 elif self.page < self.first_page:
797 self.page = self.first_page
797 self.page = self.first_page
798
798
799 # Note: the number of items on this page can be less than
799 # Note: the number of items on this page can be less than
800 # items_per_page if the last page is not full
800 # items_per_page if the last page is not full
801 self.first_item = max(0, (self.item_count) - (self.page *
801 self.first_item = max(0, (self.item_count) - (self.page *
802 items_per_page))
802 items_per_page))
803 self.last_item = ((self.item_count - 1) - items_per_page *
803 self.last_item = ((self.item_count - 1) - items_per_page *
804 (self.page - 1))
804 (self.page - 1))
805
805
806 self.items = list(self.collection[self.first_item:self.last_item + 1])
806 self.items = list(self.collection[self.first_item:self.last_item + 1])
807
807
808 # Links to previous and next page
808 # Links to previous and next page
809 if self.page > self.first_page:
809 if self.page > self.first_page:
810 self.previous_page = self.page - 1
810 self.previous_page = self.page - 1
811 else:
811 else:
812 self.previous_page = None
812 self.previous_page = None
813
813
814 if self.page < self.last_page:
814 if self.page < self.last_page:
815 self.next_page = self.page + 1
815 self.next_page = self.page + 1
816 else:
816 else:
817 self.next_page = None
817 self.next_page = None
818
818
819 # No items available
819 # No items available
820 else:
820 else:
821 self.first_page = None
821 self.first_page = None
822 self.page_count = 0
822 self.page_count = 0
823 self.last_page = None
823 self.last_page = None
824 self.first_item = None
824 self.first_item = None
825 self.last_item = None
825 self.last_item = None
826 self.previous_page = None
826 self.previous_page = None
827 self.next_page = None
827 self.next_page = None
828 self.items = []
828 self.items = []
829
829
830 # This is a subclass of the 'list' type. Initialise the list now.
830 # This is a subclass of the 'list' type. Initialise the list now.
831 list.__init__(self, reversed(self.items))
831 list.__init__(self, reversed(self.items))
832
832
833
833
834 def changed_tooltip(nodes):
834 def changed_tooltip(nodes):
835 """
835 """
836 Generates a html string for changed nodes in changeset page.
836 Generates a html string for changed nodes in changeset page.
837 It limits the output to 30 entries
837 It limits the output to 30 entries
838
838
839 :param nodes: LazyNodesGenerator
839 :param nodes: LazyNodesGenerator
840 """
840 """
841 if nodes:
841 if nodes:
842 pref = ': <br/> '
842 pref = ': <br/> '
843 suf = ''
843 suf = ''
844 if len(nodes) > 30:
844 if len(nodes) > 30:
845 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
845 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
846 return literal(pref + '<br/> '.join([safe_unicode(x.path)
846 return literal(pref + '<br/> '.join([safe_unicode(x.path)
847 for x in nodes[:30]]) + suf)
847 for x in nodes[:30]]) + suf)
848 else:
848 else:
849 return ': ' + _('No Files')
849 return ': ' + _('No Files')
850
850
851
851
852 def repo_link(groups_and_repos):
852 def repo_link(groups_and_repos):
853 """
853 """
854 Makes a breadcrumbs link to repo within a group
854 Makes a breadcrumbs link to repo within a group
855 joins &raquo; on each group to create a fancy link
855 joins &raquo; on each group to create a fancy link
856
856
857 ex::
857 ex::
858 group >> subgroup >> repo
858 group >> subgroup >> repo
859
859
860 :param groups_and_repos:
860 :param groups_and_repos:
861 """
861 """
862 groups, repo_name = groups_and_repos
862 groups, repo_name = groups_and_repos
863
863
864 if not groups:
864 if not groups:
865 return repo_name
865 return repo_name
866 else:
866 else:
867 def make_link(group):
867 def make_link(group):
868 return link_to(group.name, url('repos_group_home',
868 return link_to(group.name, url('repos_group_home',
869 group_name=group.group_name))
869 group_name=group.group_name))
870 return literal(' &raquo; '.join(map(make_link, groups)) + \
870 return literal(' &raquo; '.join(map(make_link, groups)) + \
871 " &raquo; " + repo_name)
871 " &raquo; " + repo_name)
872
872
873
873
874 def fancy_file_stats(stats):
874 def fancy_file_stats(stats):
875 """
875 """
876 Displays a fancy two colored bar for number of added/deleted
876 Displays a fancy two colored bar for number of added/deleted
877 lines of code on file
877 lines of code on file
878
878
879 :param stats: two element list of added/deleted lines of code
879 :param stats: two element list of added/deleted lines of code
880 """
880 """
881
881
882 a, d, t = stats[0], stats[1], stats[0] + stats[1]
882 a, d, t = stats[0], stats[1], stats[0] + stats[1]
883 width = 100
883 width = 100
884 unit = float(width) / (t or 1)
884 unit = float(width) / (t or 1)
885
885
886 # needs > 9% of width to be visible or 0 to be hidden
886 # needs > 9% of width to be visible or 0 to be hidden
887 a_p = max(9, unit * a) if a > 0 else 0
887 a_p = max(9, unit * a) if a > 0 else 0
888 d_p = max(9, unit * d) if d > 0 else 0
888 d_p = max(9, unit * d) if d > 0 else 0
889 p_sum = a_p + d_p
889 p_sum = a_p + d_p
890
890
891 if p_sum > width:
891 if p_sum > width:
892 #adjust the percentage to be == 100% since we adjusted to 9
892 #adjust the percentage to be == 100% since we adjusted to 9
893 if a_p > d_p:
893 if a_p > d_p:
894 a_p = a_p - (p_sum - width)
894 a_p = a_p - (p_sum - width)
895 else:
895 else:
896 d_p = d_p - (p_sum - width)
896 d_p = d_p - (p_sum - width)
897
897
898 a_v = a if a > 0 else ''
898 a_v = a if a > 0 else ''
899 d_v = d if d > 0 else ''
899 d_v = d if d > 0 else ''
900
900
901 def cgen(l_type):
901 def cgen(l_type):
902 mapping = {'tr': 'top-right-rounded-corner-mid',
902 mapping = {'tr': 'top-right-rounded-corner-mid',
903 'tl': 'top-left-rounded-corner-mid',
903 'tl': 'top-left-rounded-corner-mid',
904 'br': 'bottom-right-rounded-corner-mid',
904 'br': 'bottom-right-rounded-corner-mid',
905 'bl': 'bottom-left-rounded-corner-mid'}
905 'bl': 'bottom-left-rounded-corner-mid'}
906 map_getter = lambda x: mapping[x]
906 map_getter = lambda x: mapping[x]
907
907
908 if l_type == 'a' and d_v:
908 if l_type == 'a' and d_v:
909 #case when added and deleted are present
909 #case when added and deleted are present
910 return ' '.join(map(map_getter, ['tl', 'bl']))
910 return ' '.join(map(map_getter, ['tl', 'bl']))
911
911
912 if l_type == 'a' and not d_v:
912 if l_type == 'a' and not d_v:
913 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
913 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
914
914
915 if l_type == 'd' and a_v:
915 if l_type == 'd' and a_v:
916 return ' '.join(map(map_getter, ['tr', 'br']))
916 return ' '.join(map(map_getter, ['tr', 'br']))
917
917
918 if l_type == 'd' and not a_v:
918 if l_type == 'd' and not a_v:
919 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
919 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
920
920
921 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
921 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
922 cgen('a'), a_p, a_v
922 cgen('a'), a_p, a_v
923 )
923 )
924 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
924 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
925 cgen('d'), d_p, d_v
925 cgen('d'), d_p, d_v
926 )
926 )
927 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
927 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
928
928
929
929
930 def urlify_text(text_):
930 def urlify_text(text_):
931 import re
931 import re
932
932
933 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
933 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
934 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
934 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
935
935
936 def url_func(match_obj):
936 def url_func(match_obj):
937 url_full = match_obj.groups()[0]
937 url_full = match_obj.groups()[0]
938 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
938 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
939
939
940 return literal(url_pat.sub(url_func, text_))
940 return literal(url_pat.sub(url_func, text_))
941
941
942
942
943 def urlify_changesets(text_, repository):
943 def urlify_changesets(text_, repository):
944 """
944 """
945 Extract revision ids from changeset and make link from them
945 Extract revision ids from changeset and make link from them
946
946
947 :param text_:
947 :param text_:
948 :param repository:
948 :param repository:
949 """
949 """
950 import re
950 import re
951 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
951 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
952
952
953 def url_func(match_obj):
953 def url_func(match_obj):
954 rev = match_obj.groups()[0]
954 rev = match_obj.groups()[0]
955 pref = ''
955 pref = ''
956 if match_obj.group().startswith(' '):
956 if match_obj.group().startswith(' '):
957 pref = ' '
957 pref = ' '
958 tmpl = (
958 tmpl = (
959 '%(pref)s<a class="%(cls)s" href="%(url)s">'
959 '%(pref)s<a class="%(cls)s" href="%(url)s">'
960 '%(rev)s'
960 '%(rev)s'
961 '</a>'
961 '</a>'
962 )
962 )
963 return tmpl % {
963 return tmpl % {
964 'pref': pref,
964 'pref': pref,
965 'cls': 'revision-link',
965 'cls': 'revision-link',
966 'url': url('changeset_home', repo_name=repository, revision=rev),
966 'url': url('changeset_home', repo_name=repository, revision=rev),
967 'rev': rev,
967 'rev': rev,
968 }
968 }
969
969
970 newtext = URL_PAT.sub(url_func, text_)
970 newtext = URL_PAT.sub(url_func, text_)
971
971
972 return newtext
972 return newtext
973
973
974
974
975 def urlify_commit(text_, repository=None, link_=None):
975 def urlify_commit(text_, repository=None, link_=None):
976 """
976 """
977 Parses given text message and makes proper links.
977 Parses given text message and makes proper links.
978 issues are linked to given issue-server, and rest is a changeset link
978 issues are linked to given issue-server, and rest is a changeset link
979 if link_ is given, in other case it's a plain text
979 if link_ is given, in other case it's a plain text
980
980
981 :param text_:
981 :param text_:
982 :param repository:
982 :param repository:
983 :param link_: changeset link
983 :param link_: changeset link
984 """
984 """
985 import re
985 import re
986 import traceback
986 import traceback
987
987
988 def escaper(string):
988 def escaper(string):
989 return string.replace('<', '&lt;').replace('>', '&gt;')
989 return string.replace('<', '&lt;').replace('>', '&gt;')
990
990
991 def linkify_others(t, l):
991 def linkify_others(t, l):
992 urls = re.compile(r'(\<a.*?\<\/a\>)',)
992 urls = re.compile(r'(\<a.*?\<\/a\>)',)
993 links = []
993 links = []
994 for e in urls.split(t):
994 for e in urls.split(t):
995 if not urls.match(e):
995 if not urls.match(e):
996 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
996 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
997 else:
997 else:
998 links.append(e)
998 links.append(e)
999
999
1000 return ''.join(links)
1000 return ''.join(links)
1001
1001
1002 # urlify changesets - extrac revisions and make link out of them
1002 # urlify changesets - extrac revisions and make link out of them
1003 newtext = urlify_changesets(escaper(text_), repository)
1003 newtext = urlify_changesets(escaper(text_), repository)
1004
1004
1005 try:
1005 try:
1006 conf = config['app_conf']
1006 conf = config['app_conf']
1007
1007
1008 # allow multiple issue servers to be used
1008 # allow multiple issue servers to be used
1009 valid_indices = [
1009 valid_indices = [
1010 x.group(1)
1010 x.group(1)
1011 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1011 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1012 if x and 'issue_server_link%s' % x.group(1) in conf
1012 if x and 'issue_server_link%s' % x.group(1) in conf
1013 and 'issue_prefix%s' % x.group(1) in conf
1013 and 'issue_prefix%s' % x.group(1) in conf
1014 ]
1014 ]
1015
1015
1016 log.debug('found issue server suffixes `%s` during valuation of: %s'
1016 log.debug('found issue server suffixes `%s` during valuation of: %s'
1017 % (','.join(valid_indices), newtext))
1017 % (','.join(valid_indices), newtext))
1018
1018
1019 for pattern_index in valid_indices:
1019 for pattern_index in valid_indices:
1020 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1020 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1021 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1021 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1022 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1022 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1023
1023
1024 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1024 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1025 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1025 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1026 ISSUE_PREFIX))
1026 ISSUE_PREFIX))
1027
1027
1028 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1028 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1029
1029
1030 def url_func(match_obj):
1030 def url_func(match_obj):
1031 pref = ''
1031 pref = ''
1032 if match_obj.group().startswith(' '):
1032 if match_obj.group().startswith(' '):
1033 pref = ' '
1033 pref = ' '
1034
1034
1035 issue_id = ''.join(match_obj.groups())
1035 issue_id = ''.join(match_obj.groups())
1036 tmpl = (
1036 tmpl = (
1037 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1037 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1038 '%(issue-prefix)s%(id-repr)s'
1038 '%(issue-prefix)s%(id-repr)s'
1039 '</a>'
1039 '</a>'
1040 )
1040 )
1041 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1041 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1042 if repository:
1042 if repository:
1043 url = url.replace('{repo}', repository)
1043 url = url.replace('{repo}', repository)
1044 repo_name = repository.split(URL_SEP)[-1]
1044 repo_name = repository.split(URL_SEP)[-1]
1045 url = url.replace('{repo_name}', repo_name)
1045 url = url.replace('{repo_name}', repo_name)
1046
1046
1047 return tmpl % {
1047 return tmpl % {
1048 'pref': pref,
1048 'pref': pref,
1049 'cls': 'issue-tracker-link',
1049 'cls': 'issue-tracker-link',
1050 'url': url,
1050 'url': url,
1051 'id-repr': issue_id,
1051 'id-repr': issue_id,
1052 'issue-prefix': ISSUE_PREFIX,
1052 'issue-prefix': ISSUE_PREFIX,
1053 'serv': ISSUE_SERVER_LNK,
1053 'serv': ISSUE_SERVER_LNK,
1054 }
1054 }
1055 newtext = URL_PAT.sub(url_func, newtext)
1055 newtext = URL_PAT.sub(url_func, newtext)
1056 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1056 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1057
1057
1058 # if we actually did something above
1058 # if we actually did something above
1059 if valid_indices:
1059 if valid_indices:
1060 if link_:
1060 if link_:
1061 # wrap not links into final link => link_
1061 # wrap not links into final link => link_
1062 newtext = linkify_others(newtext, link_)
1062 newtext = linkify_others(newtext, link_)
1063
1063
1064 return literal(newtext)
1064 return literal(newtext)
1065 except:
1065 except:
1066 log.error(traceback.format_exc())
1066 log.error(traceback.format_exc())
1067 pass
1067 pass
1068
1068
1069 return newtext
1069 return newtext
1070
1070
1071
1071
1072 def rst(source):
1072 def rst(source):
1073 return literal('<div class="rst-block">%s</div>' %
1073 return literal('<div class="rst-block">%s</div>' %
1074 MarkupRenderer.rst(source))
1074 MarkupRenderer.rst(source))
1075
1075
1076
1076
1077 def rst_w_mentions(source):
1077 def rst_w_mentions(source):
1078 """
1078 """
1079 Wrapped rst renderer with @mention highlighting
1079 Wrapped rst renderer with @mention highlighting
1080
1080
1081 :param source:
1081 :param source:
1082 """
1082 """
1083 return literal('<div class="rst-block">%s</div>' %
1083 return literal('<div class="rst-block">%s</div>' %
1084 MarkupRenderer.rst_with_mentions(source))
1084 MarkupRenderer.rst_with_mentions(source))
1085
1085
1086
1086
1087 def changeset_status(repo, revision):
1087 def changeset_status(repo, revision):
1088 return ChangesetStatusModel().get_status(repo, revision)
1088 return ChangesetStatusModel().get_status(repo, revision)
1089
1089
1090
1090
1091 def changeset_status_lbl(changeset_status):
1091 def changeset_status_lbl(changeset_status):
1092 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1092 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1093
1093
1094
1094
1095 def get_permission_name(key):
1095 def get_permission_name(key):
1096 return dict(Permission.PERMS).get(key)
1096 return dict(Permission.PERMS).get(key)
General Comments 0
You need to be logged in to leave comments. Login now