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