##// END OF EJS Templates
fixes issue with whitespace for referenced tasks in commit messages
marcink -
r1912:32e1e074 beta
parent child Browse files
Show More
@@ -1,808 +1,813 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 vcs.utils import author_name, author_email
321 from 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 email_or_none(author):
332 def email_or_none(author):
333 _email = email(author)
333 _email = email(author)
334 if _email != '':
334 if _email != '':
335 return _email
335 return _email
336
336
337 # See if it contains a username we can get an email from
337 # See if it contains a username we can get an email from
338 user = User.get_by_username(author_name(author), case_insensitive=True,
338 user = User.get_by_username(author_name(author), case_insensitive=True,
339 cache=True)
339 cache=True)
340 if user is not None:
340 if user is not None:
341 return user.email
341 return user.email
342
342
343 # No valid email, not a valid user in the system, none!
343 # No valid email, not a valid user in the system, none!
344 return None
344 return None
345
345
346
346
347 def person(author):
347 def person(author):
348 # attr to return from fetched user
348 # attr to return from fetched user
349 person_getter = lambda usr: usr.username
349 person_getter = lambda usr: usr.username
350
350
351 # Valid email in the attribute passed, see if they're in the system
351 # Valid email in the attribute passed, see if they're in the system
352 _email = email(author)
352 _email = email(author)
353 if _email != '':
353 if _email != '':
354 user = User.get_by_email(_email, case_insensitive=True, cache=True)
354 user = User.get_by_email(_email, case_insensitive=True, cache=True)
355 if user is not None:
355 if user is not None:
356 return person_getter(user)
356 return person_getter(user)
357 return _email
357 return _email
358
358
359 # Maybe it's a username?
359 # Maybe it's a username?
360 _author = author_name(author)
360 _author = author_name(author)
361 user = User.get_by_username(_author, case_insensitive=True,
361 user = User.get_by_username(_author, case_insensitive=True,
362 cache=True)
362 cache=True)
363 if user is not None:
363 if user is not None:
364 return person_getter(user)
364 return person_getter(user)
365
365
366 # Still nothing? Just pass back the author name then
366 # Still nothing? Just pass back the author name then
367 return _author
367 return _author
368
368
369 def bool2icon(value):
369 def bool2icon(value):
370 """Returns True/False values represented as small html image of true/false
370 """Returns True/False values represented as small html image of true/false
371 icons
371 icons
372
372
373 :param value: bool value
373 :param value: bool value
374 """
374 """
375
375
376 if value is True:
376 if value is True:
377 return HTML.tag('img', src=url("/images/icons/accept.png"),
377 return HTML.tag('img', src=url("/images/icons/accept.png"),
378 alt=_('True'))
378 alt=_('True'))
379
379
380 if value is False:
380 if value is False:
381 return HTML.tag('img', src=url("/images/icons/cancel.png"),
381 return HTML.tag('img', src=url("/images/icons/cancel.png"),
382 alt=_('False'))
382 alt=_('False'))
383
383
384 return value
384 return value
385
385
386
386
387 def action_parser(user_log, feed=False):
387 def action_parser(user_log, feed=False):
388 """This helper will action_map the specified string action into translated
388 """This helper will action_map the specified string action into translated
389 fancy names with icons and links
389 fancy names with icons and links
390
390
391 :param user_log: user log instance
391 :param user_log: user log instance
392 :param feed: use output for feeds (no html and fancy icons)
392 :param feed: use output for feeds (no html and fancy icons)
393 """
393 """
394
394
395 action = user_log.action
395 action = user_log.action
396 action_params = ' '
396 action_params = ' '
397
397
398 x = action.split(':')
398 x = action.split(':')
399
399
400 if len(x) > 1:
400 if len(x) > 1:
401 action, action_params = x
401 action, action_params = x
402
402
403 def get_cs_links():
403 def get_cs_links():
404 revs_limit = 3 #display this amount always
404 revs_limit = 3 #display this amount always
405 revs_top_limit = 50 #show upto this amount of changesets hidden
405 revs_top_limit = 50 #show upto this amount of changesets hidden
406 revs = action_params.split(',')
406 revs = action_params.split(',')
407 repo_name = user_log.repository.repo_name
407 repo_name = user_log.repository.repo_name
408
408
409 from rhodecode.model.scm import ScmModel
409 from rhodecode.model.scm import ScmModel
410 repo = user_log.repository.scm_instance
410 repo = user_log.repository.scm_instance
411
411
412 message = lambda rev: get_changeset_safe(repo, rev).message
412 message = lambda rev: get_changeset_safe(repo, rev).message
413 cs_links = []
413 cs_links = []
414 cs_links.append(" " + ', '.join ([link_to(rev,
414 cs_links.append(" " + ', '.join ([link_to(rev,
415 url('changeset_home',
415 url('changeset_home',
416 repo_name=repo_name,
416 repo_name=repo_name,
417 revision=rev), title=tooltip(message(rev)),
417 revision=rev), title=tooltip(message(rev)),
418 class_='tooltip') for rev in revs[:revs_limit] ]))
418 class_='tooltip') for rev in revs[:revs_limit] ]))
419
419
420 compare_view = (' <div class="compare_view tooltip" title="%s">'
420 compare_view = (' <div class="compare_view tooltip" title="%s">'
421 '<a href="%s">%s</a> '
421 '<a href="%s">%s</a> '
422 '</div>' % (_('Show all combined changesets %s->%s' \
422 '</div>' % (_('Show all combined changesets %s->%s' \
423 % (revs[0], revs[-1])),
423 % (revs[0], revs[-1])),
424 url('changeset_home', repo_name=repo_name,
424 url('changeset_home', repo_name=repo_name,
425 revision='%s...%s' % (revs[0], revs[-1])
425 revision='%s...%s' % (revs[0], revs[-1])
426 ),
426 ),
427 _('compare view'))
427 _('compare view'))
428 )
428 )
429
429
430 if len(revs) > revs_limit:
430 if len(revs) > revs_limit:
431 uniq_id = revs[0]
431 uniq_id = revs[0]
432 html_tmpl = ('<span> %s '
432 html_tmpl = ('<span> %s '
433 '<a class="show_more" id="_%s" href="#more">%s</a> '
433 '<a class="show_more" id="_%s" href="#more">%s</a> '
434 '%s</span>')
434 '%s</span>')
435 if not feed:
435 if not feed:
436 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
436 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
437 % (len(revs) - revs_limit),
437 % (len(revs) - revs_limit),
438 _('revisions')))
438 _('revisions')))
439
439
440 if not feed:
440 if not feed:
441 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
441 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
442 else:
442 else:
443 html_tmpl = '<span id="%s"> %s </span>'
443 html_tmpl = '<span id="%s"> %s </span>'
444
444
445 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
445 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
446 url('changeset_home',
446 url('changeset_home',
447 repo_name=repo_name, revision=rev),
447 repo_name=repo_name, revision=rev),
448 title=message(rev), class_='tooltip')
448 title=message(rev), class_='tooltip')
449 for rev in revs[revs_limit:revs_top_limit]])))
449 for rev in revs[revs_limit:revs_top_limit]])))
450 if len(revs) > 1:
450 if len(revs) > 1:
451 cs_links.append(compare_view)
451 cs_links.append(compare_view)
452 return ''.join(cs_links)
452 return ''.join(cs_links)
453
453
454 def get_fork_name():
454 def get_fork_name():
455 repo_name = action_params
455 repo_name = action_params
456 return _('fork name ') + str(link_to(action_params, url('summary_home',
456 return _('fork name ') + str(link_to(action_params, url('summary_home',
457 repo_name=repo_name,)))
457 repo_name=repo_name,)))
458
458
459 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
459 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
460 'user_created_repo':(_('[created] repository'), None),
460 'user_created_repo':(_('[created] repository'), None),
461 'user_created_fork':(_('[created] repository as fork'), None),
461 'user_created_fork':(_('[created] repository as fork'), None),
462 'user_forked_repo':(_('[forked] repository'), get_fork_name),
462 'user_forked_repo':(_('[forked] repository'), get_fork_name),
463 'user_updated_repo':(_('[updated] repository'), None),
463 'user_updated_repo':(_('[updated] repository'), None),
464 'admin_deleted_repo':(_('[delete] repository'), None),
464 'admin_deleted_repo':(_('[delete] repository'), None),
465 'admin_created_repo':(_('[created] repository'), None),
465 'admin_created_repo':(_('[created] repository'), None),
466 'admin_forked_repo':(_('[forked] repository'), None),
466 'admin_forked_repo':(_('[forked] repository'), None),
467 'admin_updated_repo':(_('[updated] repository'), None),
467 'admin_updated_repo':(_('[updated] repository'), None),
468 'push':(_('[pushed] into'), get_cs_links),
468 'push':(_('[pushed] into'), get_cs_links),
469 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
469 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
470 'push_remote':(_('[pulled from remote] into'), get_cs_links),
470 'push_remote':(_('[pulled from remote] into'), get_cs_links),
471 'pull':(_('[pulled] from'), None),
471 'pull':(_('[pulled] from'), None),
472 'started_following_repo':(_('[started following] repository'), None),
472 'started_following_repo':(_('[started following] repository'), None),
473 'stopped_following_repo':(_('[stopped following] repository'), None),
473 'stopped_following_repo':(_('[stopped following] repository'), None),
474 }
474 }
475
475
476 action_str = action_map.get(action, action)
476 action_str = action_map.get(action, action)
477 if feed:
477 if feed:
478 action = action_str[0].replace('[', '').replace(']', '')
478 action = action_str[0].replace('[', '').replace(']', '')
479 else:
479 else:
480 action = action_str[0].replace('[', '<span class="journal_highlight">')\
480 action = action_str[0].replace('[', '<span class="journal_highlight">')\
481 .replace(']', '</span>')
481 .replace(']', '</span>')
482
482
483 action_params_func = lambda :""
483 action_params_func = lambda :""
484
484
485 if callable(action_str[1]):
485 if callable(action_str[1]):
486 action_params_func = action_str[1]
486 action_params_func = action_str[1]
487
487
488 return [literal(action), action_params_func]
488 return [literal(action), action_params_func]
489
489
490 def action_parser_icon(user_log):
490 def action_parser_icon(user_log):
491 action = user_log.action
491 action = user_log.action
492 action_params = None
492 action_params = None
493 x = action.split(':')
493 x = action.split(':')
494
494
495 if len(x) > 1:
495 if len(x) > 1:
496 action, action_params = x
496 action, action_params = x
497
497
498 tmpl = """<img src="%s%s" alt="%s"/>"""
498 tmpl = """<img src="%s%s" alt="%s"/>"""
499 map = {'user_deleted_repo':'database_delete.png',
499 map = {'user_deleted_repo':'database_delete.png',
500 'user_created_repo':'database_add.png',
500 'user_created_repo':'database_add.png',
501 'user_created_fork':'arrow_divide.png',
501 'user_created_fork':'arrow_divide.png',
502 'user_forked_repo':'arrow_divide.png',
502 'user_forked_repo':'arrow_divide.png',
503 'user_updated_repo':'database_edit.png',
503 'user_updated_repo':'database_edit.png',
504 'admin_deleted_repo':'database_delete.png',
504 'admin_deleted_repo':'database_delete.png',
505 'admin_created_repo':'database_add.png',
505 'admin_created_repo':'database_add.png',
506 'admin_forked_repo':'arrow_divide.png',
506 'admin_forked_repo':'arrow_divide.png',
507 'admin_updated_repo':'database_edit.png',
507 'admin_updated_repo':'database_edit.png',
508 'push':'script_add.png',
508 'push':'script_add.png',
509 'push_local':'script_edit.png',
509 'push_local':'script_edit.png',
510 'push_remote':'connect.png',
510 'push_remote':'connect.png',
511 'pull':'down_16.png',
511 'pull':'down_16.png',
512 'started_following_repo':'heart_add.png',
512 'started_following_repo':'heart_add.png',
513 'stopped_following_repo':'heart_delete.png',
513 'stopped_following_repo':'heart_delete.png',
514 }
514 }
515 return literal(tmpl % ((url('/images/icons/')),
515 return literal(tmpl % ((url('/images/icons/')),
516 map.get(action, action), action))
516 map.get(action, action), action))
517
517
518
518
519 #==============================================================================
519 #==============================================================================
520 # PERMS
520 # PERMS
521 #==============================================================================
521 #==============================================================================
522 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
522 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
523 HasRepoPermissionAny, HasRepoPermissionAll
523 HasRepoPermissionAny, HasRepoPermissionAll
524
524
525 #==============================================================================
525 #==============================================================================
526 # GRAVATAR URL
526 # GRAVATAR URL
527 #==============================================================================
527 #==============================================================================
528
528
529 def gravatar_url(email_address, size=30):
529 def gravatar_url(email_address, size=30):
530 if (not str2bool(config['app_conf'].get('use_gravatar')) or
530 if (not str2bool(config['app_conf'].get('use_gravatar')) or
531 not email_address or email_address == 'anonymous@rhodecode.org'):
531 not email_address or email_address == 'anonymous@rhodecode.org'):
532 return url("/images/user%s.png" % size)
532 return url("/images/user%s.png" % size)
533
533
534 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
534 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
535 default = 'identicon'
535 default = 'identicon'
536 baseurl_nossl = "http://www.gravatar.com/avatar/"
536 baseurl_nossl = "http://www.gravatar.com/avatar/"
537 baseurl_ssl = "https://secure.gravatar.com/avatar/"
537 baseurl_ssl = "https://secure.gravatar.com/avatar/"
538 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
538 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
539
539
540 if isinstance(email_address, unicode):
540 if isinstance(email_address, unicode):
541 #hashlib crashes on unicode items
541 #hashlib crashes on unicode items
542 email_address = safe_str(email_address)
542 email_address = safe_str(email_address)
543 # construct the url
543 # construct the url
544 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
544 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
545 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
545 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
546
546
547 return gravatar_url
547 return gravatar_url
548
548
549
549
550 #==============================================================================
550 #==============================================================================
551 # REPO PAGER, PAGER FOR REPOSITORY
551 # REPO PAGER, PAGER FOR REPOSITORY
552 #==============================================================================
552 #==============================================================================
553 class RepoPage(Page):
553 class RepoPage(Page):
554
554
555 def __init__(self, collection, page=1, items_per_page=20,
555 def __init__(self, collection, page=1, items_per_page=20,
556 item_count=None, url=None, **kwargs):
556 item_count=None, url=None, **kwargs):
557
557
558 """Create a "RepoPage" instance. special pager for paging
558 """Create a "RepoPage" instance. special pager for paging
559 repository
559 repository
560 """
560 """
561 self._url_generator = url
561 self._url_generator = url
562
562
563 # Safe the kwargs class-wide so they can be used in the pager() method
563 # Safe the kwargs class-wide so they can be used in the pager() method
564 self.kwargs = kwargs
564 self.kwargs = kwargs
565
565
566 # Save a reference to the collection
566 # Save a reference to the collection
567 self.original_collection = collection
567 self.original_collection = collection
568
568
569 self.collection = collection
569 self.collection = collection
570
570
571 # The self.page is the number of the current page.
571 # The self.page is the number of the current page.
572 # The first page has the number 1!
572 # The first page has the number 1!
573 try:
573 try:
574 self.page = int(page) # make it int() if we get it as a string
574 self.page = int(page) # make it int() if we get it as a string
575 except (ValueError, TypeError):
575 except (ValueError, TypeError):
576 self.page = 1
576 self.page = 1
577
577
578 self.items_per_page = items_per_page
578 self.items_per_page = items_per_page
579
579
580 # Unless the user tells us how many items the collections has
580 # Unless the user tells us how many items the collections has
581 # we calculate that ourselves.
581 # we calculate that ourselves.
582 if item_count is not None:
582 if item_count is not None:
583 self.item_count = item_count
583 self.item_count = item_count
584 else:
584 else:
585 self.item_count = len(self.collection)
585 self.item_count = len(self.collection)
586
586
587 # Compute the number of the first and last available page
587 # Compute the number of the first and last available page
588 if self.item_count > 0:
588 if self.item_count > 0:
589 self.first_page = 1
589 self.first_page = 1
590 self.page_count = int(math.ceil(float(self.item_count) /
590 self.page_count = int(math.ceil(float(self.item_count) /
591 self.items_per_page))
591 self.items_per_page))
592 self.last_page = self.first_page + self.page_count - 1
592 self.last_page = self.first_page + self.page_count - 1
593
593
594 # Make sure that the requested page number is the range of
594 # Make sure that the requested page number is the range of
595 # valid pages
595 # valid pages
596 if self.page > self.last_page:
596 if self.page > self.last_page:
597 self.page = self.last_page
597 self.page = self.last_page
598 elif self.page < self.first_page:
598 elif self.page < self.first_page:
599 self.page = self.first_page
599 self.page = self.first_page
600
600
601 # Note: the number of items on this page can be less than
601 # Note: the number of items on this page can be less than
602 # items_per_page if the last page is not full
602 # items_per_page if the last page is not full
603 self.first_item = max(0, (self.item_count) - (self.page *
603 self.first_item = max(0, (self.item_count) - (self.page *
604 items_per_page))
604 items_per_page))
605 self.last_item = ((self.item_count - 1) - items_per_page *
605 self.last_item = ((self.item_count - 1) - items_per_page *
606 (self.page - 1))
606 (self.page - 1))
607
607
608 self.items = list(self.collection[self.first_item:self.last_item + 1])
608 self.items = list(self.collection[self.first_item:self.last_item + 1])
609
609
610 # Links to previous and next page
610 # Links to previous and next page
611 if self.page > self.first_page:
611 if self.page > self.first_page:
612 self.previous_page = self.page - 1
612 self.previous_page = self.page - 1
613 else:
613 else:
614 self.previous_page = None
614 self.previous_page = None
615
615
616 if self.page < self.last_page:
616 if self.page < self.last_page:
617 self.next_page = self.page + 1
617 self.next_page = self.page + 1
618 else:
618 else:
619 self.next_page = None
619 self.next_page = None
620
620
621 # No items available
621 # No items available
622 else:
622 else:
623 self.first_page = None
623 self.first_page = None
624 self.page_count = 0
624 self.page_count = 0
625 self.last_page = None
625 self.last_page = None
626 self.first_item = None
626 self.first_item = None
627 self.last_item = None
627 self.last_item = None
628 self.previous_page = None
628 self.previous_page = None
629 self.next_page = None
629 self.next_page = None
630 self.items = []
630 self.items = []
631
631
632 # This is a subclass of the 'list' type. Initialise the list now.
632 # This is a subclass of the 'list' type. Initialise the list now.
633 list.__init__(self, reversed(self.items))
633 list.__init__(self, reversed(self.items))
634
634
635
635
636 def changed_tooltip(nodes):
636 def changed_tooltip(nodes):
637 """
637 """
638 Generates a html string for changed nodes in changeset page.
638 Generates a html string for changed nodes in changeset page.
639 It limits the output to 30 entries
639 It limits the output to 30 entries
640
640
641 :param nodes: LazyNodesGenerator
641 :param nodes: LazyNodesGenerator
642 """
642 """
643 if nodes:
643 if nodes:
644 pref = ': <br/> '
644 pref = ': <br/> '
645 suf = ''
645 suf = ''
646 if len(nodes) > 30:
646 if len(nodes) > 30:
647 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
647 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
648 return literal(pref + '<br/> '.join([safe_unicode(x.path)
648 return literal(pref + '<br/> '.join([safe_unicode(x.path)
649 for x in nodes[:30]]) + suf)
649 for x in nodes[:30]]) + suf)
650 else:
650 else:
651 return ': ' + _('No Files')
651 return ': ' + _('No Files')
652
652
653
653
654
654
655 def repo_link(groups_and_repos):
655 def repo_link(groups_and_repos):
656 """
656 """
657 Makes a breadcrumbs link to repo within a group
657 Makes a breadcrumbs link to repo within a group
658 joins &raquo; on each group to create a fancy link
658 joins &raquo; on each group to create a fancy link
659
659
660 ex::
660 ex::
661 group >> subgroup >> repo
661 group >> subgroup >> repo
662
662
663 :param groups_and_repos:
663 :param groups_and_repos:
664 """
664 """
665 groups, repo_name = groups_and_repos
665 groups, repo_name = groups_and_repos
666
666
667 if not groups:
667 if not groups:
668 return repo_name
668 return repo_name
669 else:
669 else:
670 def make_link(group):
670 def make_link(group):
671 return link_to(group.name, url('repos_group_home',
671 return link_to(group.name, url('repos_group_home',
672 group_name=group.group_name))
672 group_name=group.group_name))
673 return literal(' &raquo; '.join(map(make_link, groups)) + \
673 return literal(' &raquo; '.join(map(make_link, groups)) + \
674 " &raquo; " + repo_name)
674 " &raquo; " + repo_name)
675
675
676 def fancy_file_stats(stats):
676 def fancy_file_stats(stats):
677 """
677 """
678 Displays a fancy two colored bar for number of added/deleted
678 Displays a fancy two colored bar for number of added/deleted
679 lines of code on file
679 lines of code on file
680
680
681 :param stats: two element list of added/deleted lines of code
681 :param stats: two element list of added/deleted lines of code
682 """
682 """
683
683
684 a, d, t = stats[0], stats[1], stats[0] + stats[1]
684 a, d, t = stats[0], stats[1], stats[0] + stats[1]
685 width = 100
685 width = 100
686 unit = float(width) / (t or 1)
686 unit = float(width) / (t or 1)
687
687
688 # needs > 9% of width to be visible or 0 to be hidden
688 # needs > 9% of width to be visible or 0 to be hidden
689 a_p = max(9, unit * a) if a > 0 else 0
689 a_p = max(9, unit * a) if a > 0 else 0
690 d_p = max(9, unit * d) if d > 0 else 0
690 d_p = max(9, unit * d) if d > 0 else 0
691 p_sum = a_p + d_p
691 p_sum = a_p + d_p
692
692
693 if p_sum > width:
693 if p_sum > width:
694 #adjust the percentage to be == 100% since we adjusted to 9
694 #adjust the percentage to be == 100% since we adjusted to 9
695 if a_p > d_p:
695 if a_p > d_p:
696 a_p = a_p - (p_sum - width)
696 a_p = a_p - (p_sum - width)
697 else:
697 else:
698 d_p = d_p - (p_sum - width)
698 d_p = d_p - (p_sum - width)
699
699
700 a_v = a if a > 0 else ''
700 a_v = a if a > 0 else ''
701 d_v = d if d > 0 else ''
701 d_v = d if d > 0 else ''
702
702
703
703
704 def cgen(l_type):
704 def cgen(l_type):
705 mapping = {'tr':'top-right-rounded-corner',
705 mapping = {'tr':'top-right-rounded-corner',
706 'tl':'top-left-rounded-corner',
706 'tl':'top-left-rounded-corner',
707 'br':'bottom-right-rounded-corner',
707 'br':'bottom-right-rounded-corner',
708 'bl':'bottom-left-rounded-corner'}
708 'bl':'bottom-left-rounded-corner'}
709 map_getter = lambda x:mapping[x]
709 map_getter = lambda x:mapping[x]
710
710
711 if l_type == 'a' and d_v:
711 if l_type == 'a' and d_v:
712 #case when added and deleted are present
712 #case when added and deleted are present
713 return ' '.join(map(map_getter, ['tl', 'bl']))
713 return ' '.join(map(map_getter, ['tl', 'bl']))
714
714
715 if l_type == 'a' and not d_v:
715 if l_type == 'a' and not d_v:
716 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
716 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
717
717
718 if l_type == 'd' and a_v:
718 if l_type == 'd' and a_v:
719 return ' '.join(map(map_getter, ['tr', 'br']))
719 return ' '.join(map(map_getter, ['tr', 'br']))
720
720
721 if l_type == 'd' and not a_v:
721 if l_type == 'd' and not a_v:
722 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
722 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
723
723
724
724
725
725
726 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
726 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
727 a_p, a_v)
727 a_p, a_v)
728 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
728 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
729 d_p, d_v)
729 d_p, d_v)
730 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
730 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
731
731
732
732
733 def urlify_text(text_):
733 def urlify_text(text_):
734 import re
734 import re
735
735
736 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
736 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
737 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
737 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
738
738
739 def url_func(match_obj):
739 def url_func(match_obj):
740 url_full = match_obj.groups()[0]
740 url_full = match_obj.groups()[0]
741 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
741 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
742
742
743 return literal(url_pat.sub(url_func, text_))
743 return literal(url_pat.sub(url_func, text_))
744
744
745
745
746 def urlify_commit(text_, repository=None, link_=None):
746 def urlify_commit(text_, repository=None, link_=None):
747 import re
747 import re
748 import traceback
748 import traceback
749
749
750 if link_:
750 if link_:
751 link_ = '<a href="' + link_ + '">'
751 link_ = '<a href="' + link_ + '">'
752
752
753 try:
753 try:
754 conf = config['app_conf']
754 conf = config['app_conf']
755
755
756 URL_PAT = re.compile(r'%s' % conf.get('url_pat'))
756 URL_PAT = re.compile(r'%s' % conf.get('url_pat'))
757
757
758 if URL_PAT:
758 if URL_PAT:
759 ISSUE_SERVER_LNK = conf.get('issue_server_link')
759 ISSUE_SERVER_LNK = conf.get('issue_server_link')
760 ISSUE_PREFIX = conf.get('issue_prefix')
760 ISSUE_PREFIX = conf.get('issue_prefix')
761
761
762 def url_func(match_obj):
762 def url_func(match_obj):
763 pref = ''
764 if match_obj.group().startswith(' '):
765 pref = ' '
766
763 issue_id = match_obj.groups()[0]
767 issue_id = match_obj.groups()[0]
764 tmpl = (
768 tmpl = (
765 ' <a class="%(cls)s" href="%(url)s">'
769 '%(pref)s<a class="%(cls)s" href="%(url)s">'
766 '%(issue-prefix)s%(id-repr)s'
770 '%(issue-prefix)s%(id-repr)s'
767 '</a>'
771 '</a>'
768 )
772 )
769 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
773 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
770 if repository:
774 if repository:
771 url = url.replace('{repo}', repository)
775 url = url.replace('{repo}', repository)
772
776
773 if link_:
777 if link_:
774 tmpl = '</a>' + tmpl + link_
778 tmpl = '</a>' + tmpl + link_
775
779
776 return tmpl % (
780 return tmpl % (
777 {
781 {
782 'pref': pref,
778 'cls': 'issue-tracker-link',
783 'cls': 'issue-tracker-link',
779 'url': url,
784 'url': url,
780 'id-repr': issue_id,
785 'id-repr': issue_id,
781 'issue-prefix': ISSUE_PREFIX,
786 'issue-prefix': ISSUE_PREFIX,
782 'serv': ISSUE_SERVER_LNK,
787 'serv': ISSUE_SERVER_LNK,
783 }
788 }
784 )
789 )
785 newtext = URL_PAT.sub(url_func, text_)
790 newtext = URL_PAT.sub(url_func, text_)
786 if link_:
791 if link_:
787 newtext = link_ + newtext + '</a>'
792 newtext = link_ + newtext + '</a>'
788 return literal(newtext)
793 return literal(newtext)
789 except:
794 except:
790 log.error(traceback.format_exc())
795 log.error(traceback.format_exc())
791 pass
796 pass
792
797
793 return text_
798 return text_
794
799
795
800
796 def rst(source):
801 def rst(source):
797 return literal('<div class="rst-block">%s</div>' %
802 return literal('<div class="rst-block">%s</div>' %
798 MarkupRenderer.rst(source))
803 MarkupRenderer.rst(source))
799
804
800
805
801 def rst_w_mentions(source):
806 def rst_w_mentions(source):
802 """
807 """
803 Wrapped rst renderer with @mention highlighting
808 Wrapped rst renderer with @mention highlighting
804
809
805 :param source:
810 :param source:
806 """
811 """
807 return literal('<div class="rst-block">%s</div>' %
812 return literal('<div class="rst-block">%s</div>' %
808 MarkupRenderer.rst_with_mentions(source))
813 MarkupRenderer.rst_with_mentions(source))
General Comments 0
You need to be logged in to leave comments. Login now