##// END OF EJS Templates
Fixes #852, created custom Flash() class and Message...
marcink -
r3924:14afe4d1 beta
parent child Browse files
Show More
@@ -1,1379 +1,1413 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url
19 from pylons import url
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page as _Page
39 from webhelpers.paginate import Page as _Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
47 safe_int
47 safe_int
48 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.markup_renderer import MarkupRenderer
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
52 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.db import URL_SEP, Permission
53 from rhodecode.model.db import URL_SEP, Permission
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 html_escape_table = {
58 html_escape_table = {
59 "&": "&",
59 "&": "&",
60 '"': """,
60 '"': """,
61 "'": "'",
61 "'": "'",
62 ">": ">",
62 ">": ">",
63 "<": "&lt;",
63 "<": "&lt;",
64 }
64 }
65
65
66
66
67 def html_escape(text):
67 def html_escape(text):
68 """Produce entities within text."""
68 """Produce entities within text."""
69 return "".join(html_escape_table.get(c, c) for c in text)
69 return "".join(html_escape_table.get(c, c) for c in text)
70
70
71
71
72 def shorter(text, size=20):
72 def shorter(text, size=20):
73 postfix = '...'
73 postfix = '...'
74 if len(text) > size:
74 if len(text) > size:
75 return text[:size - len(postfix)] + postfix
75 return text[:size - len(postfix)] + postfix
76 return text
76 return text
77
77
78
78
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
80 """
80 """
81 Reset button
81 Reset button
82 """
82 """
83 _set_input_attrs(attrs, type, name, value)
83 _set_input_attrs(attrs, type, name, value)
84 _set_id_attr(attrs, id, name)
84 _set_id_attr(attrs, id, name)
85 convert_boolean_attrs(attrs, ["disabled"])
85 convert_boolean_attrs(attrs, ["disabled"])
86 return HTML.input(**attrs)
86 return HTML.input(**attrs)
87
87
88 reset = _reset
88 reset = _reset
89 safeid = _make_safe_id_component
89 safeid = _make_safe_id_component
90
90
91
91
92 def FID(raw_id, path):
92 def FID(raw_id, path):
93 """
93 """
94 Creates a uniqe ID for filenode based on it's hash of path and revision
94 Creates a uniqe ID for filenode based on it's hash of path and revision
95 it's safe to use in urls
95 it's safe to use in urls
96
96
97 :param raw_id:
97 :param raw_id:
98 :param path:
98 :param path:
99 """
99 """
100
100
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
102
102
103
103
104 def get_token():
104 def get_token():
105 """Return the current authentication token, creating one if one doesn't
105 """Return the current authentication token, creating one if one doesn't
106 already exist.
106 already exist.
107 """
107 """
108 token_key = "_authentication_token"
108 token_key = "_authentication_token"
109 from pylons import session
109 from pylons import session
110 if not token_key in session:
110 if not token_key in session:
111 try:
111 try:
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
113 except AttributeError: # Python < 2.4
113 except AttributeError: # Python < 2.4
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
115 session[token_key] = token
115 session[token_key] = token
116 if hasattr(session, 'save'):
116 if hasattr(session, 'save'):
117 session.save()
117 session.save()
118 return session[token_key]
118 return session[token_key]
119
119
120
120
121 class _GetError(object):
121 class _GetError(object):
122 """Get error from form_errors, and represent it as span wrapped error
122 """Get error from form_errors, and represent it as span wrapped error
123 message
123 message
124
124
125 :param field_name: field to fetch errors for
125 :param field_name: field to fetch errors for
126 :param form_errors: form errors dict
126 :param form_errors: form errors dict
127 """
127 """
128
128
129 def __call__(self, field_name, form_errors):
129 def __call__(self, field_name, form_errors):
130 tmpl = """<span class="error_msg">%s</span>"""
130 tmpl = """<span class="error_msg">%s</span>"""
131 if form_errors and field_name in form_errors:
131 if form_errors and field_name in form_errors:
132 return literal(tmpl % form_errors.get(field_name))
132 return literal(tmpl % form_errors.get(field_name))
133
133
134 get_error = _GetError()
134 get_error = _GetError()
135
135
136
136
137 class _ToolTip(object):
137 class _ToolTip(object):
138
138
139 def __call__(self, tooltip_title, trim_at=50):
139 def __call__(self, tooltip_title, trim_at=50):
140 """
140 """
141 Special function just to wrap our text into nice formatted
141 Special function just to wrap our text into nice formatted
142 autowrapped text
142 autowrapped text
143
143
144 :param tooltip_title:
144 :param tooltip_title:
145 """
145 """
146 tooltip_title = escape(tooltip_title)
146 tooltip_title = escape(tooltip_title)
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
148 return tooltip_title
148 return tooltip_title
149 tooltip = _ToolTip()
149 tooltip = _ToolTip()
150
150
151
151
152 class _FilesBreadCrumbs(object):
152 class _FilesBreadCrumbs(object):
153
153
154 def __call__(self, repo_name, rev, paths):
154 def __call__(self, repo_name, rev, paths):
155 if isinstance(paths, str):
155 if isinstance(paths, str):
156 paths = safe_unicode(paths)
156 paths = safe_unicode(paths)
157 url_l = [link_to(repo_name, url('files_home',
157 url_l = [link_to(repo_name, url('files_home',
158 repo_name=repo_name,
158 repo_name=repo_name,
159 revision=rev, f_path=''),
159 revision=rev, f_path=''),
160 class_='ypjax-link')]
160 class_='ypjax-link')]
161 paths_l = paths.split('/')
161 paths_l = paths.split('/')
162 for cnt, p in enumerate(paths_l):
162 for cnt, p in enumerate(paths_l):
163 if p != '':
163 if p != '':
164 url_l.append(link_to(p,
164 url_l.append(link_to(p,
165 url('files_home',
165 url('files_home',
166 repo_name=repo_name,
166 repo_name=repo_name,
167 revision=rev,
167 revision=rev,
168 f_path='/'.join(paths_l[:cnt + 1])
168 f_path='/'.join(paths_l[:cnt + 1])
169 ),
169 ),
170 class_='ypjax-link'
170 class_='ypjax-link'
171 )
171 )
172 )
172 )
173
173
174 return literal('/'.join(url_l))
174 return literal('/'.join(url_l))
175
175
176 files_breadcrumbs = _FilesBreadCrumbs()
176 files_breadcrumbs = _FilesBreadCrumbs()
177
177
178
178
179 class CodeHtmlFormatter(HtmlFormatter):
179 class CodeHtmlFormatter(HtmlFormatter):
180 """
180 """
181 My code Html Formatter for source codes
181 My code Html Formatter for source codes
182 """
182 """
183
183
184 def wrap(self, source, outfile):
184 def wrap(self, source, outfile):
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
186
186
187 def _wrap_code(self, source):
187 def _wrap_code(self, source):
188 for cnt, it in enumerate(source):
188 for cnt, it in enumerate(source):
189 i, t = it
189 i, t = it
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
191 yield i, t
191 yield i, t
192
192
193 def _wrap_tablelinenos(self, inner):
193 def _wrap_tablelinenos(self, inner):
194 dummyoutfile = StringIO.StringIO()
194 dummyoutfile = StringIO.StringIO()
195 lncount = 0
195 lncount = 0
196 for t, line in inner:
196 for t, line in inner:
197 if t:
197 if t:
198 lncount += 1
198 lncount += 1
199 dummyoutfile.write(line)
199 dummyoutfile.write(line)
200
200
201 fl = self.linenostart
201 fl = self.linenostart
202 mw = len(str(lncount + fl - 1))
202 mw = len(str(lncount + fl - 1))
203 sp = self.linenospecial
203 sp = self.linenospecial
204 st = self.linenostep
204 st = self.linenostep
205 la = self.lineanchors
205 la = self.lineanchors
206 aln = self.anchorlinenos
206 aln = self.anchorlinenos
207 nocls = self.noclasses
207 nocls = self.noclasses
208 if sp:
208 if sp:
209 lines = []
209 lines = []
210
210
211 for i in range(fl, fl + lncount):
211 for i in range(fl, fl + lncount):
212 if i % st == 0:
212 if i % st == 0:
213 if i % sp == 0:
213 if i % sp == 0:
214 if aln:
214 if aln:
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
216 (la, i, mw, i))
216 (la, i, mw, i))
217 else:
217 else:
218 lines.append('<span class="special">%*d</span>' % (mw, i))
218 lines.append('<span class="special">%*d</span>' % (mw, i))
219 else:
219 else:
220 if aln:
220 if aln:
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
222 else:
222 else:
223 lines.append('%*d' % (mw, i))
223 lines.append('%*d' % (mw, i))
224 else:
224 else:
225 lines.append('')
225 lines.append('')
226 ls = '\n'.join(lines)
226 ls = '\n'.join(lines)
227 else:
227 else:
228 lines = []
228 lines = []
229 for i in range(fl, fl + lncount):
229 for i in range(fl, fl + lncount):
230 if i % st == 0:
230 if i % st == 0:
231 if aln:
231 if aln:
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
233 else:
233 else:
234 lines.append('%*d' % (mw, i))
234 lines.append('%*d' % (mw, i))
235 else:
235 else:
236 lines.append('')
236 lines.append('')
237 ls = '\n'.join(lines)
237 ls = '\n'.join(lines)
238
238
239 # in case you wonder about the seemingly redundant <div> here: since the
239 # in case you wonder about the seemingly redundant <div> here: since the
240 # content in the other cell also is wrapped in a div, some browsers in
240 # content in the other cell also is wrapped in a div, some browsers in
241 # some configurations seem to mess up the formatting...
241 # some configurations seem to mess up the formatting...
242 if nocls:
242 if nocls:
243 yield 0, ('<table class="%stable">' % self.cssclass +
243 yield 0, ('<table class="%stable">' % self.cssclass +
244 '<tr><td><div class="linenodiv" '
244 '<tr><td><div class="linenodiv" '
245 'style="background-color: #f0f0f0; padding-right: 10px">'
245 'style="background-color: #f0f0f0; padding-right: 10px">'
246 '<pre style="line-height: 125%">' +
246 '<pre style="line-height: 125%">' +
247 ls + '</pre></div></td><td id="hlcode" class="code">')
247 ls + '</pre></div></td><td id="hlcode" class="code">')
248 else:
248 else:
249 yield 0, ('<table class="%stable">' % self.cssclass +
249 yield 0, ('<table class="%stable">' % self.cssclass +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
251 ls + '</pre></div></td><td id="hlcode" class="code">')
251 ls + '</pre></div></td><td id="hlcode" class="code">')
252 yield 0, dummyoutfile.getvalue()
252 yield 0, dummyoutfile.getvalue()
253 yield 0, '</td></tr></table>'
253 yield 0, '</td></tr></table>'
254
254
255
255
256 def pygmentize(filenode, **kwargs):
256 def pygmentize(filenode, **kwargs):
257 """
257 """
258 pygmentize function using pygments
258 pygmentize function using pygments
259
259
260 :param filenode:
260 :param filenode:
261 """
261 """
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
263 return literal(code_highlight(filenode.content, lexer,
263 return literal(code_highlight(filenode.content, lexer,
264 CodeHtmlFormatter(**kwargs)))
264 CodeHtmlFormatter(**kwargs)))
265
265
266
266
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
268 """
268 """
269 pygmentize function for annotation
269 pygmentize function for annotation
270
270
271 :param filenode:
271 :param filenode:
272 """
272 """
273
273
274 color_dict = {}
274 color_dict = {}
275
275
276 def gen_color(n=10000):
276 def gen_color(n=10000):
277 """generator for getting n of evenly distributed colors using
277 """generator for getting n of evenly distributed colors using
278 hsv color and golden ratio. It always return same order of colors
278 hsv color and golden ratio. It always return same order of colors
279
279
280 :returns: RGB tuple
280 :returns: RGB tuple
281 """
281 """
282
282
283 def hsv_to_rgb(h, s, v):
283 def hsv_to_rgb(h, s, v):
284 if s == 0.0:
284 if s == 0.0:
285 return v, v, v
285 return v, v, v
286 i = int(h * 6.0) # XXX assume int() truncates!
286 i = int(h * 6.0) # XXX assume int() truncates!
287 f = (h * 6.0) - i
287 f = (h * 6.0) - i
288 p = v * (1.0 - s)
288 p = v * (1.0 - s)
289 q = v * (1.0 - s * f)
289 q = v * (1.0 - s * f)
290 t = v * (1.0 - s * (1.0 - f))
290 t = v * (1.0 - s * (1.0 - f))
291 i = i % 6
291 i = i % 6
292 if i == 0:
292 if i == 0:
293 return v, t, p
293 return v, t, p
294 if i == 1:
294 if i == 1:
295 return q, v, p
295 return q, v, p
296 if i == 2:
296 if i == 2:
297 return p, v, t
297 return p, v, t
298 if i == 3:
298 if i == 3:
299 return p, q, v
299 return p, q, v
300 if i == 4:
300 if i == 4:
301 return t, p, v
301 return t, p, v
302 if i == 5:
302 if i == 5:
303 return v, p, q
303 return v, p, q
304
304
305 golden_ratio = 0.618033988749895
305 golden_ratio = 0.618033988749895
306 h = 0.22717784590367374
306 h = 0.22717784590367374
307
307
308 for _ in xrange(n):
308 for _ in xrange(n):
309 h += golden_ratio
309 h += golden_ratio
310 h %= 1
310 h %= 1
311 HSV_tuple = [h, 0.95, 0.95]
311 HSV_tuple = [h, 0.95, 0.95]
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
314
314
315 cgenerator = gen_color()
315 cgenerator = gen_color()
316
316
317 def get_color_string(cs):
317 def get_color_string(cs):
318 if cs in color_dict:
318 if cs in color_dict:
319 col = color_dict[cs]
319 col = color_dict[cs]
320 else:
320 else:
321 col = color_dict[cs] = cgenerator.next()
321 col = color_dict[cs] = cgenerator.next()
322 return "color: rgb(%s)! important;" % (', '.join(col))
322 return "color: rgb(%s)! important;" % (', '.join(col))
323
323
324 def url_func(repo_name):
324 def url_func(repo_name):
325
325
326 def _url_func(changeset):
326 def _url_func(changeset):
327 author = changeset.author
327 author = changeset.author
328 date = changeset.date
328 date = changeset.date
329 message = tooltip(changeset.message)
329 message = tooltip(changeset.message)
330
330
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
333 "</b> %s<br/></div>")
333 "</b> %s<br/></div>")
334
334
335 tooltip_html = tooltip_html % (author, date, message)
335 tooltip_html = tooltip_html % (author, date, message)
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 short_id(changeset.raw_id))
337 short_id(changeset.raw_id))
338 uri = link_to(
338 uri = link_to(
339 lnk_format,
339 lnk_format,
340 url('changeset_home', repo_name=repo_name,
340 url('changeset_home', repo_name=repo_name,
341 revision=changeset.raw_id),
341 revision=changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
343 class_='tooltip',
343 class_='tooltip',
344 title=tooltip_html
344 title=tooltip_html
345 )
345 )
346
346
347 uri += '\n'
347 uri += '\n'
348 return uri
348 return uri
349 return _url_func
349 return _url_func
350
350
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
352
352
353
353
354 def is_following_repo(repo_name, user_id):
354 def is_following_repo(repo_name, user_id):
355 from rhodecode.model.scm import ScmModel
355 from rhodecode.model.scm import ScmModel
356 return ScmModel().is_following_repo(repo_name, user_id)
356 return ScmModel().is_following_repo(repo_name, user_id)
357
357
358 flash = _Flash()
358 class _Message(object):
359 """A message returned by ``Flash.pop_messages()``.
360
361 Converting the message to a string returns the message text. Instances
362 also have the following attributes:
363
364 * ``message``: the message text.
365 * ``category``: the category specified when the message was created.
366 """
367
368 def __init__(self, category, message):
369 self.category=category
370 self.message=message
371
372 def __str__(self):
373 return self.message
374
375 __unicode__ = __str__
376
377 def __html__(self):
378 return escape(safe_unicode(self.message))
379
380 class Flash(_Flash):
381
382 def pop_messages(self):
383 """Return all accumulated messages and delete them from the session.
384
385 The return value is a list of ``Message`` objects.
386 """
387 from pylons import session
388 messages = session.pop(self.session_key, [])
389 session.save()
390 return [_Message(*m) for m in messages]
391
392 flash = Flash()
359
393
360 #==============================================================================
394 #==============================================================================
361 # SCM FILTERS available via h.
395 # SCM FILTERS available via h.
362 #==============================================================================
396 #==============================================================================
363 from rhodecode.lib.vcs.utils import author_name, author_email
397 from rhodecode.lib.vcs.utils import author_name, author_email
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
398 from rhodecode.lib.utils2 import credentials_filter, age as _age
365 from rhodecode.model.db import User, ChangesetStatus
399 from rhodecode.model.db import User, ChangesetStatus
366
400
367 age = lambda x, y=False: _age(x, y)
401 age = lambda x, y=False: _age(x, y)
368 capitalize = lambda x: x.capitalize()
402 capitalize = lambda x: x.capitalize()
369 email = author_email
403 email = author_email
370 short_id = lambda x: x[:12]
404 short_id = lambda x: x[:12]
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
405 hide_credentials = lambda x: ''.join(credentials_filter(x))
372
406
373
407
374 def show_id(cs):
408 def show_id(cs):
375 """
409 """
376 Configurable function that shows ID
410 Configurable function that shows ID
377 by default it's r123:fffeeefffeee
411 by default it's r123:fffeeefffeee
378
412
379 :param cs: changeset instance
413 :param cs: changeset instance
380 """
414 """
381 from rhodecode import CONFIG
415 from rhodecode import CONFIG
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
416 def_len = safe_int(CONFIG.get('show_sha_length', 12))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
417 show_rev = str2bool(CONFIG.get('show_revision_number', True))
384
418
385 raw_id = cs.raw_id[:def_len]
419 raw_id = cs.raw_id[:def_len]
386 if show_rev:
420 if show_rev:
387 return 'r%s:%s' % (cs.revision, raw_id)
421 return 'r%s:%s' % (cs.revision, raw_id)
388 else:
422 else:
389 return '%s' % (raw_id)
423 return '%s' % (raw_id)
390
424
391
425
392 def fmt_date(date):
426 def fmt_date(date):
393 if date:
427 if date:
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
428 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
395 return date.strftime(_fmt).decode('utf8')
429 return date.strftime(_fmt).decode('utf8')
396
430
397 return ""
431 return ""
398
432
399
433
400 def is_git(repository):
434 def is_git(repository):
401 if hasattr(repository, 'alias'):
435 if hasattr(repository, 'alias'):
402 _type = repository.alias
436 _type = repository.alias
403 elif hasattr(repository, 'repo_type'):
437 elif hasattr(repository, 'repo_type'):
404 _type = repository.repo_type
438 _type = repository.repo_type
405 else:
439 else:
406 _type = repository
440 _type = repository
407 return _type == 'git'
441 return _type == 'git'
408
442
409
443
410 def is_hg(repository):
444 def is_hg(repository):
411 if hasattr(repository, 'alias'):
445 if hasattr(repository, 'alias'):
412 _type = repository.alias
446 _type = repository.alias
413 elif hasattr(repository, 'repo_type'):
447 elif hasattr(repository, 'repo_type'):
414 _type = repository.repo_type
448 _type = repository.repo_type
415 else:
449 else:
416 _type = repository
450 _type = repository
417 return _type == 'hg'
451 return _type == 'hg'
418
452
419
453
420 def email_or_none(author):
454 def email_or_none(author):
421 # extract email from the commit string
455 # extract email from the commit string
422 _email = email(author)
456 _email = email(author)
423 if _email != '':
457 if _email != '':
424 # check it against RhodeCode database, and use the MAIN email for this
458 # check it against RhodeCode database, and use the MAIN email for this
425 # user
459 # user
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
460 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
461 if user is not None:
428 return user.email
462 return user.email
429 return _email
463 return _email
430
464
431 # See if it contains a username we can get an email from
465 # See if it contains a username we can get an email from
432 user = User.get_by_username(author_name(author), case_insensitive=True,
466 user = User.get_by_username(author_name(author), case_insensitive=True,
433 cache=True)
467 cache=True)
434 if user is not None:
468 if user is not None:
435 return user.email
469 return user.email
436
470
437 # No valid email, not a valid user in the system, none!
471 # No valid email, not a valid user in the system, none!
438 return None
472 return None
439
473
440
474
441 def person(author, show_attr="username_and_name"):
475 def person(author, show_attr="username_and_name"):
442 # attr to return from fetched user
476 # attr to return from fetched user
443 person_getter = lambda usr: getattr(usr, show_attr)
477 person_getter = lambda usr: getattr(usr, show_attr)
444
478
445 # Valid email in the attribute passed, see if they're in the system
479 # Valid email in the attribute passed, see if they're in the system
446 _email = email(author)
480 _email = email(author)
447 if _email != '':
481 if _email != '':
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
482 user = User.get_by_email(_email, case_insensitive=True, cache=True)
449 if user is not None:
483 if user is not None:
450 return person_getter(user)
484 return person_getter(user)
451
485
452 # Maybe it's a username?
486 # Maybe it's a username?
453 _author = author_name(author)
487 _author = author_name(author)
454 user = User.get_by_username(_author, case_insensitive=True,
488 user = User.get_by_username(_author, case_insensitive=True,
455 cache=True)
489 cache=True)
456 if user is not None:
490 if user is not None:
457 return person_getter(user)
491 return person_getter(user)
458
492
459 # Still nothing? Just pass back the author name if any, else the email
493 # Still nothing? Just pass back the author name if any, else the email
460 return _author or _email
494 return _author or _email
461
495
462
496
463 def person_by_id(id_, show_attr="username_and_name"):
497 def person_by_id(id_, show_attr="username_and_name"):
464 # attr to return from fetched user
498 # attr to return from fetched user
465 person_getter = lambda usr: getattr(usr, show_attr)
499 person_getter = lambda usr: getattr(usr, show_attr)
466
500
467 #maybe it's an ID ?
501 #maybe it's an ID ?
468 if str(id_).isdigit() or isinstance(id_, int):
502 if str(id_).isdigit() or isinstance(id_, int):
469 id_ = int(id_)
503 id_ = int(id_)
470 user = User.get(id_)
504 user = User.get(id_)
471 if user is not None:
505 if user is not None:
472 return person_getter(user)
506 return person_getter(user)
473 return id_
507 return id_
474
508
475
509
476 def desc_stylize(value):
510 def desc_stylize(value):
477 """
511 """
478 converts tags from value into html equivalent
512 converts tags from value into html equivalent
479
513
480 :param value:
514 :param value:
481 """
515 """
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
516 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
517 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
484 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
518 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
485 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
519 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
486 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
520 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
521 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
522 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
489 '<div class="metatag" tag="lang">\\2</div>', value)
523 '<div class="metatag" tag="lang">\\2</div>', value)
490 value = re.sub(r'\[([a-z]+)\]',
524 value = re.sub(r'\[([a-z]+)\]',
491 '<div class="metatag" tag="\\1">\\1</div>', value)
525 '<div class="metatag" tag="\\1">\\1</div>', value)
492
526
493 return value
527 return value
494
528
495
529
496 def boolicon(value):
530 def boolicon(value):
497 """Returns boolean value of a value, represented as small html image of true/false
531 """Returns boolean value of a value, represented as small html image of true/false
498 icons
532 icons
499
533
500 :param value: value
534 :param value: value
501 """
535 """
502
536
503 if value:
537 if value:
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
538 return HTML.tag('img', src=url("/images/icons/accept.png"),
505 alt=_('True'))
539 alt=_('True'))
506 else:
540 else:
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
541 return HTML.tag('img', src=url("/images/icons/cancel.png"),
508 alt=_('False'))
542 alt=_('False'))
509
543
510
544
511 def action_parser(user_log, feed=False, parse_cs=False):
545 def action_parser(user_log, feed=False, parse_cs=False):
512 """
546 """
513 This helper will action_map the specified string action into translated
547 This helper will action_map the specified string action into translated
514 fancy names with icons and links
548 fancy names with icons and links
515
549
516 :param user_log: user log instance
550 :param user_log: user log instance
517 :param feed: use output for feeds (no html and fancy icons)
551 :param feed: use output for feeds (no html and fancy icons)
518 :param parse_cs: parse Changesets into VCS instances
552 :param parse_cs: parse Changesets into VCS instances
519 """
553 """
520
554
521 action = user_log.action
555 action = user_log.action
522 action_params = ' '
556 action_params = ' '
523
557
524 x = action.split(':')
558 x = action.split(':')
525
559
526 if len(x) > 1:
560 if len(x) > 1:
527 action, action_params = x
561 action, action_params = x
528
562
529 def get_cs_links():
563 def get_cs_links():
530 revs_limit = 3 # display this amount always
564 revs_limit = 3 # display this amount always
531 revs_top_limit = 50 # show upto this amount of changesets hidden
565 revs_top_limit = 50 # show upto this amount of changesets hidden
532 revs_ids = action_params.split(',')
566 revs_ids = action_params.split(',')
533 deleted = user_log.repository is None
567 deleted = user_log.repository is None
534 if deleted:
568 if deleted:
535 return ','.join(revs_ids)
569 return ','.join(revs_ids)
536
570
537 repo_name = user_log.repository.repo_name
571 repo_name = user_log.repository.repo_name
538
572
539 def lnk(rev, repo_name):
573 def lnk(rev, repo_name):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
574 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
541 lazy_cs = True
575 lazy_cs = True
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
576 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
543 lazy_cs = False
577 lazy_cs = False
544 lbl = '?'
578 lbl = '?'
545 if rev.op == 'delete_branch':
579 if rev.op == 'delete_branch':
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
580 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
547 title = ''
581 title = ''
548 elif rev.op == 'tag':
582 elif rev.op == 'tag':
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
583 lbl = '%s' % _('Created tag: %s') % rev.ref_name
550 title = ''
584 title = ''
551 _url = '#'
585 _url = '#'
552
586
553 else:
587 else:
554 lbl = '%s' % (rev.short_id[:8])
588 lbl = '%s' % (rev.short_id[:8])
555 _url = url('changeset_home', repo_name=repo_name,
589 _url = url('changeset_home', repo_name=repo_name,
556 revision=rev.raw_id)
590 revision=rev.raw_id)
557 title = tooltip(rev.message)
591 title = tooltip(rev.message)
558 else:
592 else:
559 ## changeset cannot be found/striped/removed etc.
593 ## changeset cannot be found/striped/removed etc.
560 lbl = ('%s' % rev)[:12]
594 lbl = ('%s' % rev)[:12]
561 _url = '#'
595 _url = '#'
562 title = _('Changeset not found')
596 title = _('Changeset not found')
563 if parse_cs:
597 if parse_cs:
564 return link_to(lbl, _url, title=title, class_='tooltip')
598 return link_to(lbl, _url, title=title, class_='tooltip')
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
599 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
566 class_='lazy-cs' if lazy_cs else '')
600 class_='lazy-cs' if lazy_cs else '')
567
601
568 def _get_op(rev_txt):
602 def _get_op(rev_txt):
569 _op = None
603 _op = None
570 _name = rev_txt
604 _name = rev_txt
571 if len(rev_txt.split('=>')) == 2:
605 if len(rev_txt.split('=>')) == 2:
572 _op, _name = rev_txt.split('=>')
606 _op, _name = rev_txt.split('=>')
573 return _op, _name
607 return _op, _name
574
608
575 revs = []
609 revs = []
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
610 if len(filter(lambda v: v != '', revs_ids)) > 0:
577 repo = None
611 repo = None
578 for rev in revs_ids[:revs_top_limit]:
612 for rev in revs_ids[:revs_top_limit]:
579 _op, _name = _get_op(rev)
613 _op, _name = _get_op(rev)
580
614
581 # we want parsed changesets, or new log store format is bad
615 # we want parsed changesets, or new log store format is bad
582 if parse_cs:
616 if parse_cs:
583 try:
617 try:
584 if repo is None:
618 if repo is None:
585 repo = user_log.repository.scm_instance
619 repo = user_log.repository.scm_instance
586 _rev = repo.get_changeset(rev)
620 _rev = repo.get_changeset(rev)
587 revs.append(_rev)
621 revs.append(_rev)
588 except ChangesetDoesNotExistError:
622 except ChangesetDoesNotExistError:
589 log.error('cannot find revision %s in this repo' % rev)
623 log.error('cannot find revision %s in this repo' % rev)
590 revs.append(rev)
624 revs.append(rev)
591 continue
625 continue
592 else:
626 else:
593 _rev = AttributeDict({
627 _rev = AttributeDict({
594 'short_id': rev[:12],
628 'short_id': rev[:12],
595 'raw_id': rev,
629 'raw_id': rev,
596 'message': '',
630 'message': '',
597 'op': _op,
631 'op': _op,
598 'ref_name': _name
632 'ref_name': _name
599 })
633 })
600 revs.append(_rev)
634 revs.append(_rev)
601 cs_links = [" " + ', '.join(
635 cs_links = [" " + ', '.join(
602 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
636 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
603 )]
637 )]
604 _op1, _name1 = _get_op(revs_ids[0])
638 _op1, _name1 = _get_op(revs_ids[0])
605 _op2, _name2 = _get_op(revs_ids[-1])
639 _op2, _name2 = _get_op(revs_ids[-1])
606
640
607 _rev = '%s...%s' % (_name1, _name2)
641 _rev = '%s...%s' % (_name1, _name2)
608
642
609 compare_view = (
643 compare_view = (
610 ' <div class="compare_view tooltip" title="%s">'
644 ' <div class="compare_view tooltip" title="%s">'
611 '<a href="%s">%s</a> </div>' % (
645 '<a href="%s">%s</a> </div>' % (
612 _('Show all combined changesets %s->%s') % (
646 _('Show all combined changesets %s->%s') % (
613 revs_ids[0][:12], revs_ids[-1][:12]
647 revs_ids[0][:12], revs_ids[-1][:12]
614 ),
648 ),
615 url('changeset_home', repo_name=repo_name,
649 url('changeset_home', repo_name=repo_name,
616 revision=_rev
650 revision=_rev
617 ),
651 ),
618 _('compare view')
652 _('compare view')
619 )
653 )
620 )
654 )
621
655
622 # if we have exactly one more than normally displayed
656 # if we have exactly one more than normally displayed
623 # just display it, takes less space than displaying
657 # just display it, takes less space than displaying
624 # "and 1 more revisions"
658 # "and 1 more revisions"
625 if len(revs_ids) == revs_limit + 1:
659 if len(revs_ids) == revs_limit + 1:
626 rev = revs[revs_limit]
660 rev = revs[revs_limit]
627 cs_links.append(", " + lnk(rev, repo_name))
661 cs_links.append(", " + lnk(rev, repo_name))
628
662
629 # hidden-by-default ones
663 # hidden-by-default ones
630 if len(revs_ids) > revs_limit + 1:
664 if len(revs_ids) > revs_limit + 1:
631 uniq_id = revs_ids[0]
665 uniq_id = revs_ids[0]
632 html_tmpl = (
666 html_tmpl = (
633 '<span> %s <a class="show_more" id="_%s" '
667 '<span> %s <a class="show_more" id="_%s" '
634 'href="#more">%s</a> %s</span>'
668 'href="#more">%s</a> %s</span>'
635 )
669 )
636 if not feed:
670 if not feed:
637 cs_links.append(html_tmpl % (
671 cs_links.append(html_tmpl % (
638 _('and'),
672 _('and'),
639 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
673 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
640 _('revisions')
674 _('revisions')
641 )
675 )
642 )
676 )
643
677
644 if not feed:
678 if not feed:
645 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
679 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
646 else:
680 else:
647 html_tmpl = '<span id="%s"> %s </span>'
681 html_tmpl = '<span id="%s"> %s </span>'
648
682
649 morelinks = ', '.join(
683 morelinks = ', '.join(
650 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
684 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
651 )
685 )
652
686
653 if len(revs_ids) > revs_top_limit:
687 if len(revs_ids) > revs_top_limit:
654 morelinks += ', ...'
688 morelinks += ', ...'
655
689
656 cs_links.append(html_tmpl % (uniq_id, morelinks))
690 cs_links.append(html_tmpl % (uniq_id, morelinks))
657 if len(revs) > 1:
691 if len(revs) > 1:
658 cs_links.append(compare_view)
692 cs_links.append(compare_view)
659 return ''.join(cs_links)
693 return ''.join(cs_links)
660
694
661 def get_fork_name():
695 def get_fork_name():
662 repo_name = action_params
696 repo_name = action_params
663 _url = url('summary_home', repo_name=repo_name)
697 _url = url('summary_home', repo_name=repo_name)
664 return _('fork name %s') % link_to(action_params, _url)
698 return _('fork name %s') % link_to(action_params, _url)
665
699
666 def get_user_name():
700 def get_user_name():
667 user_name = action_params
701 user_name = action_params
668 return user_name
702 return user_name
669
703
670 def get_users_group():
704 def get_users_group():
671 group_name = action_params
705 group_name = action_params
672 return group_name
706 return group_name
673
707
674 def get_pull_request():
708 def get_pull_request():
675 pull_request_id = action_params
709 pull_request_id = action_params
676 deleted = user_log.repository is None
710 deleted = user_log.repository is None
677 if deleted:
711 if deleted:
678 repo_name = user_log.repository_name
712 repo_name = user_log.repository_name
679 else:
713 else:
680 repo_name = user_log.repository.repo_name
714 repo_name = user_log.repository.repo_name
681 return link_to(_('Pull request #%s') % pull_request_id,
715 return link_to(_('Pull request #%s') % pull_request_id,
682 url('pullrequest_show', repo_name=repo_name,
716 url('pullrequest_show', repo_name=repo_name,
683 pull_request_id=pull_request_id))
717 pull_request_id=pull_request_id))
684
718
685 def get_archive_name():
719 def get_archive_name():
686 archive_name = action_params
720 archive_name = action_params
687 return archive_name
721 return archive_name
688
722
689 # action : translated str, callback(extractor), icon
723 # action : translated str, callback(extractor), icon
690 action_map = {
724 action_map = {
691 'user_deleted_repo': (_('[deleted] repository'),
725 'user_deleted_repo': (_('[deleted] repository'),
692 None, 'database_delete.png'),
726 None, 'database_delete.png'),
693 'user_created_repo': (_('[created] repository'),
727 'user_created_repo': (_('[created] repository'),
694 None, 'database_add.png'),
728 None, 'database_add.png'),
695 'user_created_fork': (_('[created] repository as fork'),
729 'user_created_fork': (_('[created] repository as fork'),
696 None, 'arrow_divide.png'),
730 None, 'arrow_divide.png'),
697 'user_forked_repo': (_('[forked] repository'),
731 'user_forked_repo': (_('[forked] repository'),
698 get_fork_name, 'arrow_divide.png'),
732 get_fork_name, 'arrow_divide.png'),
699 'user_updated_repo': (_('[updated] repository'),
733 'user_updated_repo': (_('[updated] repository'),
700 None, 'database_edit.png'),
734 None, 'database_edit.png'),
701 'user_downloaded_archive': (_('[downloaded] archive from repository'),
735 'user_downloaded_archive': (_('[downloaded] archive from repository'),
702 get_archive_name, 'page_white_compressed.png'),
736 get_archive_name, 'page_white_compressed.png'),
703 'admin_deleted_repo': (_('[delete] repository'),
737 'admin_deleted_repo': (_('[delete] repository'),
704 None, 'database_delete.png'),
738 None, 'database_delete.png'),
705 'admin_created_repo': (_('[created] repository'),
739 'admin_created_repo': (_('[created] repository'),
706 None, 'database_add.png'),
740 None, 'database_add.png'),
707 'admin_forked_repo': (_('[forked] repository'),
741 'admin_forked_repo': (_('[forked] repository'),
708 None, 'arrow_divide.png'),
742 None, 'arrow_divide.png'),
709 'admin_updated_repo': (_('[updated] repository'),
743 'admin_updated_repo': (_('[updated] repository'),
710 None, 'database_edit.png'),
744 None, 'database_edit.png'),
711 'admin_created_user': (_('[created] user'),
745 'admin_created_user': (_('[created] user'),
712 get_user_name, 'user_add.png'),
746 get_user_name, 'user_add.png'),
713 'admin_updated_user': (_('[updated] user'),
747 'admin_updated_user': (_('[updated] user'),
714 get_user_name, 'user_edit.png'),
748 get_user_name, 'user_edit.png'),
715 'admin_created_users_group': (_('[created] user group'),
749 'admin_created_users_group': (_('[created] user group'),
716 get_users_group, 'group_add.png'),
750 get_users_group, 'group_add.png'),
717 'admin_updated_users_group': (_('[updated] user group'),
751 'admin_updated_users_group': (_('[updated] user group'),
718 get_users_group, 'group_edit.png'),
752 get_users_group, 'group_edit.png'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
753 'user_commented_revision': (_('[commented] on revision in repository'),
720 get_cs_links, 'comment_add.png'),
754 get_cs_links, 'comment_add.png'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
755 'user_commented_pull_request': (_('[commented] on pull request for'),
722 get_pull_request, 'comment_add.png'),
756 get_pull_request, 'comment_add.png'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
757 'user_closed_pull_request': (_('[closed] pull request for'),
724 get_pull_request, 'tick.png'),
758 get_pull_request, 'tick.png'),
725 'push': (_('[pushed] into'),
759 'push': (_('[pushed] into'),
726 get_cs_links, 'script_add.png'),
760 get_cs_links, 'script_add.png'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
761 'push_local': (_('[committed via RhodeCode] into repository'),
728 get_cs_links, 'script_edit.png'),
762 get_cs_links, 'script_edit.png'),
729 'push_remote': (_('[pulled from remote] into repository'),
763 'push_remote': (_('[pulled from remote] into repository'),
730 get_cs_links, 'connect.png'),
764 get_cs_links, 'connect.png'),
731 'pull': (_('[pulled] from'),
765 'pull': (_('[pulled] from'),
732 None, 'down_16.png'),
766 None, 'down_16.png'),
733 'started_following_repo': (_('[started following] repository'),
767 'started_following_repo': (_('[started following] repository'),
734 None, 'heart_add.png'),
768 None, 'heart_add.png'),
735 'stopped_following_repo': (_('[stopped following] repository'),
769 'stopped_following_repo': (_('[stopped following] repository'),
736 None, 'heart_delete.png'),
770 None, 'heart_delete.png'),
737 }
771 }
738
772
739 action_str = action_map.get(action, action)
773 action_str = action_map.get(action, action)
740 if feed:
774 if feed:
741 action = action_str[0].replace('[', '').replace(']', '')
775 action = action_str[0].replace('[', '').replace(']', '')
742 else:
776 else:
743 action = action_str[0]\
777 action = action_str[0]\
744 .replace('[', '<span class="journal_highlight">')\
778 .replace('[', '<span class="journal_highlight">')\
745 .replace(']', '</span>')
779 .replace(']', '</span>')
746
780
747 action_params_func = lambda: ""
781 action_params_func = lambda: ""
748
782
749 if callable(action_str[1]):
783 if callable(action_str[1]):
750 action_params_func = action_str[1]
784 action_params_func = action_str[1]
751
785
752 def action_parser_icon():
786 def action_parser_icon():
753 action = user_log.action
787 action = user_log.action
754 action_params = None
788 action_params = None
755 x = action.split(':')
789 x = action.split(':')
756
790
757 if len(x) > 1:
791 if len(x) > 1:
758 action, action_params = x
792 action, action_params = x
759
793
760 tmpl = """<img src="%s%s" alt="%s"/>"""
794 tmpl = """<img src="%s%s" alt="%s"/>"""
761 ico = action_map.get(action, ['', '', ''])[2]
795 ico = action_map.get(action, ['', '', ''])[2]
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
796 return literal(tmpl % ((url('/images/icons/')), ico, action))
763
797
764 # returned callbacks we need to call to get
798 # returned callbacks we need to call to get
765 return [lambda: literal(action), action_params_func, action_parser_icon]
799 return [lambda: literal(action), action_params_func, action_parser_icon]
766
800
767
801
768
802
769 #==============================================================================
803 #==============================================================================
770 # PERMS
804 # PERMS
771 #==============================================================================
805 #==============================================================================
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
806 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
807 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
774 HasReposGroupPermissionAny
808 HasReposGroupPermissionAny
775
809
776
810
777 #==============================================================================
811 #==============================================================================
778 # GRAVATAR URL
812 # GRAVATAR URL
779 #==============================================================================
813 #==============================================================================
780
814
781 def gravatar_url(email_address, size=30, ssl_enabled=True):
815 def gravatar_url(email_address, size=30, ssl_enabled=True):
782 from pylons import url # doh, we need to re-import url to mock it later
816 from pylons import url # doh, we need to re-import url to mock it later
783 from rhodecode import CONFIG
817 from rhodecode import CONFIG
784
818
785 _def = 'anonymous@rhodecode.org' # default gravatar
819 _def = 'anonymous@rhodecode.org' # default gravatar
786 use_gravatar = str2bool(CONFIG.get('use_gravatar'))
820 use_gravatar = str2bool(CONFIG.get('use_gravatar'))
787 alternative_gravatar_url = CONFIG.get('alternative_gravatar_url', '')
821 alternative_gravatar_url = CONFIG.get('alternative_gravatar_url', '')
788 email_address = email_address or _def
822 email_address = email_address or _def
789 if not use_gravatar or not email_address or email_address == _def:
823 if not use_gravatar or not email_address or email_address == _def:
790 f = lambda a, l: min(l, key=lambda x: abs(x - a))
824 f = lambda a, l: min(l, key=lambda x: abs(x - a))
791 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
825 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
792
826
793 if use_gravatar and alternative_gravatar_url:
827 if use_gravatar and alternative_gravatar_url:
794 tmpl = alternative_gravatar_url
828 tmpl = alternative_gravatar_url
795 parsed_url = urlparse.urlparse(url.current(qualified=True))
829 parsed_url = urlparse.urlparse(url.current(qualified=True))
796 tmpl = tmpl.replace('{email}', email_address)\
830 tmpl = tmpl.replace('{email}', email_address)\
797 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
831 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
798 .replace('{netloc}', parsed_url.netloc)\
832 .replace('{netloc}', parsed_url.netloc)\
799 .replace('{scheme}', parsed_url.scheme)\
833 .replace('{scheme}', parsed_url.scheme)\
800 .replace('{size}', str(size))
834 .replace('{size}', str(size))
801 return tmpl
835 return tmpl
802
836
803 default = 'identicon'
837 default = 'identicon'
804 baseurl_nossl = "http://www.gravatar.com/avatar/"
838 baseurl_nossl = "http://www.gravatar.com/avatar/"
805 baseurl_ssl = "https://secure.gravatar.com/avatar/"
839 baseurl_ssl = "https://secure.gravatar.com/avatar/"
806 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
840 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
807
841
808 if isinstance(email_address, unicode):
842 if isinstance(email_address, unicode):
809 #hashlib crashes on unicode items
843 #hashlib crashes on unicode items
810 email_address = safe_str(email_address)
844 email_address = safe_str(email_address)
811 # construct the url
845 # construct the url
812 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
846 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
813 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
847 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
814
848
815 return gravatar_url
849 return gravatar_url
816
850
817
851
818 class Page(_Page):
852 class Page(_Page):
819 """
853 """
820 Custom pager to match rendering style with YUI paginator
854 Custom pager to match rendering style with YUI paginator
821 """
855 """
822
856
823 def _get_pos(self, cur_page, max_page, items):
857 def _get_pos(self, cur_page, max_page, items):
824 edge = (items / 2) + 1
858 edge = (items / 2) + 1
825 if (cur_page <= edge):
859 if (cur_page <= edge):
826 radius = max(items / 2, items - cur_page)
860 radius = max(items / 2, items - cur_page)
827 elif (max_page - cur_page) < edge:
861 elif (max_page - cur_page) < edge:
828 radius = (items - 1) - (max_page - cur_page)
862 radius = (items - 1) - (max_page - cur_page)
829 else:
863 else:
830 radius = items / 2
864 radius = items / 2
831
865
832 left = max(1, (cur_page - (radius)))
866 left = max(1, (cur_page - (radius)))
833 right = min(max_page, cur_page + (radius))
867 right = min(max_page, cur_page + (radius))
834 return left, cur_page, right
868 return left, cur_page, right
835
869
836 def _range(self, regexp_match):
870 def _range(self, regexp_match):
837 """
871 """
838 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
872 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
839
873
840 Arguments:
874 Arguments:
841
875
842 regexp_match
876 regexp_match
843 A "re" (regular expressions) match object containing the
877 A "re" (regular expressions) match object containing the
844 radius of linked pages around the current page in
878 radius of linked pages around the current page in
845 regexp_match.group(1) as a string
879 regexp_match.group(1) as a string
846
880
847 This function is supposed to be called as a callable in
881 This function is supposed to be called as a callable in
848 re.sub.
882 re.sub.
849
883
850 """
884 """
851 radius = int(regexp_match.group(1))
885 radius = int(regexp_match.group(1))
852
886
853 # Compute the first and last page number within the radius
887 # Compute the first and last page number within the radius
854 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
888 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
855 # -> leftmost_page = 5
889 # -> leftmost_page = 5
856 # -> rightmost_page = 9
890 # -> rightmost_page = 9
857 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
891 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
858 self.last_page,
892 self.last_page,
859 (radius * 2) + 1)
893 (radius * 2) + 1)
860 nav_items = []
894 nav_items = []
861
895
862 # Create a link to the first page (unless we are on the first page
896 # Create a link to the first page (unless we are on the first page
863 # or there would be no need to insert '..' spacers)
897 # or there would be no need to insert '..' spacers)
864 if self.page != self.first_page and self.first_page < leftmost_page:
898 if self.page != self.first_page and self.first_page < leftmost_page:
865 nav_items.append(self._pagerlink(self.first_page, self.first_page))
899 nav_items.append(self._pagerlink(self.first_page, self.first_page))
866
900
867 # Insert dots if there are pages between the first page
901 # Insert dots if there are pages between the first page
868 # and the currently displayed page range
902 # and the currently displayed page range
869 if leftmost_page - self.first_page > 1:
903 if leftmost_page - self.first_page > 1:
870 # Wrap in a SPAN tag if nolink_attr is set
904 # Wrap in a SPAN tag if nolink_attr is set
871 text = '..'
905 text = '..'
872 if self.dotdot_attr:
906 if self.dotdot_attr:
873 text = HTML.span(c=text, **self.dotdot_attr)
907 text = HTML.span(c=text, **self.dotdot_attr)
874 nav_items.append(text)
908 nav_items.append(text)
875
909
876 for thispage in xrange(leftmost_page, rightmost_page + 1):
910 for thispage in xrange(leftmost_page, rightmost_page + 1):
877 # Hilight the current page number and do not use a link
911 # Hilight the current page number and do not use a link
878 if thispage == self.page:
912 if thispage == self.page:
879 text = '%s' % (thispage,)
913 text = '%s' % (thispage,)
880 # Wrap in a SPAN tag if nolink_attr is set
914 # Wrap in a SPAN tag if nolink_attr is set
881 if self.curpage_attr:
915 if self.curpage_attr:
882 text = HTML.span(c=text, **self.curpage_attr)
916 text = HTML.span(c=text, **self.curpage_attr)
883 nav_items.append(text)
917 nav_items.append(text)
884 # Otherwise create just a link to that page
918 # Otherwise create just a link to that page
885 else:
919 else:
886 text = '%s' % (thispage,)
920 text = '%s' % (thispage,)
887 nav_items.append(self._pagerlink(thispage, text))
921 nav_items.append(self._pagerlink(thispage, text))
888
922
889 # Insert dots if there are pages between the displayed
923 # Insert dots if there are pages between the displayed
890 # page numbers and the end of the page range
924 # page numbers and the end of the page range
891 if self.last_page - rightmost_page > 1:
925 if self.last_page - rightmost_page > 1:
892 text = '..'
926 text = '..'
893 # Wrap in a SPAN tag if nolink_attr is set
927 # Wrap in a SPAN tag if nolink_attr is set
894 if self.dotdot_attr:
928 if self.dotdot_attr:
895 text = HTML.span(c=text, **self.dotdot_attr)
929 text = HTML.span(c=text, **self.dotdot_attr)
896 nav_items.append(text)
930 nav_items.append(text)
897
931
898 # Create a link to the very last page (unless we are on the last
932 # Create a link to the very last page (unless we are on the last
899 # page or there would be no need to insert '..' spacers)
933 # page or there would be no need to insert '..' spacers)
900 if self.page != self.last_page and rightmost_page < self.last_page:
934 if self.page != self.last_page and rightmost_page < self.last_page:
901 nav_items.append(self._pagerlink(self.last_page, self.last_page))
935 nav_items.append(self._pagerlink(self.last_page, self.last_page))
902
936
903 ## prerender links
937 ## prerender links
904 nav_items.append(literal('<link rel="prerender" href="/rhodecode/changelog/1?page=%s">' % str(int(self.page)+1)))
938 nav_items.append(literal('<link rel="prerender" href="/rhodecode/changelog/1?page=%s">' % str(int(self.page)+1)))
905 return self.separator.join(nav_items)
939 return self.separator.join(nav_items)
906
940
907 def pager(self, format='~2~', page_param='page', partial_param='partial',
941 def pager(self, format='~2~', page_param='page', partial_param='partial',
908 show_if_single_page=False, separator=' ', onclick=None,
942 show_if_single_page=False, separator=' ', onclick=None,
909 symbol_first='<<', symbol_last='>>',
943 symbol_first='<<', symbol_last='>>',
910 symbol_previous='<', symbol_next='>',
944 symbol_previous='<', symbol_next='>',
911 link_attr={'class': 'pager_link', 'rel': 'prerender'},
945 link_attr={'class': 'pager_link', 'rel': 'prerender'},
912 curpage_attr={'class': 'pager_curpage'},
946 curpage_attr={'class': 'pager_curpage'},
913 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
947 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
914
948
915 self.curpage_attr = curpage_attr
949 self.curpage_attr = curpage_attr
916 self.separator = separator
950 self.separator = separator
917 self.pager_kwargs = kwargs
951 self.pager_kwargs = kwargs
918 self.page_param = page_param
952 self.page_param = page_param
919 self.partial_param = partial_param
953 self.partial_param = partial_param
920 self.onclick = onclick
954 self.onclick = onclick
921 self.link_attr = link_attr
955 self.link_attr = link_attr
922 self.dotdot_attr = dotdot_attr
956 self.dotdot_attr = dotdot_attr
923
957
924 # Don't show navigator if there is no more than one page
958 # Don't show navigator if there is no more than one page
925 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
959 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
926 return ''
960 return ''
927
961
928 from string import Template
962 from string import Template
929 # Replace ~...~ in token format by range of pages
963 # Replace ~...~ in token format by range of pages
930 result = re.sub(r'~(\d+)~', self._range, format)
964 result = re.sub(r'~(\d+)~', self._range, format)
931
965
932 # Interpolate '%' variables
966 # Interpolate '%' variables
933 result = Template(result).safe_substitute({
967 result = Template(result).safe_substitute({
934 'first_page': self.first_page,
968 'first_page': self.first_page,
935 'last_page': self.last_page,
969 'last_page': self.last_page,
936 'page': self.page,
970 'page': self.page,
937 'page_count': self.page_count,
971 'page_count': self.page_count,
938 'items_per_page': self.items_per_page,
972 'items_per_page': self.items_per_page,
939 'first_item': self.first_item,
973 'first_item': self.first_item,
940 'last_item': self.last_item,
974 'last_item': self.last_item,
941 'item_count': self.item_count,
975 'item_count': self.item_count,
942 'link_first': self.page > self.first_page and \
976 'link_first': self.page > self.first_page and \
943 self._pagerlink(self.first_page, symbol_first) or '',
977 self._pagerlink(self.first_page, symbol_first) or '',
944 'link_last': self.page < self.last_page and \
978 'link_last': self.page < self.last_page and \
945 self._pagerlink(self.last_page, symbol_last) or '',
979 self._pagerlink(self.last_page, symbol_last) or '',
946 'link_previous': self.previous_page and \
980 'link_previous': self.previous_page and \
947 self._pagerlink(self.previous_page, symbol_previous) \
981 self._pagerlink(self.previous_page, symbol_previous) \
948 or HTML.span(symbol_previous, class_="yui-pg-previous"),
982 or HTML.span(symbol_previous, class_="yui-pg-previous"),
949 'link_next': self.next_page and \
983 'link_next': self.next_page and \
950 self._pagerlink(self.next_page, symbol_next) \
984 self._pagerlink(self.next_page, symbol_next) \
951 or HTML.span(symbol_next, class_="yui-pg-next")
985 or HTML.span(symbol_next, class_="yui-pg-next")
952 })
986 })
953
987
954 return literal(result)
988 return literal(result)
955
989
956
990
957 #==============================================================================
991 #==============================================================================
958 # REPO PAGER, PAGER FOR REPOSITORY
992 # REPO PAGER, PAGER FOR REPOSITORY
959 #==============================================================================
993 #==============================================================================
960 class RepoPage(Page):
994 class RepoPage(Page):
961
995
962 def __init__(self, collection, page=1, items_per_page=20,
996 def __init__(self, collection, page=1, items_per_page=20,
963 item_count=None, url=None, **kwargs):
997 item_count=None, url=None, **kwargs):
964
998
965 """Create a "RepoPage" instance. special pager for paging
999 """Create a "RepoPage" instance. special pager for paging
966 repository
1000 repository
967 """
1001 """
968 self._url_generator = url
1002 self._url_generator = url
969
1003
970 # Safe the kwargs class-wide so they can be used in the pager() method
1004 # Safe the kwargs class-wide so they can be used in the pager() method
971 self.kwargs = kwargs
1005 self.kwargs = kwargs
972
1006
973 # Save a reference to the collection
1007 # Save a reference to the collection
974 self.original_collection = collection
1008 self.original_collection = collection
975
1009
976 self.collection = collection
1010 self.collection = collection
977
1011
978 # The self.page is the number of the current page.
1012 # The self.page is the number of the current page.
979 # The first page has the number 1!
1013 # The first page has the number 1!
980 try:
1014 try:
981 self.page = int(page) # make it int() if we get it as a string
1015 self.page = int(page) # make it int() if we get it as a string
982 except (ValueError, TypeError):
1016 except (ValueError, TypeError):
983 self.page = 1
1017 self.page = 1
984
1018
985 self.items_per_page = items_per_page
1019 self.items_per_page = items_per_page
986
1020
987 # Unless the user tells us how many items the collections has
1021 # Unless the user tells us how many items the collections has
988 # we calculate that ourselves.
1022 # we calculate that ourselves.
989 if item_count is not None:
1023 if item_count is not None:
990 self.item_count = item_count
1024 self.item_count = item_count
991 else:
1025 else:
992 self.item_count = len(self.collection)
1026 self.item_count = len(self.collection)
993
1027
994 # Compute the number of the first and last available page
1028 # Compute the number of the first and last available page
995 if self.item_count > 0:
1029 if self.item_count > 0:
996 self.first_page = 1
1030 self.first_page = 1
997 self.page_count = int(math.ceil(float(self.item_count) /
1031 self.page_count = int(math.ceil(float(self.item_count) /
998 self.items_per_page))
1032 self.items_per_page))
999 self.last_page = self.first_page + self.page_count - 1
1033 self.last_page = self.first_page + self.page_count - 1
1000
1034
1001 # Make sure that the requested page number is the range of
1035 # Make sure that the requested page number is the range of
1002 # valid pages
1036 # valid pages
1003 if self.page > self.last_page:
1037 if self.page > self.last_page:
1004 self.page = self.last_page
1038 self.page = self.last_page
1005 elif self.page < self.first_page:
1039 elif self.page < self.first_page:
1006 self.page = self.first_page
1040 self.page = self.first_page
1007
1041
1008 # Note: the number of items on this page can be less than
1042 # Note: the number of items on this page can be less than
1009 # items_per_page if the last page is not full
1043 # items_per_page if the last page is not full
1010 self.first_item = max(0, (self.item_count) - (self.page *
1044 self.first_item = max(0, (self.item_count) - (self.page *
1011 items_per_page))
1045 items_per_page))
1012 self.last_item = ((self.item_count - 1) - items_per_page *
1046 self.last_item = ((self.item_count - 1) - items_per_page *
1013 (self.page - 1))
1047 (self.page - 1))
1014
1048
1015 self.items = list(self.collection[self.first_item:self.last_item + 1])
1049 self.items = list(self.collection[self.first_item:self.last_item + 1])
1016
1050
1017 # Links to previous and next page
1051 # Links to previous and next page
1018 if self.page > self.first_page:
1052 if self.page > self.first_page:
1019 self.previous_page = self.page - 1
1053 self.previous_page = self.page - 1
1020 else:
1054 else:
1021 self.previous_page = None
1055 self.previous_page = None
1022
1056
1023 if self.page < self.last_page:
1057 if self.page < self.last_page:
1024 self.next_page = self.page + 1
1058 self.next_page = self.page + 1
1025 else:
1059 else:
1026 self.next_page = None
1060 self.next_page = None
1027
1061
1028 # No items available
1062 # No items available
1029 else:
1063 else:
1030 self.first_page = None
1064 self.first_page = None
1031 self.page_count = 0
1065 self.page_count = 0
1032 self.last_page = None
1066 self.last_page = None
1033 self.first_item = None
1067 self.first_item = None
1034 self.last_item = None
1068 self.last_item = None
1035 self.previous_page = None
1069 self.previous_page = None
1036 self.next_page = None
1070 self.next_page = None
1037 self.items = []
1071 self.items = []
1038
1072
1039 # This is a subclass of the 'list' type. Initialise the list now.
1073 # This is a subclass of the 'list' type. Initialise the list now.
1040 list.__init__(self, reversed(self.items))
1074 list.__init__(self, reversed(self.items))
1041
1075
1042
1076
1043 def changed_tooltip(nodes):
1077 def changed_tooltip(nodes):
1044 """
1078 """
1045 Generates a html string for changed nodes in changeset page.
1079 Generates a html string for changed nodes in changeset page.
1046 It limits the output to 30 entries
1080 It limits the output to 30 entries
1047
1081
1048 :param nodes: LazyNodesGenerator
1082 :param nodes: LazyNodesGenerator
1049 """
1083 """
1050 if nodes:
1084 if nodes:
1051 pref = ': <br/> '
1085 pref = ': <br/> '
1052 suf = ''
1086 suf = ''
1053 if len(nodes) > 30:
1087 if len(nodes) > 30:
1054 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1088 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1055 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1089 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1056 for x in nodes[:30]]) + suf)
1090 for x in nodes[:30]]) + suf)
1057 else:
1091 else:
1058 return ': ' + _('No Files')
1092 return ': ' + _('No Files')
1059
1093
1060
1094
1061 def repo_link(groups_and_repos):
1095 def repo_link(groups_and_repos):
1062 """
1096 """
1063 Makes a breadcrumbs link to repo within a group
1097 Makes a breadcrumbs link to repo within a group
1064 joins &raquo; on each group to create a fancy link
1098 joins &raquo; on each group to create a fancy link
1065
1099
1066 ex::
1100 ex::
1067 group >> subgroup >> repo
1101 group >> subgroup >> repo
1068
1102
1069 :param groups_and_repos:
1103 :param groups_and_repos:
1070 :param last_url:
1104 :param last_url:
1071 """
1105 """
1072 groups, just_name, repo_name = groups_and_repos
1106 groups, just_name, repo_name = groups_and_repos
1073 last_url = url('summary_home', repo_name=repo_name)
1107 last_url = url('summary_home', repo_name=repo_name)
1074 last_link = link_to(just_name, last_url)
1108 last_link = link_to(just_name, last_url)
1075
1109
1076 def make_link(group):
1110 def make_link(group):
1077 return link_to(group.name,
1111 return link_to(group.name,
1078 url('repos_group_home', group_name=group.group_name))
1112 url('repos_group_home', group_name=group.group_name))
1079 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1113 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1080
1114
1081
1115
1082 def fancy_file_stats(stats):
1116 def fancy_file_stats(stats):
1083 """
1117 """
1084 Displays a fancy two colored bar for number of added/deleted
1118 Displays a fancy two colored bar for number of added/deleted
1085 lines of code on file
1119 lines of code on file
1086
1120
1087 :param stats: two element list of added/deleted lines of code
1121 :param stats: two element list of added/deleted lines of code
1088 """
1122 """
1089 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1123 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1090 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1124 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1091
1125
1092 def cgen(l_type, a_v, d_v):
1126 def cgen(l_type, a_v, d_v):
1093 mapping = {'tr': 'top-right-rounded-corner-mid',
1127 mapping = {'tr': 'top-right-rounded-corner-mid',
1094 'tl': 'top-left-rounded-corner-mid',
1128 'tl': 'top-left-rounded-corner-mid',
1095 'br': 'bottom-right-rounded-corner-mid',
1129 'br': 'bottom-right-rounded-corner-mid',
1096 'bl': 'bottom-left-rounded-corner-mid'}
1130 'bl': 'bottom-left-rounded-corner-mid'}
1097 map_getter = lambda x: mapping[x]
1131 map_getter = lambda x: mapping[x]
1098
1132
1099 if l_type == 'a' and d_v:
1133 if l_type == 'a' and d_v:
1100 #case when added and deleted are present
1134 #case when added and deleted are present
1101 return ' '.join(map(map_getter, ['tl', 'bl']))
1135 return ' '.join(map(map_getter, ['tl', 'bl']))
1102
1136
1103 if l_type == 'a' and not d_v:
1137 if l_type == 'a' and not d_v:
1104 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1138 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1105
1139
1106 if l_type == 'd' and a_v:
1140 if l_type == 'd' and a_v:
1107 return ' '.join(map(map_getter, ['tr', 'br']))
1141 return ' '.join(map(map_getter, ['tr', 'br']))
1108
1142
1109 if l_type == 'd' and not a_v:
1143 if l_type == 'd' and not a_v:
1110 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1144 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1111
1145
1112 a, d = stats['added'], stats['deleted']
1146 a, d = stats['added'], stats['deleted']
1113 width = 100
1147 width = 100
1114
1148
1115 if stats['binary']:
1149 if stats['binary']:
1116 #binary mode
1150 #binary mode
1117 lbl = ''
1151 lbl = ''
1118 bin_op = 1
1152 bin_op = 1
1119
1153
1120 if BIN_FILENODE in stats['ops']:
1154 if BIN_FILENODE in stats['ops']:
1121 lbl = 'bin+'
1155 lbl = 'bin+'
1122
1156
1123 if NEW_FILENODE in stats['ops']:
1157 if NEW_FILENODE in stats['ops']:
1124 lbl += _('new file')
1158 lbl += _('new file')
1125 bin_op = NEW_FILENODE
1159 bin_op = NEW_FILENODE
1126 elif MOD_FILENODE in stats['ops']:
1160 elif MOD_FILENODE in stats['ops']:
1127 lbl += _('mod')
1161 lbl += _('mod')
1128 bin_op = MOD_FILENODE
1162 bin_op = MOD_FILENODE
1129 elif DEL_FILENODE in stats['ops']:
1163 elif DEL_FILENODE in stats['ops']:
1130 lbl += _('del')
1164 lbl += _('del')
1131 bin_op = DEL_FILENODE
1165 bin_op = DEL_FILENODE
1132 elif RENAMED_FILENODE in stats['ops']:
1166 elif RENAMED_FILENODE in stats['ops']:
1133 lbl += _('rename')
1167 lbl += _('rename')
1134 bin_op = RENAMED_FILENODE
1168 bin_op = RENAMED_FILENODE
1135
1169
1136 #chmod can go with other operations
1170 #chmod can go with other operations
1137 if CHMOD_FILENODE in stats['ops']:
1171 if CHMOD_FILENODE in stats['ops']:
1138 _org_lbl = _('chmod')
1172 _org_lbl = _('chmod')
1139 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1173 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1140
1174
1141 #import ipdb;ipdb.set_trace()
1175 #import ipdb;ipdb.set_trace()
1142 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1176 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1143 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1177 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1144 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1178 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1145
1179
1146 t = stats['added'] + stats['deleted']
1180 t = stats['added'] + stats['deleted']
1147 unit = float(width) / (t or 1)
1181 unit = float(width) / (t or 1)
1148
1182
1149 # needs > 9% of width to be visible or 0 to be hidden
1183 # needs > 9% of width to be visible or 0 to be hidden
1150 a_p = max(9, unit * a) if a > 0 else 0
1184 a_p = max(9, unit * a) if a > 0 else 0
1151 d_p = max(9, unit * d) if d > 0 else 0
1185 d_p = max(9, unit * d) if d > 0 else 0
1152 p_sum = a_p + d_p
1186 p_sum = a_p + d_p
1153
1187
1154 if p_sum > width:
1188 if p_sum > width:
1155 #adjust the percentage to be == 100% since we adjusted to 9
1189 #adjust the percentage to be == 100% since we adjusted to 9
1156 if a_p > d_p:
1190 if a_p > d_p:
1157 a_p = a_p - (p_sum - width)
1191 a_p = a_p - (p_sum - width)
1158 else:
1192 else:
1159 d_p = d_p - (p_sum - width)
1193 d_p = d_p - (p_sum - width)
1160
1194
1161 a_v = a if a > 0 else ''
1195 a_v = a if a > 0 else ''
1162 d_v = d if d > 0 else ''
1196 d_v = d if d > 0 else ''
1163
1197
1164 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1198 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1165 cgen('a', a_v, d_v), a_p, a_v
1199 cgen('a', a_v, d_v), a_p, a_v
1166 )
1200 )
1167 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1201 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1168 cgen('d', a_v, d_v), d_p, d_v
1202 cgen('d', a_v, d_v), d_p, d_v
1169 )
1203 )
1170 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1204 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1171
1205
1172
1206
1173 def urlify_text(text_, safe=True):
1207 def urlify_text(text_, safe=True):
1174 """
1208 """
1175 Extrac urls from text and make html links out of them
1209 Extrac urls from text and make html links out of them
1176
1210
1177 :param text_:
1211 :param text_:
1178 """
1212 """
1179
1213
1180 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1214 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1181 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1215 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1182
1216
1183 def url_func(match_obj):
1217 def url_func(match_obj):
1184 url_full = match_obj.groups()[0]
1218 url_full = match_obj.groups()[0]
1185 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1219 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1186 _newtext = url_pat.sub(url_func, text_)
1220 _newtext = url_pat.sub(url_func, text_)
1187 if safe:
1221 if safe:
1188 return literal(_newtext)
1222 return literal(_newtext)
1189 return _newtext
1223 return _newtext
1190
1224
1191
1225
1192 def urlify_changesets(text_, repository):
1226 def urlify_changesets(text_, repository):
1193 """
1227 """
1194 Extract revision ids from changeset and make link from them
1228 Extract revision ids from changeset and make link from them
1195
1229
1196 :param text_:
1230 :param text_:
1197 :param repository: repo name to build the URL with
1231 :param repository: repo name to build the URL with
1198 """
1232 """
1199 from pylons import url # doh, we need to re-import url to mock it later
1233 from pylons import url # doh, we need to re-import url to mock it later
1200 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1234 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1201
1235
1202 def url_func(match_obj):
1236 def url_func(match_obj):
1203 rev = match_obj.groups()[1]
1237 rev = match_obj.groups()[1]
1204 pref = match_obj.groups()[0]
1238 pref = match_obj.groups()[0]
1205 suf = match_obj.groups()[2]
1239 suf = match_obj.groups()[2]
1206
1240
1207 tmpl = (
1241 tmpl = (
1208 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1242 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1209 '%(rev)s</a>%(suf)s'
1243 '%(rev)s</a>%(suf)s'
1210 )
1244 )
1211 return tmpl % {
1245 return tmpl % {
1212 'pref': pref,
1246 'pref': pref,
1213 'cls': 'revision-link',
1247 'cls': 'revision-link',
1214 'url': url('changeset_home', repo_name=repository, revision=rev),
1248 'url': url('changeset_home', repo_name=repository, revision=rev),
1215 'rev': rev,
1249 'rev': rev,
1216 'suf': suf
1250 'suf': suf
1217 }
1251 }
1218
1252
1219 newtext = URL_PAT.sub(url_func, text_)
1253 newtext = URL_PAT.sub(url_func, text_)
1220
1254
1221 return newtext
1255 return newtext
1222
1256
1223
1257
1224 def urlify_commit(text_, repository=None, link_=None):
1258 def urlify_commit(text_, repository=None, link_=None):
1225 """
1259 """
1226 Parses given text message and makes proper links.
1260 Parses given text message and makes proper links.
1227 issues are linked to given issue-server, and rest is a changeset link
1261 issues are linked to given issue-server, and rest is a changeset link
1228 if link_ is given, in other case it's a plain text
1262 if link_ is given, in other case it's a plain text
1229
1263
1230 :param text_:
1264 :param text_:
1231 :param repository:
1265 :param repository:
1232 :param link_: changeset link
1266 :param link_: changeset link
1233 """
1267 """
1234 import traceback
1268 import traceback
1235 from pylons import url # doh, we need to re-import url to mock it later
1269 from pylons import url # doh, we need to re-import url to mock it later
1236
1270
1237 def escaper(string):
1271 def escaper(string):
1238 return string.replace('<', '&lt;').replace('>', '&gt;')
1272 return string.replace('<', '&lt;').replace('>', '&gt;')
1239
1273
1240 def linkify_others(t, l):
1274 def linkify_others(t, l):
1241 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1275 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1242 links = []
1276 links = []
1243 for e in urls.split(t):
1277 for e in urls.split(t):
1244 if not urls.match(e):
1278 if not urls.match(e):
1245 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1279 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1246 else:
1280 else:
1247 links.append(e)
1281 links.append(e)
1248
1282
1249 return ''.join(links)
1283 return ''.join(links)
1250
1284
1251 # urlify changesets - extrac revisions and make link out of them
1285 # urlify changesets - extrac revisions and make link out of them
1252 newtext = urlify_changesets(escaper(text_), repository)
1286 newtext = urlify_changesets(escaper(text_), repository)
1253
1287
1254 # extract http/https links and make them real urls
1288 # extract http/https links and make them real urls
1255 newtext = urlify_text(newtext, safe=False)
1289 newtext = urlify_text(newtext, safe=False)
1256
1290
1257 try:
1291 try:
1258 from rhodecode import CONFIG
1292 from rhodecode import CONFIG
1259 conf = CONFIG
1293 conf = CONFIG
1260
1294
1261 # allow multiple issue servers to be used
1295 # allow multiple issue servers to be used
1262 valid_indices = [
1296 valid_indices = [
1263 x.group(1)
1297 x.group(1)
1264 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1298 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1265 if x and 'issue_server_link%s' % x.group(1) in conf
1299 if x and 'issue_server_link%s' % x.group(1) in conf
1266 and 'issue_prefix%s' % x.group(1) in conf
1300 and 'issue_prefix%s' % x.group(1) in conf
1267 ]
1301 ]
1268
1302
1269 log.debug('found issue server suffixes `%s` during valuation of: %s'
1303 log.debug('found issue server suffixes `%s` during valuation of: %s'
1270 % (','.join(valid_indices), newtext))
1304 % (','.join(valid_indices), newtext))
1271
1305
1272 for pattern_index in valid_indices:
1306 for pattern_index in valid_indices:
1273 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1307 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1274 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1308 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1275 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1309 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1276
1310
1277 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1311 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1278 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1312 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1279 ISSUE_PREFIX))
1313 ISSUE_PREFIX))
1280
1314
1281 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1315 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1282
1316
1283 def url_func(match_obj):
1317 def url_func(match_obj):
1284 pref = ''
1318 pref = ''
1285 if match_obj.group().startswith(' '):
1319 if match_obj.group().startswith(' '):
1286 pref = ' '
1320 pref = ' '
1287
1321
1288 issue_id = ''.join(match_obj.groups())
1322 issue_id = ''.join(match_obj.groups())
1289 tmpl = (
1323 tmpl = (
1290 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1324 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1291 '%(issue-prefix)s%(id-repr)s'
1325 '%(issue-prefix)s%(id-repr)s'
1292 '</a>'
1326 '</a>'
1293 )
1327 )
1294 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1328 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1295 if repository:
1329 if repository:
1296 url = url.replace('{repo}', repository)
1330 url = url.replace('{repo}', repository)
1297 repo_name = repository.split(URL_SEP)[-1]
1331 repo_name = repository.split(URL_SEP)[-1]
1298 url = url.replace('{repo_name}', repo_name)
1332 url = url.replace('{repo_name}', repo_name)
1299
1333
1300 return tmpl % {
1334 return tmpl % {
1301 'pref': pref,
1335 'pref': pref,
1302 'cls': 'issue-tracker-link',
1336 'cls': 'issue-tracker-link',
1303 'url': url,
1337 'url': url,
1304 'id-repr': issue_id,
1338 'id-repr': issue_id,
1305 'issue-prefix': ISSUE_PREFIX,
1339 'issue-prefix': ISSUE_PREFIX,
1306 'serv': ISSUE_SERVER_LNK,
1340 'serv': ISSUE_SERVER_LNK,
1307 }
1341 }
1308 newtext = URL_PAT.sub(url_func, newtext)
1342 newtext = URL_PAT.sub(url_func, newtext)
1309 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1343 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1310
1344
1311 # if we actually did something above
1345 # if we actually did something above
1312 if link_:
1346 if link_:
1313 # wrap not links into final link => link_
1347 # wrap not links into final link => link_
1314 newtext = linkify_others(newtext, link_)
1348 newtext = linkify_others(newtext, link_)
1315 except Exception:
1349 except Exception:
1316 log.error(traceback.format_exc())
1350 log.error(traceback.format_exc())
1317 pass
1351 pass
1318
1352
1319 return literal(newtext)
1353 return literal(newtext)
1320
1354
1321
1355
1322 def rst(source):
1356 def rst(source):
1323 return literal('<div class="rst-block">%s</div>' %
1357 return literal('<div class="rst-block">%s</div>' %
1324 MarkupRenderer.rst(source))
1358 MarkupRenderer.rst(source))
1325
1359
1326
1360
1327 def rst_w_mentions(source):
1361 def rst_w_mentions(source):
1328 """
1362 """
1329 Wrapped rst renderer with @mention highlighting
1363 Wrapped rst renderer with @mention highlighting
1330
1364
1331 :param source:
1365 :param source:
1332 """
1366 """
1333 return literal('<div class="rst-block">%s</div>' %
1367 return literal('<div class="rst-block">%s</div>' %
1334 MarkupRenderer.rst_with_mentions(source))
1368 MarkupRenderer.rst_with_mentions(source))
1335
1369
1336
1370
1337 def changeset_status(repo, revision):
1371 def changeset_status(repo, revision):
1338 return ChangesetStatusModel().get_status(repo, revision)
1372 return ChangesetStatusModel().get_status(repo, revision)
1339
1373
1340
1374
1341 def changeset_status_lbl(changeset_status):
1375 def changeset_status_lbl(changeset_status):
1342 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1376 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1343
1377
1344
1378
1345 def get_permission_name(key):
1379 def get_permission_name(key):
1346 return dict(Permission.PERMS).get(key)
1380 return dict(Permission.PERMS).get(key)
1347
1381
1348
1382
1349 def journal_filter_help():
1383 def journal_filter_help():
1350 return _(textwrap.dedent('''
1384 return _(textwrap.dedent('''
1351 Example filter terms:
1385 Example filter terms:
1352 repository:vcs
1386 repository:vcs
1353 username:marcin
1387 username:marcin
1354 action:*push*
1388 action:*push*
1355 ip:127.0.0.1
1389 ip:127.0.0.1
1356 date:20120101
1390 date:20120101
1357 date:[20120101100000 TO 20120102]
1391 date:[20120101100000 TO 20120102]
1358
1392
1359 Generate wildcards using '*' character:
1393 Generate wildcards using '*' character:
1360 "repositroy:vcs*" - search everything starting with 'vcs'
1394 "repositroy:vcs*" - search everything starting with 'vcs'
1361 "repository:*vcs*" - search for repository containing 'vcs'
1395 "repository:*vcs*" - search for repository containing 'vcs'
1362
1396
1363 Optional AND / OR operators in queries
1397 Optional AND / OR operators in queries
1364 "repository:vcs OR repository:test"
1398 "repository:vcs OR repository:test"
1365 "username:test AND repository:test*"
1399 "username:test AND repository:test*"
1366 '''))
1400 '''))
1367
1401
1368
1402
1369 def not_mapped_error(repo_name):
1403 def not_mapped_error(repo_name):
1370 flash(_('%s repository is not mapped to db perhaps'
1404 flash(_('%s repository is not mapped to db perhaps'
1371 ' it was created or renamed from the filesystem'
1405 ' it was created or renamed from the filesystem'
1372 ' please run the application again'
1406 ' please run the application again'
1373 ' in order to rescan repositories') % repo_name, category='error')
1407 ' in order to rescan repositories') % repo_name, category='error')
1374
1408
1375
1409
1376 def ip_range(ip_addr):
1410 def ip_range(ip_addr):
1377 from rhodecode.model.db import UserIpMap
1411 from rhodecode.model.db import UserIpMap
1378 s, e = UserIpMap._get_ip_range(ip_addr)
1412 s, e = UserIpMap._get_ip_range(ip_addr)
1379 return '%s - %s' % (s, e)
1413 return '%s - %s' % (s, e)
General Comments 0
You need to be logged in to leave comments. Login now