##// END OF EJS Templates
fixed issues when urlify commit created empty links when link_ param was empty
marcink -
r2028:ef3114b0 beta
parent child Browse files
Show More
@@ -1,904 +1,914 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 """
409 """
410 This helper will action_map the specified string action into translated
410 This helper will action_map the specified string action into translated
411 fancy names with icons and links
411 fancy names with icons and links
412
412
413 :param user_log: user log instance
413 :param user_log: user log instance
414 :param feed: use output for feeds (no html and fancy icons)
414 :param feed: use output for feeds (no html and fancy icons)
415 """
415 """
416
416
417 action = user_log.action
417 action = user_log.action
418 action_params = ' '
418 action_params = ' '
419
419
420 x = action.split(':')
420 x = action.split(':')
421
421
422 if len(x) > 1:
422 if len(x) > 1:
423 action, action_params = x
423 action, action_params = x
424
424
425 def get_cs_links():
425 def get_cs_links():
426 revs_limit = 3 # display this amount always
426 revs_limit = 3 # display this amount always
427 revs_top_limit = 50 # show upto this amount of changesets hidden
427 revs_top_limit = 50 # show upto this amount of changesets hidden
428 revs_ids = action_params.split(',')
428 revs_ids = action_params.split(',')
429 deleted = user_log.repository is None
429 deleted = user_log.repository is None
430 if deleted:
430 if deleted:
431 return ','.join(revs_ids)
431 return ','.join(revs_ids)
432
432
433 repo_name = user_log.repository.repo_name
433 repo_name = user_log.repository.repo_name
434
434
435 repo = user_log.repository.scm_instance
435 repo = user_log.repository.scm_instance
436
436
437 message = lambda rev: rev.message
437 message = lambda rev: rev.message
438 lnk = lambda rev, repo_name: (
438 lnk = lambda rev, repo_name: (
439 link_to('r%s:%s' % (rev.revision, rev.short_id),
439 link_to('r%s:%s' % (rev.revision, rev.short_id),
440 url('changeset_home', repo_name=repo_name,
440 url('changeset_home', repo_name=repo_name,
441 revision=rev.raw_id),
441 revision=rev.raw_id),
442 title=tooltip(message(rev)), class_='tooltip')
442 title=tooltip(message(rev)), class_='tooltip')
443 )
443 )
444 # get only max revs_top_limit of changeset for performance/ui reasons
444 # get only max revs_top_limit of changeset for performance/ui reasons
445 revs = [
445 revs = [
446 x for x in repo.get_changesets(revs_ids[0],
446 x for x in repo.get_changesets(revs_ids[0],
447 revs_ids[:revs_top_limit][-1])
447 revs_ids[:revs_top_limit][-1])
448 ]
448 ]
449
449
450 cs_links = []
450 cs_links = []
451 cs_links.append(" " + ', '.join(
451 cs_links.append(" " + ', '.join(
452 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
452 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
453 )
453 )
454 )
454 )
455
455
456 compare_view = (
456 compare_view = (
457 ' <div class="compare_view tooltip" title="%s">'
457 ' <div class="compare_view tooltip" title="%s">'
458 '<a href="%s">%s</a> </div>' % (
458 '<a href="%s">%s</a> </div>' % (
459 _('Show all combined changesets %s->%s') % (
459 _('Show all combined changesets %s->%s') % (
460 revs_ids[0], revs_ids[-1]
460 revs_ids[0], revs_ids[-1]
461 ),
461 ),
462 url('changeset_home', repo_name=repo_name,
462 url('changeset_home', repo_name=repo_name,
463 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
463 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
464 ),
464 ),
465 _('compare view')
465 _('compare view')
466 )
466 )
467 )
467 )
468
468
469 # if we have exactly one more than normally displayed
469 # if we have exactly one more than normally displayed
470 # just display it, takes less space than displaying
470 # just display it, takes less space than displaying
471 # "and 1 more revisions"
471 # "and 1 more revisions"
472 if len(revs_ids) == revs_limit + 1:
472 if len(revs_ids) == revs_limit + 1:
473 rev = revs[revs_limit]
473 rev = revs[revs_limit]
474 cs_links.append(", " + lnk(rev, repo_name))
474 cs_links.append(", " + lnk(rev, repo_name))
475
475
476 # hidden-by-default ones
476 # hidden-by-default ones
477 if len(revs_ids) > revs_limit + 1:
477 if len(revs_ids) > revs_limit + 1:
478 uniq_id = revs_ids[0]
478 uniq_id = revs_ids[0]
479 html_tmpl = (
479 html_tmpl = (
480 '<span> %s <a class="show_more" id="_%s" '
480 '<span> %s <a class="show_more" id="_%s" '
481 'href="#more">%s</a> %s</span>'
481 'href="#more">%s</a> %s</span>'
482 )
482 )
483 if not feed:
483 if not feed:
484 cs_links.append(html_tmpl % (
484 cs_links.append(html_tmpl % (
485 _('and'),
485 _('and'),
486 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
486 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
487 _('revisions')
487 _('revisions')
488 )
488 )
489 )
489 )
490
490
491 if not feed:
491 if not feed:
492 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
492 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
493 else:
493 else:
494 html_tmpl = '<span id="%s"> %s </span>'
494 html_tmpl = '<span id="%s"> %s </span>'
495
495
496 morelinks = ', '.join(
496 morelinks = ', '.join(
497 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
497 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
498 )
498 )
499
499
500 if len(revs_ids) > revs_top_limit:
500 if len(revs_ids) > revs_top_limit:
501 morelinks += ', ...'
501 morelinks += ', ...'
502
502
503 cs_links.append(html_tmpl % (uniq_id, morelinks))
503 cs_links.append(html_tmpl % (uniq_id, morelinks))
504 if len(revs) > 1:
504 if len(revs) > 1:
505 cs_links.append(compare_view)
505 cs_links.append(compare_view)
506 return ''.join(cs_links)
506 return ''.join(cs_links)
507
507
508 def get_fork_name():
508 def get_fork_name():
509 repo_name = action_params
509 repo_name = action_params
510 return _('fork name ') + str(link_to(action_params, url('summary_home',
510 return _('fork name ') + str(link_to(action_params, url('summary_home',
511 repo_name=repo_name,)))
511 repo_name=repo_name,)))
512
512
513 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
513 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
514 'user_created_repo': (_('[created] repository'), None),
514 'user_created_repo': (_('[created] repository'), None),
515 'user_created_fork': (_('[created] repository as fork'), None),
515 'user_created_fork': (_('[created] repository as fork'), None),
516 'user_forked_repo': (_('[forked] repository'), get_fork_name),
516 'user_forked_repo': (_('[forked] repository'), get_fork_name),
517 'user_updated_repo': (_('[updated] repository'), None),
517 'user_updated_repo': (_('[updated] repository'), None),
518 'admin_deleted_repo': (_('[delete] repository'), None),
518 'admin_deleted_repo': (_('[delete] repository'), None),
519 'admin_created_repo': (_('[created] repository'), None),
519 'admin_created_repo': (_('[created] repository'), None),
520 'admin_forked_repo': (_('[forked] repository'), None),
520 'admin_forked_repo': (_('[forked] repository'), None),
521 'admin_updated_repo': (_('[updated] repository'), None),
521 'admin_updated_repo': (_('[updated] repository'), None),
522 'push': (_('[pushed] into'), get_cs_links),
522 'push': (_('[pushed] into'), get_cs_links),
523 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
523 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
524 'push_remote': (_('[pulled from remote] into'), get_cs_links),
524 'push_remote': (_('[pulled from remote] into'), get_cs_links),
525 'pull': (_('[pulled] from'), None),
525 'pull': (_('[pulled] from'), None),
526 'started_following_repo': (_('[started following] repository'), None),
526 'started_following_repo': (_('[started following] repository'), None),
527 'stopped_following_repo': (_('[stopped following] repository'), None),
527 'stopped_following_repo': (_('[stopped following] repository'), None),
528 }
528 }
529
529
530 action_str = action_map.get(action, action)
530 action_str = action_map.get(action, action)
531 if feed:
531 if feed:
532 action = action_str[0].replace('[', '').replace(']', '')
532 action = action_str[0].replace('[', '').replace(']', '')
533 else:
533 else:
534 action = action_str[0]\
534 action = action_str[0]\
535 .replace('[', '<span class="journal_highlight">')\
535 .replace('[', '<span class="journal_highlight">')\
536 .replace(']', '</span>')
536 .replace(']', '</span>')
537
537
538 action_params_func = lambda: ""
538 action_params_func = lambda: ""
539
539
540 if callable(action_str[1]):
540 if callable(action_str[1]):
541 action_params_func = action_str[1]
541 action_params_func = action_str[1]
542
542
543 return [literal(action), action_params_func]
543 return [literal(action), action_params_func]
544
544
545
545
546 def action_parser_icon(user_log):
546 def action_parser_icon(user_log):
547 action = user_log.action
547 action = user_log.action
548 action_params = None
548 action_params = None
549 x = action.split(':')
549 x = action.split(':')
550
550
551 if len(x) > 1:
551 if len(x) > 1:
552 action, action_params = x
552 action, action_params = x
553
553
554 tmpl = """<img src="%s%s" alt="%s"/>"""
554 tmpl = """<img src="%s%s" alt="%s"/>"""
555 map = {'user_deleted_repo':'database_delete.png',
555 map = {'user_deleted_repo':'database_delete.png',
556 'user_created_repo':'database_add.png',
556 'user_created_repo':'database_add.png',
557 'user_created_fork':'arrow_divide.png',
557 'user_created_fork':'arrow_divide.png',
558 'user_forked_repo':'arrow_divide.png',
558 'user_forked_repo':'arrow_divide.png',
559 'user_updated_repo':'database_edit.png',
559 'user_updated_repo':'database_edit.png',
560 'admin_deleted_repo':'database_delete.png',
560 'admin_deleted_repo':'database_delete.png',
561 'admin_created_repo':'database_add.png',
561 'admin_created_repo':'database_add.png',
562 'admin_forked_repo':'arrow_divide.png',
562 'admin_forked_repo':'arrow_divide.png',
563 'admin_updated_repo':'database_edit.png',
563 'admin_updated_repo':'database_edit.png',
564 'push':'script_add.png',
564 'push':'script_add.png',
565 'push_local':'script_edit.png',
565 'push_local':'script_edit.png',
566 'push_remote':'connect.png',
566 'push_remote':'connect.png',
567 'pull':'down_16.png',
567 'pull':'down_16.png',
568 'started_following_repo':'heart_add.png',
568 'started_following_repo':'heart_add.png',
569 'stopped_following_repo':'heart_delete.png',
569 'stopped_following_repo':'heart_delete.png',
570 }
570 }
571 return literal(tmpl % ((url('/images/icons/')),
571 return literal(tmpl % ((url('/images/icons/')),
572 map.get(action, action), action))
572 map.get(action, action), action))
573
573
574
574
575 #==============================================================================
575 #==============================================================================
576 # PERMS
576 # PERMS
577 #==============================================================================
577 #==============================================================================
578 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
578 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
579 HasRepoPermissionAny, HasRepoPermissionAll
579 HasRepoPermissionAny, HasRepoPermissionAll
580
580
581
581
582 #==============================================================================
582 #==============================================================================
583 # GRAVATAR URL
583 # GRAVATAR URL
584 #==============================================================================
584 #==============================================================================
585
585
586 def gravatar_url(email_address, size=30):
586 def gravatar_url(email_address, size=30):
587 if (not str2bool(config['app_conf'].get('use_gravatar')) or
587 if (not str2bool(config['app_conf'].get('use_gravatar')) or
588 not email_address or email_address == 'anonymous@rhodecode.org'):
588 not email_address or email_address == 'anonymous@rhodecode.org'):
589 f = lambda a, l: min(l, key=lambda x: abs(x - a))
589 f = lambda a, l: min(l, key=lambda x: abs(x - a))
590 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
590 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
591
591
592 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
592 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
593 default = 'identicon'
593 default = 'identicon'
594 baseurl_nossl = "http://www.gravatar.com/avatar/"
594 baseurl_nossl = "http://www.gravatar.com/avatar/"
595 baseurl_ssl = "https://secure.gravatar.com/avatar/"
595 baseurl_ssl = "https://secure.gravatar.com/avatar/"
596 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
596 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
597
597
598 if isinstance(email_address, unicode):
598 if isinstance(email_address, unicode):
599 #hashlib crashes on unicode items
599 #hashlib crashes on unicode items
600 email_address = safe_str(email_address)
600 email_address = safe_str(email_address)
601 # construct the url
601 # construct the url
602 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
602 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
603 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
603 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
604
604
605 return gravatar_url
605 return gravatar_url
606
606
607
607
608 #==============================================================================
608 #==============================================================================
609 # REPO PAGER, PAGER FOR REPOSITORY
609 # REPO PAGER, PAGER FOR REPOSITORY
610 #==============================================================================
610 #==============================================================================
611 class RepoPage(Page):
611 class RepoPage(Page):
612
612
613 def __init__(self, collection, page=1, items_per_page=20,
613 def __init__(self, collection, page=1, items_per_page=20,
614 item_count=None, url=None, **kwargs):
614 item_count=None, url=None, **kwargs):
615
615
616 """Create a "RepoPage" instance. special pager for paging
616 """Create a "RepoPage" instance. special pager for paging
617 repository
617 repository
618 """
618 """
619 self._url_generator = url
619 self._url_generator = url
620
620
621 # Safe the kwargs class-wide so they can be used in the pager() method
621 # Safe the kwargs class-wide so they can be used in the pager() method
622 self.kwargs = kwargs
622 self.kwargs = kwargs
623
623
624 # Save a reference to the collection
624 # Save a reference to the collection
625 self.original_collection = collection
625 self.original_collection = collection
626
626
627 self.collection = collection
627 self.collection = collection
628
628
629 # The self.page is the number of the current page.
629 # The self.page is the number of the current page.
630 # The first page has the number 1!
630 # The first page has the number 1!
631 try:
631 try:
632 self.page = int(page) # make it int() if we get it as a string
632 self.page = int(page) # make it int() if we get it as a string
633 except (ValueError, TypeError):
633 except (ValueError, TypeError):
634 self.page = 1
634 self.page = 1
635
635
636 self.items_per_page = items_per_page
636 self.items_per_page = items_per_page
637
637
638 # Unless the user tells us how many items the collections has
638 # Unless the user tells us how many items the collections has
639 # we calculate that ourselves.
639 # we calculate that ourselves.
640 if item_count is not None:
640 if item_count is not None:
641 self.item_count = item_count
641 self.item_count = item_count
642 else:
642 else:
643 self.item_count = len(self.collection)
643 self.item_count = len(self.collection)
644
644
645 # Compute the number of the first and last available page
645 # Compute the number of the first and last available page
646 if self.item_count > 0:
646 if self.item_count > 0:
647 self.first_page = 1
647 self.first_page = 1
648 self.page_count = int(math.ceil(float(self.item_count) /
648 self.page_count = int(math.ceil(float(self.item_count) /
649 self.items_per_page))
649 self.items_per_page))
650 self.last_page = self.first_page + self.page_count - 1
650 self.last_page = self.first_page + self.page_count - 1
651
651
652 # Make sure that the requested page number is the range of
652 # Make sure that the requested page number is the range of
653 # valid pages
653 # valid pages
654 if self.page > self.last_page:
654 if self.page > self.last_page:
655 self.page = self.last_page
655 self.page = self.last_page
656 elif self.page < self.first_page:
656 elif self.page < self.first_page:
657 self.page = self.first_page
657 self.page = self.first_page
658
658
659 # Note: the number of items on this page can be less than
659 # Note: the number of items on this page can be less than
660 # items_per_page if the last page is not full
660 # items_per_page if the last page is not full
661 self.first_item = max(0, (self.item_count) - (self.page *
661 self.first_item = max(0, (self.item_count) - (self.page *
662 items_per_page))
662 items_per_page))
663 self.last_item = ((self.item_count - 1) - items_per_page *
663 self.last_item = ((self.item_count - 1) - items_per_page *
664 (self.page - 1))
664 (self.page - 1))
665
665
666 self.items = list(self.collection[self.first_item:self.last_item + 1])
666 self.items = list(self.collection[self.first_item:self.last_item + 1])
667
667
668 # Links to previous and next page
668 # Links to previous and next page
669 if self.page > self.first_page:
669 if self.page > self.first_page:
670 self.previous_page = self.page - 1
670 self.previous_page = self.page - 1
671 else:
671 else:
672 self.previous_page = None
672 self.previous_page = None
673
673
674 if self.page < self.last_page:
674 if self.page < self.last_page:
675 self.next_page = self.page + 1
675 self.next_page = self.page + 1
676 else:
676 else:
677 self.next_page = None
677 self.next_page = None
678
678
679 # No items available
679 # No items available
680 else:
680 else:
681 self.first_page = None
681 self.first_page = None
682 self.page_count = 0
682 self.page_count = 0
683 self.last_page = None
683 self.last_page = None
684 self.first_item = None
684 self.first_item = None
685 self.last_item = None
685 self.last_item = None
686 self.previous_page = None
686 self.previous_page = None
687 self.next_page = None
687 self.next_page = None
688 self.items = []
688 self.items = []
689
689
690 # This is a subclass of the 'list' type. Initialise the list now.
690 # This is a subclass of the 'list' type. Initialise the list now.
691 list.__init__(self, reversed(self.items))
691 list.__init__(self, reversed(self.items))
692
692
693
693
694 def changed_tooltip(nodes):
694 def changed_tooltip(nodes):
695 """
695 """
696 Generates a html string for changed nodes in changeset page.
696 Generates a html string for changed nodes in changeset page.
697 It limits the output to 30 entries
697 It limits the output to 30 entries
698
698
699 :param nodes: LazyNodesGenerator
699 :param nodes: LazyNodesGenerator
700 """
700 """
701 if nodes:
701 if nodes:
702 pref = ': <br/> '
702 pref = ': <br/> '
703 suf = ''
703 suf = ''
704 if len(nodes) > 30:
704 if len(nodes) > 30:
705 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
705 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
706 return literal(pref + '<br/> '.join([safe_unicode(x.path)
706 return literal(pref + '<br/> '.join([safe_unicode(x.path)
707 for x in nodes[:30]]) + suf)
707 for x in nodes[:30]]) + suf)
708 else:
708 else:
709 return ': ' + _('No Files')
709 return ': ' + _('No Files')
710
710
711
711
712 def repo_link(groups_and_repos):
712 def repo_link(groups_and_repos):
713 """
713 """
714 Makes a breadcrumbs link to repo within a group
714 Makes a breadcrumbs link to repo within a group
715 joins &raquo; on each group to create a fancy link
715 joins &raquo; on each group to create a fancy link
716
716
717 ex::
717 ex::
718 group >> subgroup >> repo
718 group >> subgroup >> repo
719
719
720 :param groups_and_repos:
720 :param groups_and_repos:
721 """
721 """
722 groups, repo_name = groups_and_repos
722 groups, repo_name = groups_and_repos
723
723
724 if not groups:
724 if not groups:
725 return repo_name
725 return repo_name
726 else:
726 else:
727 def make_link(group):
727 def make_link(group):
728 return link_to(group.name, url('repos_group_home',
728 return link_to(group.name, url('repos_group_home',
729 group_name=group.group_name))
729 group_name=group.group_name))
730 return literal(' &raquo; '.join(map(make_link, groups)) + \
730 return literal(' &raquo; '.join(map(make_link, groups)) + \
731 " &raquo; " + repo_name)
731 " &raquo; " + repo_name)
732
732
733
733
734 def fancy_file_stats(stats):
734 def fancy_file_stats(stats):
735 """
735 """
736 Displays a fancy two colored bar for number of added/deleted
736 Displays a fancy two colored bar for number of added/deleted
737 lines of code on file
737 lines of code on file
738
738
739 :param stats: two element list of added/deleted lines of code
739 :param stats: two element list of added/deleted lines of code
740 """
740 """
741
741
742 a, d, t = stats[0], stats[1], stats[0] + stats[1]
742 a, d, t = stats[0], stats[1], stats[0] + stats[1]
743 width = 100
743 width = 100
744 unit = float(width) / (t or 1)
744 unit = float(width) / (t or 1)
745
745
746 # needs > 9% of width to be visible or 0 to be hidden
746 # needs > 9% of width to be visible or 0 to be hidden
747 a_p = max(9, unit * a) if a > 0 else 0
747 a_p = max(9, unit * a) if a > 0 else 0
748 d_p = max(9, unit * d) if d > 0 else 0
748 d_p = max(9, unit * d) if d > 0 else 0
749 p_sum = a_p + d_p
749 p_sum = a_p + d_p
750
750
751 if p_sum > width:
751 if p_sum > width:
752 #adjust the percentage to be == 100% since we adjusted to 9
752 #adjust the percentage to be == 100% since we adjusted to 9
753 if a_p > d_p:
753 if a_p > d_p:
754 a_p = a_p - (p_sum - width)
754 a_p = a_p - (p_sum - width)
755 else:
755 else:
756 d_p = d_p - (p_sum - width)
756 d_p = d_p - (p_sum - width)
757
757
758 a_v = a if a > 0 else ''
758 a_v = a if a > 0 else ''
759 d_v = d if d > 0 else ''
759 d_v = d if d > 0 else ''
760
760
761 def cgen(l_type):
761 def cgen(l_type):
762 mapping = {'tr': 'top-right-rounded-corner',
762 mapping = {'tr': 'top-right-rounded-corner',
763 'tl': 'top-left-rounded-corner',
763 'tl': 'top-left-rounded-corner',
764 'br': 'bottom-right-rounded-corner',
764 'br': 'bottom-right-rounded-corner',
765 'bl': 'bottom-left-rounded-corner'}
765 'bl': 'bottom-left-rounded-corner'}
766 map_getter = lambda x: mapping[x]
766 map_getter = lambda x: mapping[x]
767
767
768 if l_type == 'a' and d_v:
768 if l_type == 'a' and d_v:
769 #case when added and deleted are present
769 #case when added and deleted are present
770 return ' '.join(map(map_getter, ['tl', 'bl']))
770 return ' '.join(map(map_getter, ['tl', 'bl']))
771
771
772 if l_type == 'a' and not d_v:
772 if l_type == 'a' and not d_v:
773 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
773 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
774
774
775 if l_type == 'd' and a_v:
775 if l_type == 'd' and a_v:
776 return ' '.join(map(map_getter, ['tr', 'br']))
776 return ' '.join(map(map_getter, ['tr', 'br']))
777
777
778 if l_type == 'd' and not a_v:
778 if l_type == 'd' and not a_v:
779 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
779 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
780
780
781 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
781 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
782 cgen('a'), a_p, a_v
782 cgen('a'), a_p, a_v
783 )
783 )
784 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
784 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
785 cgen('d'), d_p, d_v
785 cgen('d'), d_p, d_v
786 )
786 )
787 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
787 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
788
788
789
789
790 def urlify_text(text_):
790 def urlify_text(text_):
791 import re
791 import re
792
792
793 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
793 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
794 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
794 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
795
795
796 def url_func(match_obj):
796 def url_func(match_obj):
797 url_full = match_obj.groups()[0]
797 url_full = match_obj.groups()[0]
798 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
798 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
799
799
800 return literal(url_pat.sub(url_func, text_))
800 return literal(url_pat.sub(url_func, text_))
801
801
802
802
803 def urlify_changesets(text_, repository):
803 def urlify_changesets(text_, repository):
804 import re
804 import re
805 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
805 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
806
806
807 def url_func(match_obj):
807 def url_func(match_obj):
808 rev = match_obj.groups()[0]
808 rev = match_obj.groups()[0]
809 pref = ''
809 pref = ''
810 if match_obj.group().startswith(' '):
810 if match_obj.group().startswith(' '):
811 pref = ' '
811 pref = ' '
812 tmpl = (
812 tmpl = (
813 '%(pref)s<a class="%(cls)s" href="%(url)s">'
813 '%(pref)s<a class="%(cls)s" href="%(url)s">'
814 '%(rev)s'
814 '%(rev)s'
815 '</a>'
815 '</a>'
816 )
816 )
817 return tmpl % {
817 return tmpl % {
818 'pref': pref,
818 'pref': pref,
819 'cls': 'revision-link',
819 'cls': 'revision-link',
820 'url': url('changeset_home', repo_name=repository, revision=rev),
820 'url': url('changeset_home', repo_name=repository, revision=rev),
821 'rev': rev,
821 'rev': rev,
822 }
822 }
823
823
824 newtext = URL_PAT.sub(url_func, text_)
824 newtext = URL_PAT.sub(url_func, text_)
825
825
826 return newtext
826 return newtext
827
827
828
828
829 def urlify_commit(text_, repository=None, link_=None):
829 def urlify_commit(text_, repository=None, link_=None):
830 """
831 Parses given text message and makes proper links.
832 issues are linked to given issue-server, and rest is a changeset link
833 if link_ is given, in other case it's a plain text
834
835 :param text_:
836 :param repository:
837 :param link_: changeset link
838 """
830 import re
839 import re
831 import traceback
840 import traceback
832
841
833 # urlify changesets
842 # urlify changesets
834 text_ = urlify_changesets(text_, repository)
843 text_ = urlify_changesets(text_, repository)
835
844
836 def linkify_others(t, l):
845 def linkify_others(t, l):
837 urls = re.compile(r'(\<a.*?\<\/a\>)',)
846 urls = re.compile(r'(\<a.*?\<\/a\>)',)
838 links = []
847 links = []
839 for e in urls.split(t):
848 for e in urls.split(t):
840 if not urls.match(e):
849 if not urls.match(e):
841 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
850 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
842 else:
851 else:
843 links.append(e)
852 links.append(e)
844
853
845 return ''.join(links)
854 return ''.join(links)
846 try:
855 try:
847 conf = config['app_conf']
856 conf = config['app_conf']
848
857
849 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
858 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
850
859
851 if URL_PAT:
860 if URL_PAT:
852 ISSUE_SERVER_LNK = conf.get('issue_server_link')
861 ISSUE_SERVER_LNK = conf.get('issue_server_link')
853 ISSUE_PREFIX = conf.get('issue_prefix')
862 ISSUE_PREFIX = conf.get('issue_prefix')
854
863
855 def url_func(match_obj):
864 def url_func(match_obj):
856 pref = ''
865 pref = ''
857 if match_obj.group().startswith(' '):
866 if match_obj.group().startswith(' '):
858 pref = ' '
867 pref = ' '
859
868
860 issue_id = ''.join(match_obj.groups())
869 issue_id = ''.join(match_obj.groups())
861 tmpl = (
870 tmpl = (
862 '%(pref)s<a class="%(cls)s" href="%(url)s">'
871 '%(pref)s<a class="%(cls)s" href="%(url)s">'
863 '%(issue-prefix)s%(id-repr)s'
872 '%(issue-prefix)s%(id-repr)s'
864 '</a>'
873 '</a>'
865 )
874 )
866 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
875 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
867 if repository:
876 if repository:
868 url = url.replace('{repo}', repository)
877 url = url.replace('{repo}', repository)
869
878
870 return tmpl % {
879 return tmpl % {
871 'pref': pref,
880 'pref': pref,
872 'cls': 'issue-tracker-link',
881 'cls': 'issue-tracker-link',
873 'url': url,
882 'url': url,
874 'id-repr': issue_id,
883 'id-repr': issue_id,
875 'issue-prefix': ISSUE_PREFIX,
884 'issue-prefix': ISSUE_PREFIX,
876 'serv': ISSUE_SERVER_LNK,
885 'serv': ISSUE_SERVER_LNK,
877 }
886 }
878
887
879 newtext = URL_PAT.sub(url_func, text_)
888 newtext = URL_PAT.sub(url_func, text_)
880
889
881 # wrap not links into final link => link_
890 if link_:
882 newtext = linkify_others(newtext, link_)
891 # wrap not links into final link => link_
892 newtext = linkify_others(newtext, link_)
883
893
884 return literal(newtext)
894 return literal(newtext)
885 except:
895 except:
886 log.error(traceback.format_exc())
896 log.error(traceback.format_exc())
887 pass
897 pass
888
898
889 return text_
899 return text_
890
900
891
901
892 def rst(source):
902 def rst(source):
893 return literal('<div class="rst-block">%s</div>' %
903 return literal('<div class="rst-block">%s</div>' %
894 MarkupRenderer.rst(source))
904 MarkupRenderer.rst(source))
895
905
896
906
897 def rst_w_mentions(source):
907 def rst_w_mentions(source):
898 """
908 """
899 Wrapped rst renderer with @mention highlighting
909 Wrapped rst renderer with @mention highlighting
900
910
901 :param source:
911 :param source:
902 """
912 """
903 return literal('<div class="rst-block">%s</div>' %
913 return literal('<div class="rst-block">%s</div>' %
904 MarkupRenderer.rst_with_mentions(source))
914 MarkupRenderer.rst_with_mentions(source))
General Comments 0
You need to be logged in to leave comments. Login now