##// END OF EJS Templates
helpers: fixed some errors on helpers, and removed dead code.
marcink -
r254:339501a7 default
parent child Browse files
Show More
@@ -1,1899 +1,1897 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import random
28 import random
29 import hashlib
29 import hashlib
30 import StringIO
30 import StringIO
31 import urllib
31 import urllib
32 import math
32 import math
33 import logging
33 import logging
34 import re
34 import re
35 import urlparse
35 import urlparse
36 import time
36 import time
37 import string
37 import string
38 import hashlib
38 import hashlib
39 import pygments
39 import pygments
40
40
41 from datetime import datetime
41 from datetime import datetime
42 from functools import partial
42 from functools import partial
43 from pygments.formatters.html import HtmlFormatter
43 from pygments.formatters.html import HtmlFormatter
44 from pygments import highlight as code_highlight
44 from pygments import highlight as code_highlight
45 from pygments.lexers import (
45 from pygments.lexers import (
46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
47 from pylons import url
47 from pylons import url
48 from pylons.i18n.translation import _, ungettext
48 from pylons.i18n.translation import _, ungettext
49 from pyramid.threadlocal import get_current_request
49 from pyramid.threadlocal import get_current_request
50
50
51 from webhelpers.html import literal, HTML, escape
51 from webhelpers.html import literal, HTML, escape
52 from webhelpers.html.tools import *
52 from webhelpers.html.tools import *
53 from webhelpers.html.builder import make_tag
53 from webhelpers.html.builder import make_tag
54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
57 submit, text, password, textarea, title, ul, xml_declaration, radio
57 submit, text, password, textarea, title, ul, xml_declaration, radio
58 from webhelpers.html.tools import auto_link, button_to, highlight, \
58 from webhelpers.html.tools import auto_link, button_to, highlight, \
59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
60 from webhelpers.pylonslib import Flash as _Flash
60 from webhelpers.pylonslib import Flash as _Flash
61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
63 replace_whitespace, urlify, truncate, wrap_paragraphs
63 replace_whitespace, urlify, truncate, wrap_paragraphs
64 from webhelpers.date import time_ago_in_words
64 from webhelpers.date import time_ago_in_words
65 from webhelpers.paginate import Page as _Page
65 from webhelpers.paginate import Page as _Page
66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
67 convert_boolean_attrs, NotGiven, _make_safe_id_component
67 convert_boolean_attrs, NotGiven, _make_safe_id_component
68 from webhelpers2.number import format_byte_size
68 from webhelpers2.number import format_byte_size
69
69
70 from rhodecode.lib.annotate import annotate_highlight
70 from rhodecode.lib.annotate import annotate_highlight
71 from rhodecode.lib.action_parser import action_parser
71 from rhodecode.lib.action_parser import action_parser
72 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
72 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
73 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
73 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
74 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
74 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
75 AttributeDict, safe_int, md5, md5_safe
75 AttributeDict, safe_int, md5, md5_safe
76 from rhodecode.lib.markup_renderer import MarkupRenderer
76 from rhodecode.lib.markup_renderer import MarkupRenderer
77 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
77 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
78 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
78 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
79 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
79 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
80 from rhodecode.model.changeset_status import ChangesetStatusModel
80 from rhodecode.model.changeset_status import ChangesetStatusModel
81 from rhodecode.model.db import Permission, User, Repository
81 from rhodecode.model.db import Permission, User, Repository
82 from rhodecode.model.repo_group import RepoGroupModel
82 from rhodecode.model.repo_group import RepoGroupModel
83 from rhodecode.model.settings import IssueTrackerSettingsModel
83 from rhodecode.model.settings import IssueTrackerSettingsModel
84
84
85 log = logging.getLogger(__name__)
85 log = logging.getLogger(__name__)
86
86
87 DEFAULT_USER = User.DEFAULT_USER
87 DEFAULT_USER = User.DEFAULT_USER
88 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
88 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
89
89
90
90
91 def html_escape(text, html_escape_table=None):
91 def html_escape(text, html_escape_table=None):
92 """Produce entities within text."""
92 """Produce entities within text."""
93 if not html_escape_table:
93 if not html_escape_table:
94 html_escape_table = {
94 html_escape_table = {
95 "&": "&amp;",
95 "&": "&amp;",
96 '"': "&quot;",
96 '"': "&quot;",
97 "'": "&apos;",
97 "'": "&apos;",
98 ">": "&gt;",
98 ">": "&gt;",
99 "<": "&lt;",
99 "<": "&lt;",
100 }
100 }
101 return "".join(html_escape_table.get(c, c) for c in text)
101 return "".join(html_escape_table.get(c, c) for c in text)
102
102
103
103
104 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
104 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
105 """
105 """
106 Truncate string ``s`` at the first occurrence of ``sub``.
106 Truncate string ``s`` at the first occurrence of ``sub``.
107
107
108 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
108 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
109 """
109 """
110 suffix_if_chopped = suffix_if_chopped or ''
110 suffix_if_chopped = suffix_if_chopped or ''
111 pos = s.find(sub)
111 pos = s.find(sub)
112 if pos == -1:
112 if pos == -1:
113 return s
113 return s
114
114
115 if inclusive:
115 if inclusive:
116 pos += len(sub)
116 pos += len(sub)
117
117
118 chopped = s[:pos]
118 chopped = s[:pos]
119 left = s[pos:].strip()
119 left = s[pos:].strip()
120
120
121 if left and suffix_if_chopped:
121 if left and suffix_if_chopped:
122 chopped += suffix_if_chopped
122 chopped += suffix_if_chopped
123
123
124 return chopped
124 return chopped
125
125
126
126
127 def shorter(text, size=20):
127 def shorter(text, size=20):
128 postfix = '...'
128 postfix = '...'
129 if len(text) > size:
129 if len(text) > size:
130 return text[:size - len(postfix)] + postfix
130 return text[:size - len(postfix)] + postfix
131 return text
131 return text
132
132
133
133
134 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
134 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
135 """
135 """
136 Reset button
136 Reset button
137 """
137 """
138 _set_input_attrs(attrs, type, name, value)
138 _set_input_attrs(attrs, type, name, value)
139 _set_id_attr(attrs, id, name)
139 _set_id_attr(attrs, id, name)
140 convert_boolean_attrs(attrs, ["disabled"])
140 convert_boolean_attrs(attrs, ["disabled"])
141 return HTML.input(**attrs)
141 return HTML.input(**attrs)
142
142
143 reset = _reset
143 reset = _reset
144 safeid = _make_safe_id_component
144 safeid = _make_safe_id_component
145
145
146
146
147 def branding(name, length=40):
147 def branding(name, length=40):
148 return truncate(name, length, indicator="")
148 return truncate(name, length, indicator="")
149
149
150
150
151 def FID(raw_id, path):
151 def FID(raw_id, path):
152 """
152 """
153 Creates a unique ID for filenode based on it's hash of path and commit
153 Creates a unique ID for filenode based on it's hash of path and commit
154 it's safe to use in urls
154 it's safe to use in urls
155
155
156 :param raw_id:
156 :param raw_id:
157 :param path:
157 :param path:
158 """
158 """
159
159
160 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
160 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
161
161
162
162
163 class _GetError(object):
163 class _GetError(object):
164 """Get error from form_errors, and represent it as span wrapped error
164 """Get error from form_errors, and represent it as span wrapped error
165 message
165 message
166
166
167 :param field_name: field to fetch errors for
167 :param field_name: field to fetch errors for
168 :param form_errors: form errors dict
168 :param form_errors: form errors dict
169 """
169 """
170
170
171 def __call__(self, field_name, form_errors):
171 def __call__(self, field_name, form_errors):
172 tmpl = """<span class="error_msg">%s</span>"""
172 tmpl = """<span class="error_msg">%s</span>"""
173 if form_errors and field_name in form_errors:
173 if form_errors and field_name in form_errors:
174 return literal(tmpl % form_errors.get(field_name))
174 return literal(tmpl % form_errors.get(field_name))
175
175
176 get_error = _GetError()
176 get_error = _GetError()
177
177
178
178
179 class _ToolTip(object):
179 class _ToolTip(object):
180
180
181 def __call__(self, tooltip_title, trim_at=50):
181 def __call__(self, tooltip_title, trim_at=50):
182 """
182 """
183 Special function just to wrap our text into nice formatted
183 Special function just to wrap our text into nice formatted
184 autowrapped text
184 autowrapped text
185
185
186 :param tooltip_title:
186 :param tooltip_title:
187 """
187 """
188 tooltip_title = escape(tooltip_title)
188 tooltip_title = escape(tooltip_title)
189 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
189 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
190 return tooltip_title
190 return tooltip_title
191 tooltip = _ToolTip()
191 tooltip = _ToolTip()
192
192
193
193
194 def files_breadcrumbs(repo_name, commit_id, file_path):
194 def files_breadcrumbs(repo_name, commit_id, file_path):
195 if isinstance(file_path, str):
195 if isinstance(file_path, str):
196 file_path = safe_unicode(file_path)
196 file_path = safe_unicode(file_path)
197
197
198 # TODO: johbo: Is this always a url like path, or is this operating
198 # TODO: johbo: Is this always a url like path, or is this operating
199 # system dependent?
199 # system dependent?
200 path_segments = file_path.split('/')
200 path_segments = file_path.split('/')
201
201
202 repo_name_html = escape(repo_name)
202 repo_name_html = escape(repo_name)
203 if len(path_segments) == 1 and path_segments[0] == '':
203 if len(path_segments) == 1 and path_segments[0] == '':
204 url_segments = [repo_name_html]
204 url_segments = [repo_name_html]
205 else:
205 else:
206 url_segments = [
206 url_segments = [
207 link_to(
207 link_to(
208 repo_name_html,
208 repo_name_html,
209 url('files_home',
209 url('files_home',
210 repo_name=repo_name,
210 repo_name=repo_name,
211 revision=commit_id,
211 revision=commit_id,
212 f_path=''),
212 f_path=''),
213 class_='pjax-link')]
213 class_='pjax-link')]
214
214
215 last_cnt = len(path_segments) - 1
215 last_cnt = len(path_segments) - 1
216 for cnt, segment in enumerate(path_segments):
216 for cnt, segment in enumerate(path_segments):
217 if not segment:
217 if not segment:
218 continue
218 continue
219 segment_html = escape(segment)
219 segment_html = escape(segment)
220
220
221 if cnt != last_cnt:
221 if cnt != last_cnt:
222 url_segments.append(
222 url_segments.append(
223 link_to(
223 link_to(
224 segment_html,
224 segment_html,
225 url('files_home',
225 url('files_home',
226 repo_name=repo_name,
226 repo_name=repo_name,
227 revision=commit_id,
227 revision=commit_id,
228 f_path='/'.join(path_segments[:cnt + 1])),
228 f_path='/'.join(path_segments[:cnt + 1])),
229 class_='pjax-link'))
229 class_='pjax-link'))
230 else:
230 else:
231 url_segments.append(segment_html)
231 url_segments.append(segment_html)
232
232
233 return literal('/'.join(url_segments))
233 return literal('/'.join(url_segments))
234
234
235
235
236 class CodeHtmlFormatter(HtmlFormatter):
236 class CodeHtmlFormatter(HtmlFormatter):
237 """
237 """
238 My code Html Formatter for source codes
238 My code Html Formatter for source codes
239 """
239 """
240
240
241 def wrap(self, source, outfile):
241 def wrap(self, source, outfile):
242 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
242 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
243
243
244 def _wrap_code(self, source):
244 def _wrap_code(self, source):
245 for cnt, it in enumerate(source):
245 for cnt, it in enumerate(source):
246 i, t = it
246 i, t = it
247 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
247 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
248 yield i, t
248 yield i, t
249
249
250 def _wrap_tablelinenos(self, inner):
250 def _wrap_tablelinenos(self, inner):
251 dummyoutfile = StringIO.StringIO()
251 dummyoutfile = StringIO.StringIO()
252 lncount = 0
252 lncount = 0
253 for t, line in inner:
253 for t, line in inner:
254 if t:
254 if t:
255 lncount += 1
255 lncount += 1
256 dummyoutfile.write(line)
256 dummyoutfile.write(line)
257
257
258 fl = self.linenostart
258 fl = self.linenostart
259 mw = len(str(lncount + fl - 1))
259 mw = len(str(lncount + fl - 1))
260 sp = self.linenospecial
260 sp = self.linenospecial
261 st = self.linenostep
261 st = self.linenostep
262 la = self.lineanchors
262 la = self.lineanchors
263 aln = self.anchorlinenos
263 aln = self.anchorlinenos
264 nocls = self.noclasses
264 nocls = self.noclasses
265 if sp:
265 if sp:
266 lines = []
266 lines = []
267
267
268 for i in range(fl, fl + lncount):
268 for i in range(fl, fl + lncount):
269 if i % st == 0:
269 if i % st == 0:
270 if i % sp == 0:
270 if i % sp == 0:
271 if aln:
271 if aln:
272 lines.append('<a href="#%s%d" class="special">%*d</a>' %
272 lines.append('<a href="#%s%d" class="special">%*d</a>' %
273 (la, i, mw, i))
273 (la, i, mw, i))
274 else:
274 else:
275 lines.append('<span class="special">%*d</span>' % (mw, i))
275 lines.append('<span class="special">%*d</span>' % (mw, i))
276 else:
276 else:
277 if aln:
277 if aln:
278 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
278 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
279 else:
279 else:
280 lines.append('%*d' % (mw, i))
280 lines.append('%*d' % (mw, i))
281 else:
281 else:
282 lines.append('')
282 lines.append('')
283 ls = '\n'.join(lines)
283 ls = '\n'.join(lines)
284 else:
284 else:
285 lines = []
285 lines = []
286 for i in range(fl, fl + lncount):
286 for i in range(fl, fl + lncount):
287 if i % st == 0:
287 if i % st == 0:
288 if aln:
288 if aln:
289 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
289 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
290 else:
290 else:
291 lines.append('%*d' % (mw, i))
291 lines.append('%*d' % (mw, i))
292 else:
292 else:
293 lines.append('')
293 lines.append('')
294 ls = '\n'.join(lines)
294 ls = '\n'.join(lines)
295
295
296 # in case you wonder about the seemingly redundant <div> here: since the
296 # in case you wonder about the seemingly redundant <div> here: since the
297 # content in the other cell also is wrapped in a div, some browsers in
297 # content in the other cell also is wrapped in a div, some browsers in
298 # some configurations seem to mess up the formatting...
298 # some configurations seem to mess up the formatting...
299 if nocls:
299 if nocls:
300 yield 0, ('<table class="%stable">' % self.cssclass +
300 yield 0, ('<table class="%stable">' % self.cssclass +
301 '<tr><td><div class="linenodiv" '
301 '<tr><td><div class="linenodiv" '
302 'style="background-color: #f0f0f0; padding-right: 10px">'
302 'style="background-color: #f0f0f0; padding-right: 10px">'
303 '<pre style="line-height: 125%">' +
303 '<pre style="line-height: 125%">' +
304 ls + '</pre></div></td><td id="hlcode" class="code">')
304 ls + '</pre></div></td><td id="hlcode" class="code">')
305 else:
305 else:
306 yield 0, ('<table class="%stable">' % self.cssclass +
306 yield 0, ('<table class="%stable">' % self.cssclass +
307 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
307 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
308 ls + '</pre></div></td><td id="hlcode" class="code">')
308 ls + '</pre></div></td><td id="hlcode" class="code">')
309 yield 0, dummyoutfile.getvalue()
309 yield 0, dummyoutfile.getvalue()
310 yield 0, '</td></tr></table>'
310 yield 0, '</td></tr></table>'
311
311
312
312
313 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
313 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
314 def __init__(self, **kw):
314 def __init__(self, **kw):
315 # only show these line numbers if set
315 # only show these line numbers if set
316 self.only_lines = kw.pop('only_line_numbers', [])
316 self.only_lines = kw.pop('only_line_numbers', [])
317 self.query_terms = kw.pop('query_terms', [])
317 self.query_terms = kw.pop('query_terms', [])
318 self.max_lines = kw.pop('max_lines', 5)
318 self.max_lines = kw.pop('max_lines', 5)
319 self.line_context = kw.pop('line_context', 3)
319 self.line_context = kw.pop('line_context', 3)
320 self.url = kw.pop('url', None)
320 self.url = kw.pop('url', None)
321
321
322 super(CodeHtmlFormatter, self).__init__(**kw)
322 super(CodeHtmlFormatter, self).__init__(**kw)
323
323
324 def _wrap_code(self, source):
324 def _wrap_code(self, source):
325 for cnt, it in enumerate(source):
325 for cnt, it in enumerate(source):
326 i, t = it
326 i, t = it
327 t = '<pre>%s</pre>' % t
327 t = '<pre>%s</pre>' % t
328 yield i, t
328 yield i, t
329
329
330 def _wrap_tablelinenos(self, inner):
330 def _wrap_tablelinenos(self, inner):
331 yield 0, '<table class="code-highlight %stable">' % self.cssclass
331 yield 0, '<table class="code-highlight %stable">' % self.cssclass
332
332
333 last_shown_line_number = 0
333 last_shown_line_number = 0
334 current_line_number = 1
334 current_line_number = 1
335
335
336 for t, line in inner:
336 for t, line in inner:
337 if not t:
337 if not t:
338 yield t, line
338 yield t, line
339 continue
339 continue
340
340
341 if current_line_number in self.only_lines:
341 if current_line_number in self.only_lines:
342 if last_shown_line_number + 1 != current_line_number:
342 if last_shown_line_number + 1 != current_line_number:
343 yield 0, '<tr>'
343 yield 0, '<tr>'
344 yield 0, '<td class="line">...</td>'
344 yield 0, '<td class="line">...</td>'
345 yield 0, '<td id="hlcode" class="code"></td>'
345 yield 0, '<td id="hlcode" class="code"></td>'
346 yield 0, '</tr>'
346 yield 0, '</tr>'
347
347
348 yield 0, '<tr>'
348 yield 0, '<tr>'
349 if self.url:
349 if self.url:
350 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
350 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
351 self.url, current_line_number, current_line_number)
351 self.url, current_line_number, current_line_number)
352 else:
352 else:
353 yield 0, '<td class="line"><a href="">%i</a></td>' % (
353 yield 0, '<td class="line"><a href="">%i</a></td>' % (
354 current_line_number)
354 current_line_number)
355 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
355 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
356 yield 0, '</tr>'
356 yield 0, '</tr>'
357
357
358 last_shown_line_number = current_line_number
358 last_shown_line_number = current_line_number
359
359
360 current_line_number += 1
360 current_line_number += 1
361
361
362
362
363 yield 0, '</table>'
363 yield 0, '</table>'
364
364
365
365
366 def extract_phrases(text_query):
366 def extract_phrases(text_query):
367 """
367 """
368 Extracts phrases from search term string making sure phrases
368 Extracts phrases from search term string making sure phrases
369 contained in double quotes are kept together - and discarding empty values
369 contained in double quotes are kept together - and discarding empty values
370 or fully whitespace values eg.
370 or fully whitespace values eg.
371
371
372 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
372 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
373
373
374 """
374 """
375
375
376 in_phrase = False
376 in_phrase = False
377 buf = ''
377 buf = ''
378 phrases = []
378 phrases = []
379 for char in text_query:
379 for char in text_query:
380 if in_phrase:
380 if in_phrase:
381 if char == '"': # end phrase
381 if char == '"': # end phrase
382 phrases.append(buf)
382 phrases.append(buf)
383 buf = ''
383 buf = ''
384 in_phrase = False
384 in_phrase = False
385 continue
385 continue
386 else:
386 else:
387 buf += char
387 buf += char
388 continue
388 continue
389 else:
389 else:
390 if char == '"': # start phrase
390 if char == '"': # start phrase
391 in_phrase = True
391 in_phrase = True
392 phrases.append(buf)
392 phrases.append(buf)
393 buf = ''
393 buf = ''
394 continue
394 continue
395 elif char == ' ':
395 elif char == ' ':
396 phrases.append(buf)
396 phrases.append(buf)
397 buf = ''
397 buf = ''
398 continue
398 continue
399 else:
399 else:
400 buf += char
400 buf += char
401
401
402 phrases.append(buf)
402 phrases.append(buf)
403 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
403 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
404 return phrases
404 return phrases
405
405
406
406
407 def get_matching_offsets(text, phrases):
407 def get_matching_offsets(text, phrases):
408 """
408 """
409 Returns a list of string offsets in `text` that the list of `terms` match
409 Returns a list of string offsets in `text` that the list of `terms` match
410
410
411 >>> get_matching_offsets('some text here', ['some', 'here'])
411 >>> get_matching_offsets('some text here', ['some', 'here'])
412 [(0, 4), (10, 14)]
412 [(0, 4), (10, 14)]
413
413
414 """
414 """
415 offsets = []
415 offsets = []
416 for phrase in phrases:
416 for phrase in phrases:
417 for match in re.finditer(phrase, text):
417 for match in re.finditer(phrase, text):
418 offsets.append((match.start(), match.end()))
418 offsets.append((match.start(), match.end()))
419
419
420 return offsets
420 return offsets
421
421
422
422
423 def normalize_text_for_matching(x):
423 def normalize_text_for_matching(x):
424 """
424 """
425 Replaces all non alnum characters to spaces and lower cases the string,
425 Replaces all non alnum characters to spaces and lower cases the string,
426 useful for comparing two text strings without punctuation
426 useful for comparing two text strings without punctuation
427 """
427 """
428 return re.sub(r'[^\w]', ' ', x.lower())
428 return re.sub(r'[^\w]', ' ', x.lower())
429
429
430
430
431 def get_matching_line_offsets(lines, terms):
431 def get_matching_line_offsets(lines, terms):
432 """ Return a set of `lines` indices (starting from 1) matching a
432 """ Return a set of `lines` indices (starting from 1) matching a
433 text search query, along with `context` lines above/below matching lines
433 text search query, along with `context` lines above/below matching lines
434
434
435 :param lines: list of strings representing lines
435 :param lines: list of strings representing lines
436 :param terms: search term string to match in lines eg. 'some text'
436 :param terms: search term string to match in lines eg. 'some text'
437 :param context: number of lines above/below a matching line to add to result
437 :param context: number of lines above/below a matching line to add to result
438 :param max_lines: cut off for lines of interest
438 :param max_lines: cut off for lines of interest
439 eg.
439 eg.
440
440
441 >>> get_matching_line_offsets('''
441 text = '''
442 words words words
442 words words words
443 words words words
443 words words words
444 some text some
444 some text some
445 words words words
445 words words words
446 words words words
446 words words words
447 text here what
447 text here what
448 ''', 'text', context=1)
448 '''
449 get_matching_line_offsets(text, 'text', context=1)
449 {3: [(5, 9)], 6: [(0, 4)]]
450 {3: [(5, 9)], 6: [(0, 4)]]
451
450 """
452 """
451 matching_lines = {}
453 matching_lines = {}
452 phrases = [normalize_text_for_matching(phrase)
454 phrases = [normalize_text_for_matching(phrase)
453 for phrase in extract_phrases(terms)]
455 for phrase in extract_phrases(terms)]
454
456
455 for line_index, line in enumerate(lines, start=1):
457 for line_index, line in enumerate(lines, start=1):
456 match_offsets = get_matching_offsets(
458 match_offsets = get_matching_offsets(
457 normalize_text_for_matching(line), phrases)
459 normalize_text_for_matching(line), phrases)
458 if match_offsets:
460 if match_offsets:
459 matching_lines[line_index] = match_offsets
461 matching_lines[line_index] = match_offsets
460
462
461 return matching_lines
463 return matching_lines
462
464
465
463 def get_lexer_safe(mimetype=None, filepath=None):
466 def get_lexer_safe(mimetype=None, filepath=None):
464 """
467 """
465 Tries to return a relevant pygments lexer using mimetype/filepath name,
468 Tries to return a relevant pygments lexer using mimetype/filepath name,
466 defaulting to plain text if none could be found
469 defaulting to plain text if none could be found
467 """
470 """
468 lexer = None
471 lexer = None
469 try:
472 try:
470 if mimetype:
473 if mimetype:
471 lexer = get_lexer_for_mimetype(mimetype)
474 lexer = get_lexer_for_mimetype(mimetype)
472 if not lexer:
475 if not lexer:
473 lexer = get_lexer_for_filename(path)
476 lexer = get_lexer_for_filename(filepath)
474 except pygments.util.ClassNotFound:
477 except pygments.util.ClassNotFound:
475 pass
478 pass
476
479
477 if not lexer:
480 if not lexer:
478 lexer = get_lexer_by_name('text')
481 lexer = get_lexer_by_name('text')
479
482
480 return lexer
483 return lexer
481
484
482
485
483 def pygmentize(filenode, **kwargs):
486 def pygmentize(filenode, **kwargs):
484 """
487 """
485 pygmentize function using pygments
488 pygmentize function using pygments
486
489
487 :param filenode:
490 :param filenode:
488 """
491 """
489 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
492 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
490 return literal(code_highlight(filenode.content, lexer,
493 return literal(code_highlight(filenode.content, lexer,
491 CodeHtmlFormatter(**kwargs)))
494 CodeHtmlFormatter(**kwargs)))
492
495
493
496
494 def pygmentize_annotation(repo_name, filenode, **kwargs):
497 def pygmentize_annotation(repo_name, filenode, **kwargs):
495 """
498 """
496 pygmentize function for annotation
499 pygmentize function for annotation
497
500
498 :param filenode:
501 :param filenode:
499 """
502 """
500
503
501 color_dict = {}
504 color_dict = {}
502
505
503 def gen_color(n=10000):
506 def gen_color(n=10000):
504 """generator for getting n of evenly distributed colors using
507 """generator for getting n of evenly distributed colors using
505 hsv color and golden ratio. It always return same order of colors
508 hsv color and golden ratio. It always return same order of colors
506
509
507 :returns: RGB tuple
510 :returns: RGB tuple
508 """
511 """
509
512
510 def hsv_to_rgb(h, s, v):
513 def hsv_to_rgb(h, s, v):
511 if s == 0.0:
514 if s == 0.0:
512 return v, v, v
515 return v, v, v
513 i = int(h * 6.0) # XXX assume int() truncates!
516 i = int(h * 6.0) # XXX assume int() truncates!
514 f = (h * 6.0) - i
517 f = (h * 6.0) - i
515 p = v * (1.0 - s)
518 p = v * (1.0 - s)
516 q = v * (1.0 - s * f)
519 q = v * (1.0 - s * f)
517 t = v * (1.0 - s * (1.0 - f))
520 t = v * (1.0 - s * (1.0 - f))
518 i = i % 6
521 i = i % 6
519 if i == 0:
522 if i == 0:
520 return v, t, p
523 return v, t, p
521 if i == 1:
524 if i == 1:
522 return q, v, p
525 return q, v, p
523 if i == 2:
526 if i == 2:
524 return p, v, t
527 return p, v, t
525 if i == 3:
528 if i == 3:
526 return p, q, v
529 return p, q, v
527 if i == 4:
530 if i == 4:
528 return t, p, v
531 return t, p, v
529 if i == 5:
532 if i == 5:
530 return v, p, q
533 return v, p, q
531
534
532 golden_ratio = 0.618033988749895
535 golden_ratio = 0.618033988749895
533 h = 0.22717784590367374
536 h = 0.22717784590367374
534
537
535 for _ in xrange(n):
538 for _ in xrange(n):
536 h += golden_ratio
539 h += golden_ratio
537 h %= 1
540 h %= 1
538 HSV_tuple = [h, 0.95, 0.95]
541 HSV_tuple = [h, 0.95, 0.95]
539 RGB_tuple = hsv_to_rgb(*HSV_tuple)
542 RGB_tuple = hsv_to_rgb(*HSV_tuple)
540 yield map(lambda x: str(int(x * 256)), RGB_tuple)
543 yield map(lambda x: str(int(x * 256)), RGB_tuple)
541
544
542 cgenerator = gen_color()
545 cgenerator = gen_color()
543
546
544 def get_color_string(commit_id):
547 def get_color_string(commit_id):
545 if commit_id in color_dict:
548 if commit_id in color_dict:
546 col = color_dict[commit_id]
549 col = color_dict[commit_id]
547 else:
550 else:
548 col = color_dict[commit_id] = cgenerator.next()
551 col = color_dict[commit_id] = cgenerator.next()
549 return "color: rgb(%s)! important;" % (', '.join(col))
552 return "color: rgb(%s)! important;" % (', '.join(col))
550
553
551 def url_func(repo_name):
554 def url_func(repo_name):
552
555
553 def _url_func(commit):
556 def _url_func(commit):
554 author = commit.author
557 author = commit.author
555 date = commit.date
558 date = commit.date
556 message = tooltip(commit.message)
559 message = tooltip(commit.message)
557
560
558 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
561 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
559 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
562 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
560 "</b> %s<br/></div>")
563 "</b> %s<br/></div>")
561
564
562 tooltip_html = tooltip_html % (author, date, message)
565 tooltip_html = tooltip_html % (author, date, message)
563 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
566 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
564 uri = link_to(
567 uri = link_to(
565 lnk_format,
568 lnk_format,
566 url('changeset_home', repo_name=repo_name,
569 url('changeset_home', repo_name=repo_name,
567 revision=commit.raw_id),
570 revision=commit.raw_id),
568 style=get_color_string(commit.raw_id),
571 style=get_color_string(commit.raw_id),
569 class_='tooltip',
572 class_='tooltip',
570 title=tooltip_html
573 title=tooltip_html
571 )
574 )
572
575
573 uri += '\n'
576 uri += '\n'
574 return uri
577 return uri
575 return _url_func
578 return _url_func
576
579
577 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
580 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
578
581
579
582
580 def is_following_repo(repo_name, user_id):
583 def is_following_repo(repo_name, user_id):
581 from rhodecode.model.scm import ScmModel
584 from rhodecode.model.scm import ScmModel
582 return ScmModel().is_following_repo(repo_name, user_id)
585 return ScmModel().is_following_repo(repo_name, user_id)
583
586
584
587
585 class _Message(object):
588 class _Message(object):
586 """A message returned by ``Flash.pop_messages()``.
589 """A message returned by ``Flash.pop_messages()``.
587
590
588 Converting the message to a string returns the message text. Instances
591 Converting the message to a string returns the message text. Instances
589 also have the following attributes:
592 also have the following attributes:
590
593
591 * ``message``: the message text.
594 * ``message``: the message text.
592 * ``category``: the category specified when the message was created.
595 * ``category``: the category specified when the message was created.
593 """
596 """
594
597
595 def __init__(self, category, message):
598 def __init__(self, category, message):
596 self.category = category
599 self.category = category
597 self.message = message
600 self.message = message
598
601
599 def __str__(self):
602 def __str__(self):
600 return self.message
603 return self.message
601
604
602 __unicode__ = __str__
605 __unicode__ = __str__
603
606
604 def __html__(self):
607 def __html__(self):
605 return escape(safe_unicode(self.message))
608 return escape(safe_unicode(self.message))
606
609
607
610
608 class Flash(_Flash):
611 class Flash(_Flash):
609
612
610 def pop_messages(self):
613 def pop_messages(self):
611 """Return all accumulated messages and delete them from the session.
614 """Return all accumulated messages and delete them from the session.
612
615
613 The return value is a list of ``Message`` objects.
616 The return value is a list of ``Message`` objects.
614 """
617 """
615 from pylons import session
618 from pylons import session
616
619
617 messages = []
620 messages = []
618
621
619 # Pop the 'old' pylons flash messages. They are tuples of the form
622 # Pop the 'old' pylons flash messages. They are tuples of the form
620 # (category, message)
623 # (category, message)
621 for cat, msg in session.pop(self.session_key, []):
624 for cat, msg in session.pop(self.session_key, []):
622 messages.append(_Message(cat, msg))
625 messages.append(_Message(cat, msg))
623
626
624 # Pop the 'new' pyramid flash messages for each category as list
627 # Pop the 'new' pyramid flash messages for each category as list
625 # of strings.
628 # of strings.
626 for cat in self.categories:
629 for cat in self.categories:
627 for msg in session.pop_flash(queue=cat):
630 for msg in session.pop_flash(queue=cat):
628 messages.append(_Message(cat, msg))
631 messages.append(_Message(cat, msg))
629 # Map messages from the default queue to the 'notice' category.
632 # Map messages from the default queue to the 'notice' category.
630 for msg in session.pop_flash():
633 for msg in session.pop_flash():
631 messages.append(_Message('notice', msg))
634 messages.append(_Message('notice', msg))
632
635
633 session.save()
636 session.save()
634 return messages
637 return messages
635
638
636 flash = Flash()
639 flash = Flash()
637
640
638 #==============================================================================
641 #==============================================================================
639 # SCM FILTERS available via h.
642 # SCM FILTERS available via h.
640 #==============================================================================
643 #==============================================================================
641 from rhodecode.lib.vcs.utils import author_name, author_email
644 from rhodecode.lib.vcs.utils import author_name, author_email
642 from rhodecode.lib.utils2 import credentials_filter, age as _age
645 from rhodecode.lib.utils2 import credentials_filter, age as _age
643 from rhodecode.model.db import User, ChangesetStatus
646 from rhodecode.model.db import User, ChangesetStatus
644
647
645 age = _age
648 age = _age
646 capitalize = lambda x: x.capitalize()
649 capitalize = lambda x: x.capitalize()
647 email = author_email
650 email = author_email
648 short_id = lambda x: x[:12]
651 short_id = lambda x: x[:12]
649 hide_credentials = lambda x: ''.join(credentials_filter(x))
652 hide_credentials = lambda x: ''.join(credentials_filter(x))
650
653
651
654
652 def age_component(datetime_iso, value=None, time_is_local=False):
655 def age_component(datetime_iso, value=None, time_is_local=False):
653 title = value or format_date(datetime_iso)
656 title = value or format_date(datetime_iso)
654
657
655 # detect if we have a timezone info, otherwise, add it
658 # detect if we have a timezone info, otherwise, add it
656 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
659 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
657 tzinfo = '+00:00'
660 tzinfo = '+00:00'
658
661
659 if time_is_local:
662 if time_is_local:
660 tzinfo = time.strftime("+%H:%M",
663 tzinfo = time.strftime("+%H:%M",
661 time.gmtime(
664 time.gmtime(
662 (datetime.now() - datetime.utcnow()).seconds + 1
665 (datetime.now() - datetime.utcnow()).seconds + 1
663 )
666 )
664 )
667 )
665
668
666 return literal(
669 return literal(
667 '<time class="timeago tooltip" '
670 '<time class="timeago tooltip" '
668 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
671 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
669 datetime_iso, title, tzinfo))
672 datetime_iso, title, tzinfo))
670
673
671
674
672 def _shorten_commit_id(commit_id):
675 def _shorten_commit_id(commit_id):
673 from rhodecode import CONFIG
676 from rhodecode import CONFIG
674 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
677 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
675 return commit_id[:def_len]
678 return commit_id[:def_len]
676
679
677
680
678 def get_repo_id_from_name(repo_name):
679 repo = get_by_repo_name(repo_name)
680 return repo.repo_id
681
682
683 def show_id(commit):
681 def show_id(commit):
684 """
682 """
685 Configurable function that shows ID
683 Configurable function that shows ID
686 by default it's r123:fffeeefffeee
684 by default it's r123:fffeeefffeee
687
685
688 :param commit: commit instance
686 :param commit: commit instance
689 """
687 """
690 from rhodecode import CONFIG
688 from rhodecode import CONFIG
691 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
689 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
692
690
693 raw_id = _shorten_commit_id(commit.raw_id)
691 raw_id = _shorten_commit_id(commit.raw_id)
694 if show_idx:
692 if show_idx:
695 return 'r%s:%s' % (commit.idx, raw_id)
693 return 'r%s:%s' % (commit.idx, raw_id)
696 else:
694 else:
697 return '%s' % (raw_id, )
695 return '%s' % (raw_id, )
698
696
699
697
700 def format_date(date):
698 def format_date(date):
701 """
699 """
702 use a standardized formatting for dates used in RhodeCode
700 use a standardized formatting for dates used in RhodeCode
703
701
704 :param date: date/datetime object
702 :param date: date/datetime object
705 :return: formatted date
703 :return: formatted date
706 """
704 """
707
705
708 if date:
706 if date:
709 _fmt = "%a, %d %b %Y %H:%M:%S"
707 _fmt = "%a, %d %b %Y %H:%M:%S"
710 return safe_unicode(date.strftime(_fmt))
708 return safe_unicode(date.strftime(_fmt))
711
709
712 return u""
710 return u""
713
711
714
712
715 class _RepoChecker(object):
713 class _RepoChecker(object):
716
714
717 def __init__(self, backend_alias):
715 def __init__(self, backend_alias):
718 self._backend_alias = backend_alias
716 self._backend_alias = backend_alias
719
717
720 def __call__(self, repository):
718 def __call__(self, repository):
721 if hasattr(repository, 'alias'):
719 if hasattr(repository, 'alias'):
722 _type = repository.alias
720 _type = repository.alias
723 elif hasattr(repository, 'repo_type'):
721 elif hasattr(repository, 'repo_type'):
724 _type = repository.repo_type
722 _type = repository.repo_type
725 else:
723 else:
726 _type = repository
724 _type = repository
727 return _type == self._backend_alias
725 return _type == self._backend_alias
728
726
729 is_git = _RepoChecker('git')
727 is_git = _RepoChecker('git')
730 is_hg = _RepoChecker('hg')
728 is_hg = _RepoChecker('hg')
731 is_svn = _RepoChecker('svn')
729 is_svn = _RepoChecker('svn')
732
730
733
731
734 def get_repo_type_by_name(repo_name):
732 def get_repo_type_by_name(repo_name):
735 repo = Repository.get_by_repo_name(repo_name)
733 repo = Repository.get_by_repo_name(repo_name)
736 return repo.repo_type
734 return repo.repo_type
737
735
738
736
739 def is_svn_without_proxy(repository):
737 def is_svn_without_proxy(repository):
740 from rhodecode import CONFIG
738 from rhodecode import CONFIG
741 if is_svn(repository):
739 if is_svn(repository):
742 if not CONFIG.get('rhodecode_proxy_subversion_http_requests', False):
740 if not CONFIG.get('rhodecode_proxy_subversion_http_requests', False):
743 return True
741 return True
744 return False
742 return False
745
743
746
744
747 def discover_user(author):
745 def discover_user(author):
748 """
746 """
749 Tries to discover RhodeCode User based on the autho string. Author string
747 Tries to discover RhodeCode User based on the autho string. Author string
750 is typically `FirstName LastName <email@address.com>`
748 is typically `FirstName LastName <email@address.com>`
751 """
749 """
752
750
753 # if author is already an instance use it for extraction
751 # if author is already an instance use it for extraction
754 if isinstance(author, User):
752 if isinstance(author, User):
755 return author
753 return author
756
754
757 # Valid email in the attribute passed, see if they're in the system
755 # Valid email in the attribute passed, see if they're in the system
758 _email = author_email(author)
756 _email = author_email(author)
759 if _email != '':
757 if _email != '':
760 user = User.get_by_email(_email, case_insensitive=True, cache=True)
758 user = User.get_by_email(_email, case_insensitive=True, cache=True)
761 if user is not None:
759 if user is not None:
762 return user
760 return user
763
761
764 # Maybe it's a username, we try to extract it and fetch by username ?
762 # Maybe it's a username, we try to extract it and fetch by username ?
765 _author = author_name(author)
763 _author = author_name(author)
766 user = User.get_by_username(_author, case_insensitive=True, cache=True)
764 user = User.get_by_username(_author, case_insensitive=True, cache=True)
767 if user is not None:
765 if user is not None:
768 return user
766 return user
769
767
770 return None
768 return None
771
769
772
770
773 def email_or_none(author):
771 def email_or_none(author):
774 # extract email from the commit string
772 # extract email from the commit string
775 _email = author_email(author)
773 _email = author_email(author)
776 if _email != '':
774 if _email != '':
777 # check it against RhodeCode database, and use the MAIN email for this
775 # check it against RhodeCode database, and use the MAIN email for this
778 # user
776 # user
779 user = User.get_by_email(_email, case_insensitive=True, cache=True)
777 user = User.get_by_email(_email, case_insensitive=True, cache=True)
780 if user is not None:
778 if user is not None:
781 return user.email
779 return user.email
782 return _email
780 return _email
783
781
784 # See if it contains a username we can get an email from
782 # See if it contains a username we can get an email from
785 user = User.get_by_username(author_name(author), case_insensitive=True,
783 user = User.get_by_username(author_name(author), case_insensitive=True,
786 cache=True)
784 cache=True)
787 if user is not None:
785 if user is not None:
788 return user.email
786 return user.email
789
787
790 # No valid email, not a valid user in the system, none!
788 # No valid email, not a valid user in the system, none!
791 return None
789 return None
792
790
793
791
794 def link_to_user(author, length=0, **kwargs):
792 def link_to_user(author, length=0, **kwargs):
795 user = discover_user(author)
793 user = discover_user(author)
796 # user can be None, but if we have it already it means we can re-use it
794 # user can be None, but if we have it already it means we can re-use it
797 # in the person() function, so we save 1 intensive-query
795 # in the person() function, so we save 1 intensive-query
798 if user:
796 if user:
799 author = user
797 author = user
800
798
801 display_person = person(author, 'username_or_name_or_email')
799 display_person = person(author, 'username_or_name_or_email')
802 if length:
800 if length:
803 display_person = shorter(display_person, length)
801 display_person = shorter(display_person, length)
804
802
805 if user:
803 if user:
806 return link_to(
804 return link_to(
807 escape(display_person),
805 escape(display_person),
808 url('user_profile', username=user.username),
806 url('user_profile', username=user.username),
809 **kwargs)
807 **kwargs)
810 else:
808 else:
811 return escape(display_person)
809 return escape(display_person)
812
810
813
811
814 def person(author, show_attr="username_and_name"):
812 def person(author, show_attr="username_and_name"):
815 user = discover_user(author)
813 user = discover_user(author)
816 if user:
814 if user:
817 return getattr(user, show_attr)
815 return getattr(user, show_attr)
818 else:
816 else:
819 _author = author_name(author)
817 _author = author_name(author)
820 _email = email(author)
818 _email = email(author)
821 return _author or _email
819 return _author or _email
822
820
823
821
824 def person_by_id(id_, show_attr="username_and_name"):
822 def person_by_id(id_, show_attr="username_and_name"):
825 # attr to return from fetched user
823 # attr to return from fetched user
826 person_getter = lambda usr: getattr(usr, show_attr)
824 person_getter = lambda usr: getattr(usr, show_attr)
827
825
828 #maybe it's an ID ?
826 #maybe it's an ID ?
829 if str(id_).isdigit() or isinstance(id_, int):
827 if str(id_).isdigit() or isinstance(id_, int):
830 id_ = int(id_)
828 id_ = int(id_)
831 user = User.get(id_)
829 user = User.get(id_)
832 if user is not None:
830 if user is not None:
833 return person_getter(user)
831 return person_getter(user)
834 return id_
832 return id_
835
833
836
834
837 def gravatar_with_user(author, show_disabled=False):
835 def gravatar_with_user(author, show_disabled=False):
838 from rhodecode.lib.utils import PartialRenderer
836 from rhodecode.lib.utils import PartialRenderer
839 _render = PartialRenderer('base/base.html')
837 _render = PartialRenderer('base/base.html')
840 return _render('gravatar_with_user', author, show_disabled=show_disabled)
838 return _render('gravatar_with_user', author, show_disabled=show_disabled)
841
839
842
840
843 def desc_stylize(value):
841 def desc_stylize(value):
844 """
842 """
845 converts tags from value into html equivalent
843 converts tags from value into html equivalent
846
844
847 :param value:
845 :param value:
848 """
846 """
849 if not value:
847 if not value:
850 return ''
848 return ''
851
849
852 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
850 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
853 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
851 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
854 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
852 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
855 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
853 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
856 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
854 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
857 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
855 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
858 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
856 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
859 '<div class="metatag" tag="lang">\\2</div>', value)
857 '<div class="metatag" tag="lang">\\2</div>', value)
860 value = re.sub(r'\[([a-z]+)\]',
858 value = re.sub(r'\[([a-z]+)\]',
861 '<div class="metatag" tag="\\1">\\1</div>', value)
859 '<div class="metatag" tag="\\1">\\1</div>', value)
862
860
863 return value
861 return value
864
862
865
863
866 def escaped_stylize(value):
864 def escaped_stylize(value):
867 """
865 """
868 converts tags from value into html equivalent, but escaping its value first
866 converts tags from value into html equivalent, but escaping its value first
869 """
867 """
870 if not value:
868 if not value:
871 return ''
869 return ''
872
870
873 # Using default webhelper escape method, but has to force it as a
871 # Using default webhelper escape method, but has to force it as a
874 # plain unicode instead of a markup tag to be used in regex expressions
872 # plain unicode instead of a markup tag to be used in regex expressions
875 value = unicode(escape(safe_unicode(value)))
873 value = unicode(escape(safe_unicode(value)))
876
874
877 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
875 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
878 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
876 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
879 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
877 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
880 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
878 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
881 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
879 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
882 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
880 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
883 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
881 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
884 '<div class="metatag" tag="lang">\\2</div>', value)
882 '<div class="metatag" tag="lang">\\2</div>', value)
885 value = re.sub(r'\[([a-z]+)\]',
883 value = re.sub(r'\[([a-z]+)\]',
886 '<div class="metatag" tag="\\1">\\1</div>', value)
884 '<div class="metatag" tag="\\1">\\1</div>', value)
887
885
888 return value
886 return value
889
887
890
888
891 def bool2icon(value):
889 def bool2icon(value):
892 """
890 """
893 Returns boolean value of a given value, represented as html element with
891 Returns boolean value of a given value, represented as html element with
894 classes that will represent icons
892 classes that will represent icons
895
893
896 :param value: given value to convert to html node
894 :param value: given value to convert to html node
897 """
895 """
898
896
899 if value: # does bool conversion
897 if value: # does bool conversion
900 return HTML.tag('i', class_="icon-true")
898 return HTML.tag('i', class_="icon-true")
901 else: # not true as bool
899 else: # not true as bool
902 return HTML.tag('i', class_="icon-false")
900 return HTML.tag('i', class_="icon-false")
903
901
904
902
905 #==============================================================================
903 #==============================================================================
906 # PERMS
904 # PERMS
907 #==============================================================================
905 #==============================================================================
908 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
906 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
909 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
907 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
910 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
908 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
911
909
912
910
913 #==============================================================================
911 #==============================================================================
914 # GRAVATAR URL
912 # GRAVATAR URL
915 #==============================================================================
913 #==============================================================================
916 class InitialsGravatar(object):
914 class InitialsGravatar(object):
917 def __init__(self, email_address, first_name, last_name, size=30,
915 def __init__(self, email_address, first_name, last_name, size=30,
918 background=None, text_color='#fff'):
916 background=None, text_color='#fff'):
919 self.size = size
917 self.size = size
920 self.first_name = first_name
918 self.first_name = first_name
921 self.last_name = last_name
919 self.last_name = last_name
922 self.email_address = email_address
920 self.email_address = email_address
923 self.background = background or self.str2color(email_address)
921 self.background = background or self.str2color(email_address)
924 self.text_color = text_color
922 self.text_color = text_color
925
923
926 def get_color_bank(self):
924 def get_color_bank(self):
927 """
925 """
928 returns a predefined list of colors that gravatars can use.
926 returns a predefined list of colors that gravatars can use.
929 Those are randomized distinct colors that guarantee readability and
927 Those are randomized distinct colors that guarantee readability and
930 uniqueness.
928 uniqueness.
931
929
932 generated with: http://phrogz.net/css/distinct-colors.html
930 generated with: http://phrogz.net/css/distinct-colors.html
933 """
931 """
934 return [
932 return [
935 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
933 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
936 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
934 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
937 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
935 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
938 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
936 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
939 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
937 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
940 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
938 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
941 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
939 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
942 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
940 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
943 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
941 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
944 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
942 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
945 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
943 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
946 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
944 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
947 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
945 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
948 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
946 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
949 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
947 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
950 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
948 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
951 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
949 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
952 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
950 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
953 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
951 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
954 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
952 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
955 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
953 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
956 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
954 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
957 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
955 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
958 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
956 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
959 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
957 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
960 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
958 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
961 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
959 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
962 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
960 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
963 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
961 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
964 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
962 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
965 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
963 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
966 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
964 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
967 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
965 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
968 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
966 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
969 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
967 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
970 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
968 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
971 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
969 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
972 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
970 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
973 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
971 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
974 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
972 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
975 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
973 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
976 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
974 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
977 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
975 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
978 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
976 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
979 '#4f8c46', '#368dd9', '#5c0073'
977 '#4f8c46', '#368dd9', '#5c0073'
980 ]
978 ]
981
979
982 def rgb_to_hex_color(self, rgb_tuple):
980 def rgb_to_hex_color(self, rgb_tuple):
983 """
981 """
984 Converts an rgb_tuple passed to an hex color.
982 Converts an rgb_tuple passed to an hex color.
985
983
986 :param rgb_tuple: tuple with 3 ints represents rgb color space
984 :param rgb_tuple: tuple with 3 ints represents rgb color space
987 """
985 """
988 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
986 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
989
987
990 def email_to_int_list(self, email_str):
988 def email_to_int_list(self, email_str):
991 """
989 """
992 Get every byte of the hex digest value of email and turn it to integer.
990 Get every byte of the hex digest value of email and turn it to integer.
993 It's going to be always between 0-255
991 It's going to be always between 0-255
994 """
992 """
995 digest = md5_safe(email_str.lower())
993 digest = md5_safe(email_str.lower())
996 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
994 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
997
995
998 def pick_color_bank_index(self, email_str, color_bank):
996 def pick_color_bank_index(self, email_str, color_bank):
999 return self.email_to_int_list(email_str)[0] % len(color_bank)
997 return self.email_to_int_list(email_str)[0] % len(color_bank)
1000
998
1001 def str2color(self, email_str):
999 def str2color(self, email_str):
1002 """
1000 """
1003 Tries to map in a stable algorithm an email to color
1001 Tries to map in a stable algorithm an email to color
1004
1002
1005 :param email_str:
1003 :param email_str:
1006 """
1004 """
1007 color_bank = self.get_color_bank()
1005 color_bank = self.get_color_bank()
1008 # pick position (module it's length so we always find it in the
1006 # pick position (module it's length so we always find it in the
1009 # bank even if it's smaller than 256 values
1007 # bank even if it's smaller than 256 values
1010 pos = self.pick_color_bank_index(email_str, color_bank)
1008 pos = self.pick_color_bank_index(email_str, color_bank)
1011 return color_bank[pos]
1009 return color_bank[pos]
1012
1010
1013 def normalize_email(self, email_address):
1011 def normalize_email(self, email_address):
1014 import unicodedata
1012 import unicodedata
1015 # default host used to fill in the fake/missing email
1013 # default host used to fill in the fake/missing email
1016 default_host = u'localhost'
1014 default_host = u'localhost'
1017
1015
1018 if not email_address:
1016 if not email_address:
1019 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1017 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1020
1018
1021 email_address = safe_unicode(email_address)
1019 email_address = safe_unicode(email_address)
1022
1020
1023 if u'@' not in email_address:
1021 if u'@' not in email_address:
1024 email_address = u'%s@%s' % (email_address, default_host)
1022 email_address = u'%s@%s' % (email_address, default_host)
1025
1023
1026 if email_address.endswith(u'@'):
1024 if email_address.endswith(u'@'):
1027 email_address = u'%s%s' % (email_address, default_host)
1025 email_address = u'%s%s' % (email_address, default_host)
1028
1026
1029 email_address = unicodedata.normalize('NFKD', email_address)\
1027 email_address = unicodedata.normalize('NFKD', email_address)\
1030 .encode('ascii', 'ignore')
1028 .encode('ascii', 'ignore')
1031 return email_address
1029 return email_address
1032
1030
1033 def get_initials(self):
1031 def get_initials(self):
1034 """
1032 """
1035 Returns 2 letter initials calculated based on the input.
1033 Returns 2 letter initials calculated based on the input.
1036 The algorithm picks first given email address, and takes first letter
1034 The algorithm picks first given email address, and takes first letter
1037 of part before @, and then the first letter of server name. In case
1035 of part before @, and then the first letter of server name. In case
1038 the part before @ is in a format of `somestring.somestring2` it replaces
1036 the part before @ is in a format of `somestring.somestring2` it replaces
1039 the server letter with first letter of somestring2
1037 the server letter with first letter of somestring2
1040
1038
1041 In case function was initialized with both first and lastname, this
1039 In case function was initialized with both first and lastname, this
1042 overrides the extraction from email by first letter of the first and
1040 overrides the extraction from email by first letter of the first and
1043 last name. We add special logic to that functionality, In case Full name
1041 last name. We add special logic to that functionality, In case Full name
1044 is compound, like Guido Von Rossum, we use last part of the last name
1042 is compound, like Guido Von Rossum, we use last part of the last name
1045 (Von Rossum) picking `R`.
1043 (Von Rossum) picking `R`.
1046
1044
1047 Function also normalizes the non-ascii characters to they ascii
1045 Function also normalizes the non-ascii characters to they ascii
1048 representation, eg Δ„ => A
1046 representation, eg Δ„ => A
1049 """
1047 """
1050 import unicodedata
1048 import unicodedata
1051 # replace non-ascii to ascii
1049 # replace non-ascii to ascii
1052 first_name = unicodedata.normalize(
1050 first_name = unicodedata.normalize(
1053 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1051 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1054 last_name = unicodedata.normalize(
1052 last_name = unicodedata.normalize(
1055 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1053 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1056
1054
1057 # do NFKD encoding, and also make sure email has proper format
1055 # do NFKD encoding, and also make sure email has proper format
1058 email_address = self.normalize_email(self.email_address)
1056 email_address = self.normalize_email(self.email_address)
1059
1057
1060 # first push the email initials
1058 # first push the email initials
1061 prefix, server = email_address.split('@', 1)
1059 prefix, server = email_address.split('@', 1)
1062
1060
1063 # check if prefix is maybe a 'firstname.lastname' syntax
1061 # check if prefix is maybe a 'firstname.lastname' syntax
1064 _dot_split = prefix.rsplit('.', 1)
1062 _dot_split = prefix.rsplit('.', 1)
1065 if len(_dot_split) == 2:
1063 if len(_dot_split) == 2:
1066 initials = [_dot_split[0][0], _dot_split[1][0]]
1064 initials = [_dot_split[0][0], _dot_split[1][0]]
1067 else:
1065 else:
1068 initials = [prefix[0], server[0]]
1066 initials = [prefix[0], server[0]]
1069
1067
1070 # then try to replace either firtname or lastname
1068 # then try to replace either firtname or lastname
1071 fn_letter = (first_name or " ")[0].strip()
1069 fn_letter = (first_name or " ")[0].strip()
1072 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1070 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1073
1071
1074 if fn_letter:
1072 if fn_letter:
1075 initials[0] = fn_letter
1073 initials[0] = fn_letter
1076
1074
1077 if ln_letter:
1075 if ln_letter:
1078 initials[1] = ln_letter
1076 initials[1] = ln_letter
1079
1077
1080 return ''.join(initials).upper()
1078 return ''.join(initials).upper()
1081
1079
1082 def get_img_data_by_type(self, font_family, img_type):
1080 def get_img_data_by_type(self, font_family, img_type):
1083 default_user = """
1081 default_user = """
1084 <svg xmlns="http://www.w3.org/2000/svg"
1082 <svg xmlns="http://www.w3.org/2000/svg"
1085 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1083 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1086 viewBox="-15 -10 439.165 429.164"
1084 viewBox="-15 -10 439.165 429.164"
1087
1085
1088 xml:space="preserve"
1086 xml:space="preserve"
1089 style="background:{background};" >
1087 style="background:{background};" >
1090
1088
1091 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1089 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1092 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1090 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1093 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1091 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1094 168.596,153.916,216.671,
1092 168.596,153.916,216.671,
1095 204.583,216.671z" fill="{text_color}"/>
1093 204.583,216.671z" fill="{text_color}"/>
1096 <path d="M407.164,374.717L360.88,
1094 <path d="M407.164,374.717L360.88,
1097 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1095 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1098 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1096 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1099 15.366-44.203,23.488-69.076,23.488c-24.877,
1097 15.366-44.203,23.488-69.076,23.488c-24.877,
1100 0-48.762-8.122-69.078-23.488
1098 0-48.762-8.122-69.078-23.488
1101 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1099 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1102 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1100 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1103 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1101 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1104 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1102 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1105 19.402-10.527 C409.699,390.129,
1103 19.402-10.527 C409.699,390.129,
1106 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1104 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1107 </svg>""".format(
1105 </svg>""".format(
1108 size=self.size,
1106 size=self.size,
1109 background='#979797', # @grey4
1107 background='#979797', # @grey4
1110 text_color=self.text_color,
1108 text_color=self.text_color,
1111 font_family=font_family)
1109 font_family=font_family)
1112
1110
1113 return {
1111 return {
1114 "default_user": default_user
1112 "default_user": default_user
1115 }[img_type]
1113 }[img_type]
1116
1114
1117 def get_img_data(self, svg_type=None):
1115 def get_img_data(self, svg_type=None):
1118 """
1116 """
1119 generates the svg metadata for image
1117 generates the svg metadata for image
1120 """
1118 """
1121
1119
1122 font_family = ','.join([
1120 font_family = ','.join([
1123 'proximanovaregular',
1121 'proximanovaregular',
1124 'Proxima Nova Regular',
1122 'Proxima Nova Regular',
1125 'Proxima Nova',
1123 'Proxima Nova',
1126 'Arial',
1124 'Arial',
1127 'Lucida Grande',
1125 'Lucida Grande',
1128 'sans-serif'
1126 'sans-serif'
1129 ])
1127 ])
1130 if svg_type:
1128 if svg_type:
1131 return self.get_img_data_by_type(font_family, svg_type)
1129 return self.get_img_data_by_type(font_family, svg_type)
1132
1130
1133 initials = self.get_initials()
1131 initials = self.get_initials()
1134 img_data = """
1132 img_data = """
1135 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1133 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1136 width="{size}" height="{size}"
1134 width="{size}" height="{size}"
1137 style="width: 100%; height: 100%; background-color: {background}"
1135 style="width: 100%; height: 100%; background-color: {background}"
1138 viewBox="0 0 {size} {size}">
1136 viewBox="0 0 {size} {size}">
1139 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1137 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1140 pointer-events="auto" fill="{text_color}"
1138 pointer-events="auto" fill="{text_color}"
1141 font-family="{font_family}"
1139 font-family="{font_family}"
1142 style="font-weight: 400; font-size: {f_size}px;">{text}
1140 style="font-weight: 400; font-size: {f_size}px;">{text}
1143 </text>
1141 </text>
1144 </svg>""".format(
1142 </svg>""".format(
1145 size=self.size,
1143 size=self.size,
1146 f_size=self.size/1.85, # scale the text inside the box nicely
1144 f_size=self.size/1.85, # scale the text inside the box nicely
1147 background=self.background,
1145 background=self.background,
1148 text_color=self.text_color,
1146 text_color=self.text_color,
1149 text=initials.upper(),
1147 text=initials.upper(),
1150 font_family=font_family)
1148 font_family=font_family)
1151
1149
1152 return img_data
1150 return img_data
1153
1151
1154 def generate_svg(self, svg_type=None):
1152 def generate_svg(self, svg_type=None):
1155 img_data = self.get_img_data(svg_type)
1153 img_data = self.get_img_data(svg_type)
1156 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1154 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1157
1155
1158
1156
1159 def initials_gravatar(email_address, first_name, last_name, size=30):
1157 def initials_gravatar(email_address, first_name, last_name, size=30):
1160 svg_type = None
1158 svg_type = None
1161 if email_address == User.DEFAULT_USER_EMAIL:
1159 if email_address == User.DEFAULT_USER_EMAIL:
1162 svg_type = 'default_user'
1160 svg_type = 'default_user'
1163 klass = InitialsGravatar(email_address, first_name, last_name, size)
1161 klass = InitialsGravatar(email_address, first_name, last_name, size)
1164 return klass.generate_svg(svg_type=svg_type)
1162 return klass.generate_svg(svg_type=svg_type)
1165
1163
1166
1164
1167 def gravatar_url(email_address, size=30):
1165 def gravatar_url(email_address, size=30):
1168 # doh, we need to re-import those to mock it later
1166 # doh, we need to re-import those to mock it later
1169 from pylons import tmpl_context as c
1167 from pylons import tmpl_context as c
1170
1168
1171 _use_gravatar = c.visual.use_gravatar
1169 _use_gravatar = c.visual.use_gravatar
1172 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1170 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1173
1171
1174 email_address = email_address or User.DEFAULT_USER_EMAIL
1172 email_address = email_address or User.DEFAULT_USER_EMAIL
1175 if isinstance(email_address, unicode):
1173 if isinstance(email_address, unicode):
1176 # hashlib crashes on unicode items
1174 # hashlib crashes on unicode items
1177 email_address = safe_str(email_address)
1175 email_address = safe_str(email_address)
1178
1176
1179 # empty email or default user
1177 # empty email or default user
1180 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1178 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1181 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1179 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1182
1180
1183 if _use_gravatar:
1181 if _use_gravatar:
1184 # TODO: Disuse pyramid thread locals. Think about another solution to
1182 # TODO: Disuse pyramid thread locals. Think about another solution to
1185 # get the host and schema here.
1183 # get the host and schema here.
1186 request = get_current_request()
1184 request = get_current_request()
1187 tmpl = safe_str(_gravatar_url)
1185 tmpl = safe_str(_gravatar_url)
1188 tmpl = tmpl.replace('{email}', email_address)\
1186 tmpl = tmpl.replace('{email}', email_address)\
1189 .replace('{md5email}', md5_safe(email_address.lower())) \
1187 .replace('{md5email}', md5_safe(email_address.lower())) \
1190 .replace('{netloc}', request.host)\
1188 .replace('{netloc}', request.host)\
1191 .replace('{scheme}', request.scheme)\
1189 .replace('{scheme}', request.scheme)\
1192 .replace('{size}', safe_str(size))
1190 .replace('{size}', safe_str(size))
1193 return tmpl
1191 return tmpl
1194 else:
1192 else:
1195 return initials_gravatar(email_address, '', '', size=size)
1193 return initials_gravatar(email_address, '', '', size=size)
1196
1194
1197
1195
1198 class Page(_Page):
1196 class Page(_Page):
1199 """
1197 """
1200 Custom pager to match rendering style with paginator
1198 Custom pager to match rendering style with paginator
1201 """
1199 """
1202
1200
1203 def _get_pos(self, cur_page, max_page, items):
1201 def _get_pos(self, cur_page, max_page, items):
1204 edge = (items / 2) + 1
1202 edge = (items / 2) + 1
1205 if (cur_page <= edge):
1203 if (cur_page <= edge):
1206 radius = max(items / 2, items - cur_page)
1204 radius = max(items / 2, items - cur_page)
1207 elif (max_page - cur_page) < edge:
1205 elif (max_page - cur_page) < edge:
1208 radius = (items - 1) - (max_page - cur_page)
1206 radius = (items - 1) - (max_page - cur_page)
1209 else:
1207 else:
1210 radius = items / 2
1208 radius = items / 2
1211
1209
1212 left = max(1, (cur_page - (radius)))
1210 left = max(1, (cur_page - (radius)))
1213 right = min(max_page, cur_page + (radius))
1211 right = min(max_page, cur_page + (radius))
1214 return left, cur_page, right
1212 return left, cur_page, right
1215
1213
1216 def _range(self, regexp_match):
1214 def _range(self, regexp_match):
1217 """
1215 """
1218 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1216 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1219
1217
1220 Arguments:
1218 Arguments:
1221
1219
1222 regexp_match
1220 regexp_match
1223 A "re" (regular expressions) match object containing the
1221 A "re" (regular expressions) match object containing the
1224 radius of linked pages around the current page in
1222 radius of linked pages around the current page in
1225 regexp_match.group(1) as a string
1223 regexp_match.group(1) as a string
1226
1224
1227 This function is supposed to be called as a callable in
1225 This function is supposed to be called as a callable in
1228 re.sub.
1226 re.sub.
1229
1227
1230 """
1228 """
1231 radius = int(regexp_match.group(1))
1229 radius = int(regexp_match.group(1))
1232
1230
1233 # Compute the first and last page number within the radius
1231 # Compute the first and last page number within the radius
1234 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1232 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1235 # -> leftmost_page = 5
1233 # -> leftmost_page = 5
1236 # -> rightmost_page = 9
1234 # -> rightmost_page = 9
1237 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1235 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1238 self.last_page,
1236 self.last_page,
1239 (radius * 2) + 1)
1237 (radius * 2) + 1)
1240 nav_items = []
1238 nav_items = []
1241
1239
1242 # Create a link to the first page (unless we are on the first page
1240 # Create a link to the first page (unless we are on the first page
1243 # or there would be no need to insert '..' spacers)
1241 # or there would be no need to insert '..' spacers)
1244 if self.page != self.first_page and self.first_page < leftmost_page:
1242 if self.page != self.first_page and self.first_page < leftmost_page:
1245 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1243 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1246
1244
1247 # Insert dots if there are pages between the first page
1245 # Insert dots if there are pages between the first page
1248 # and the currently displayed page range
1246 # and the currently displayed page range
1249 if leftmost_page - self.first_page > 1:
1247 if leftmost_page - self.first_page > 1:
1250 # Wrap in a SPAN tag if nolink_attr is set
1248 # Wrap in a SPAN tag if nolink_attr is set
1251 text = '..'
1249 text = '..'
1252 if self.dotdot_attr:
1250 if self.dotdot_attr:
1253 text = HTML.span(c=text, **self.dotdot_attr)
1251 text = HTML.span(c=text, **self.dotdot_attr)
1254 nav_items.append(text)
1252 nav_items.append(text)
1255
1253
1256 for thispage in xrange(leftmost_page, rightmost_page + 1):
1254 for thispage in xrange(leftmost_page, rightmost_page + 1):
1257 # Hilight the current page number and do not use a link
1255 # Hilight the current page number and do not use a link
1258 if thispage == self.page:
1256 if thispage == self.page:
1259 text = '%s' % (thispage,)
1257 text = '%s' % (thispage,)
1260 # Wrap in a SPAN tag if nolink_attr is set
1258 # Wrap in a SPAN tag if nolink_attr is set
1261 if self.curpage_attr:
1259 if self.curpage_attr:
1262 text = HTML.span(c=text, **self.curpage_attr)
1260 text = HTML.span(c=text, **self.curpage_attr)
1263 nav_items.append(text)
1261 nav_items.append(text)
1264 # Otherwise create just a link to that page
1262 # Otherwise create just a link to that page
1265 else:
1263 else:
1266 text = '%s' % (thispage,)
1264 text = '%s' % (thispage,)
1267 nav_items.append(self._pagerlink(thispage, text))
1265 nav_items.append(self._pagerlink(thispage, text))
1268
1266
1269 # Insert dots if there are pages between the displayed
1267 # Insert dots if there are pages between the displayed
1270 # page numbers and the end of the page range
1268 # page numbers and the end of the page range
1271 if self.last_page - rightmost_page > 1:
1269 if self.last_page - rightmost_page > 1:
1272 text = '..'
1270 text = '..'
1273 # Wrap in a SPAN tag if nolink_attr is set
1271 # Wrap in a SPAN tag if nolink_attr is set
1274 if self.dotdot_attr:
1272 if self.dotdot_attr:
1275 text = HTML.span(c=text, **self.dotdot_attr)
1273 text = HTML.span(c=text, **self.dotdot_attr)
1276 nav_items.append(text)
1274 nav_items.append(text)
1277
1275
1278 # Create a link to the very last page (unless we are on the last
1276 # Create a link to the very last page (unless we are on the last
1279 # page or there would be no need to insert '..' spacers)
1277 # page or there would be no need to insert '..' spacers)
1280 if self.page != self.last_page and rightmost_page < self.last_page:
1278 if self.page != self.last_page and rightmost_page < self.last_page:
1281 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1279 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1282
1280
1283 ## prerender links
1281 ## prerender links
1284 #_page_link = url.current()
1282 #_page_link = url.current()
1285 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1283 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1286 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1284 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1287 return self.separator.join(nav_items)
1285 return self.separator.join(nav_items)
1288
1286
1289 def pager(self, format='~2~', page_param='page', partial_param='partial',
1287 def pager(self, format='~2~', page_param='page', partial_param='partial',
1290 show_if_single_page=False, separator=' ', onclick=None,
1288 show_if_single_page=False, separator=' ', onclick=None,
1291 symbol_first='<<', symbol_last='>>',
1289 symbol_first='<<', symbol_last='>>',
1292 symbol_previous='<', symbol_next='>',
1290 symbol_previous='<', symbol_next='>',
1293 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1291 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1294 curpage_attr={'class': 'pager_curpage'},
1292 curpage_attr={'class': 'pager_curpage'},
1295 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1293 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1296
1294
1297 self.curpage_attr = curpage_attr
1295 self.curpage_attr = curpage_attr
1298 self.separator = separator
1296 self.separator = separator
1299 self.pager_kwargs = kwargs
1297 self.pager_kwargs = kwargs
1300 self.page_param = page_param
1298 self.page_param = page_param
1301 self.partial_param = partial_param
1299 self.partial_param = partial_param
1302 self.onclick = onclick
1300 self.onclick = onclick
1303 self.link_attr = link_attr
1301 self.link_attr = link_attr
1304 self.dotdot_attr = dotdot_attr
1302 self.dotdot_attr = dotdot_attr
1305
1303
1306 # Don't show navigator if there is no more than one page
1304 # Don't show navigator if there is no more than one page
1307 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1305 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1308 return ''
1306 return ''
1309
1307
1310 from string import Template
1308 from string import Template
1311 # Replace ~...~ in token format by range of pages
1309 # Replace ~...~ in token format by range of pages
1312 result = re.sub(r'~(\d+)~', self._range, format)
1310 result = re.sub(r'~(\d+)~', self._range, format)
1313
1311
1314 # Interpolate '%' variables
1312 # Interpolate '%' variables
1315 result = Template(result).safe_substitute({
1313 result = Template(result).safe_substitute({
1316 'first_page': self.first_page,
1314 'first_page': self.first_page,
1317 'last_page': self.last_page,
1315 'last_page': self.last_page,
1318 'page': self.page,
1316 'page': self.page,
1319 'page_count': self.page_count,
1317 'page_count': self.page_count,
1320 'items_per_page': self.items_per_page,
1318 'items_per_page': self.items_per_page,
1321 'first_item': self.first_item,
1319 'first_item': self.first_item,
1322 'last_item': self.last_item,
1320 'last_item': self.last_item,
1323 'item_count': self.item_count,
1321 'item_count': self.item_count,
1324 'link_first': self.page > self.first_page and \
1322 'link_first': self.page > self.first_page and \
1325 self._pagerlink(self.first_page, symbol_first) or '',
1323 self._pagerlink(self.first_page, symbol_first) or '',
1326 'link_last': self.page < self.last_page and \
1324 'link_last': self.page < self.last_page and \
1327 self._pagerlink(self.last_page, symbol_last) or '',
1325 self._pagerlink(self.last_page, symbol_last) or '',
1328 'link_previous': self.previous_page and \
1326 'link_previous': self.previous_page and \
1329 self._pagerlink(self.previous_page, symbol_previous) \
1327 self._pagerlink(self.previous_page, symbol_previous) \
1330 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1328 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1331 'link_next': self.next_page and \
1329 'link_next': self.next_page and \
1332 self._pagerlink(self.next_page, symbol_next) \
1330 self._pagerlink(self.next_page, symbol_next) \
1333 or HTML.span(symbol_next, class_="pg-next disabled")
1331 or HTML.span(symbol_next, class_="pg-next disabled")
1334 })
1332 })
1335
1333
1336 return literal(result)
1334 return literal(result)
1337
1335
1338
1336
1339 #==============================================================================
1337 #==============================================================================
1340 # REPO PAGER, PAGER FOR REPOSITORY
1338 # REPO PAGER, PAGER FOR REPOSITORY
1341 #==============================================================================
1339 #==============================================================================
1342 class RepoPage(Page):
1340 class RepoPage(Page):
1343
1341
1344 def __init__(self, collection, page=1, items_per_page=20,
1342 def __init__(self, collection, page=1, items_per_page=20,
1345 item_count=None, url=None, **kwargs):
1343 item_count=None, url=None, **kwargs):
1346
1344
1347 """Create a "RepoPage" instance. special pager for paging
1345 """Create a "RepoPage" instance. special pager for paging
1348 repository
1346 repository
1349 """
1347 """
1350 self._url_generator = url
1348 self._url_generator = url
1351
1349
1352 # Safe the kwargs class-wide so they can be used in the pager() method
1350 # Safe the kwargs class-wide so they can be used in the pager() method
1353 self.kwargs = kwargs
1351 self.kwargs = kwargs
1354
1352
1355 # Save a reference to the collection
1353 # Save a reference to the collection
1356 self.original_collection = collection
1354 self.original_collection = collection
1357
1355
1358 self.collection = collection
1356 self.collection = collection
1359
1357
1360 # The self.page is the number of the current page.
1358 # The self.page is the number of the current page.
1361 # The first page has the number 1!
1359 # The first page has the number 1!
1362 try:
1360 try:
1363 self.page = int(page) # make it int() if we get it as a string
1361 self.page = int(page) # make it int() if we get it as a string
1364 except (ValueError, TypeError):
1362 except (ValueError, TypeError):
1365 self.page = 1
1363 self.page = 1
1366
1364
1367 self.items_per_page = items_per_page
1365 self.items_per_page = items_per_page
1368
1366
1369 # Unless the user tells us how many items the collections has
1367 # Unless the user tells us how many items the collections has
1370 # we calculate that ourselves.
1368 # we calculate that ourselves.
1371 if item_count is not None:
1369 if item_count is not None:
1372 self.item_count = item_count
1370 self.item_count = item_count
1373 else:
1371 else:
1374 self.item_count = len(self.collection)
1372 self.item_count = len(self.collection)
1375
1373
1376 # Compute the number of the first and last available page
1374 # Compute the number of the first and last available page
1377 if self.item_count > 0:
1375 if self.item_count > 0:
1378 self.first_page = 1
1376 self.first_page = 1
1379 self.page_count = int(math.ceil(float(self.item_count) /
1377 self.page_count = int(math.ceil(float(self.item_count) /
1380 self.items_per_page))
1378 self.items_per_page))
1381 self.last_page = self.first_page + self.page_count - 1
1379 self.last_page = self.first_page + self.page_count - 1
1382
1380
1383 # Make sure that the requested page number is the range of
1381 # Make sure that the requested page number is the range of
1384 # valid pages
1382 # valid pages
1385 if self.page > self.last_page:
1383 if self.page > self.last_page:
1386 self.page = self.last_page
1384 self.page = self.last_page
1387 elif self.page < self.first_page:
1385 elif self.page < self.first_page:
1388 self.page = self.first_page
1386 self.page = self.first_page
1389
1387
1390 # Note: the number of items on this page can be less than
1388 # Note: the number of items on this page can be less than
1391 # items_per_page if the last page is not full
1389 # items_per_page if the last page is not full
1392 self.first_item = max(0, (self.item_count) - (self.page *
1390 self.first_item = max(0, (self.item_count) - (self.page *
1393 items_per_page))
1391 items_per_page))
1394 self.last_item = ((self.item_count - 1) - items_per_page *
1392 self.last_item = ((self.item_count - 1) - items_per_page *
1395 (self.page - 1))
1393 (self.page - 1))
1396
1394
1397 self.items = list(self.collection[self.first_item:self.last_item + 1])
1395 self.items = list(self.collection[self.first_item:self.last_item + 1])
1398
1396
1399 # Links to previous and next page
1397 # Links to previous and next page
1400 if self.page > self.first_page:
1398 if self.page > self.first_page:
1401 self.previous_page = self.page - 1
1399 self.previous_page = self.page - 1
1402 else:
1400 else:
1403 self.previous_page = None
1401 self.previous_page = None
1404
1402
1405 if self.page < self.last_page:
1403 if self.page < self.last_page:
1406 self.next_page = self.page + 1
1404 self.next_page = self.page + 1
1407 else:
1405 else:
1408 self.next_page = None
1406 self.next_page = None
1409
1407
1410 # No items available
1408 # No items available
1411 else:
1409 else:
1412 self.first_page = None
1410 self.first_page = None
1413 self.page_count = 0
1411 self.page_count = 0
1414 self.last_page = None
1412 self.last_page = None
1415 self.first_item = None
1413 self.first_item = None
1416 self.last_item = None
1414 self.last_item = None
1417 self.previous_page = None
1415 self.previous_page = None
1418 self.next_page = None
1416 self.next_page = None
1419 self.items = []
1417 self.items = []
1420
1418
1421 # This is a subclass of the 'list' type. Initialise the list now.
1419 # This is a subclass of the 'list' type. Initialise the list now.
1422 list.__init__(self, reversed(self.items))
1420 list.__init__(self, reversed(self.items))
1423
1421
1424
1422
1425 def changed_tooltip(nodes):
1423 def changed_tooltip(nodes):
1426 """
1424 """
1427 Generates a html string for changed nodes in commit page.
1425 Generates a html string for changed nodes in commit page.
1428 It limits the output to 30 entries
1426 It limits the output to 30 entries
1429
1427
1430 :param nodes: LazyNodesGenerator
1428 :param nodes: LazyNodesGenerator
1431 """
1429 """
1432 if nodes:
1430 if nodes:
1433 pref = ': <br/> '
1431 pref = ': <br/> '
1434 suf = ''
1432 suf = ''
1435 if len(nodes) > 30:
1433 if len(nodes) > 30:
1436 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1434 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1437 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1435 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1438 for x in nodes[:30]]) + suf)
1436 for x in nodes[:30]]) + suf)
1439 else:
1437 else:
1440 return ': ' + _('No Files')
1438 return ': ' + _('No Files')
1441
1439
1442
1440
1443 def breadcrumb_repo_link(repo):
1441 def breadcrumb_repo_link(repo):
1444 """
1442 """
1445 Makes a breadcrumbs path link to repo
1443 Makes a breadcrumbs path link to repo
1446
1444
1447 ex::
1445 ex::
1448 group >> subgroup >> repo
1446 group >> subgroup >> repo
1449
1447
1450 :param repo: a Repository instance
1448 :param repo: a Repository instance
1451 """
1449 """
1452
1450
1453 path = [
1451 path = [
1454 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1452 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1455 for group in repo.groups_with_parents
1453 for group in repo.groups_with_parents
1456 ] + [
1454 ] + [
1457 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1455 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1458 ]
1456 ]
1459
1457
1460 return literal(' &raquo; '.join(path))
1458 return literal(' &raquo; '.join(path))
1461
1459
1462
1460
1463 def format_byte_size_binary(file_size):
1461 def format_byte_size_binary(file_size):
1464 """
1462 """
1465 Formats file/folder sizes to standard.
1463 Formats file/folder sizes to standard.
1466 """
1464 """
1467 formatted_size = format_byte_size(file_size, binary=True)
1465 formatted_size = format_byte_size(file_size, binary=True)
1468 return formatted_size
1466 return formatted_size
1469
1467
1470
1468
1471 def fancy_file_stats(stats):
1469 def fancy_file_stats(stats):
1472 """
1470 """
1473 Displays a fancy two colored bar for number of added/deleted
1471 Displays a fancy two colored bar for number of added/deleted
1474 lines of code on file
1472 lines of code on file
1475
1473
1476 :param stats: two element list of added/deleted lines of code
1474 :param stats: two element list of added/deleted lines of code
1477 """
1475 """
1478 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1476 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1479 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1477 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1480
1478
1481 def cgen(l_type, a_v, d_v):
1479 def cgen(l_type, a_v, d_v):
1482 mapping = {'tr': 'top-right-rounded-corner-mid',
1480 mapping = {'tr': 'top-right-rounded-corner-mid',
1483 'tl': 'top-left-rounded-corner-mid',
1481 'tl': 'top-left-rounded-corner-mid',
1484 'br': 'bottom-right-rounded-corner-mid',
1482 'br': 'bottom-right-rounded-corner-mid',
1485 'bl': 'bottom-left-rounded-corner-mid'}
1483 'bl': 'bottom-left-rounded-corner-mid'}
1486 map_getter = lambda x: mapping[x]
1484 map_getter = lambda x: mapping[x]
1487
1485
1488 if l_type == 'a' and d_v:
1486 if l_type == 'a' and d_v:
1489 #case when added and deleted are present
1487 #case when added and deleted are present
1490 return ' '.join(map(map_getter, ['tl', 'bl']))
1488 return ' '.join(map(map_getter, ['tl', 'bl']))
1491
1489
1492 if l_type == 'a' and not d_v:
1490 if l_type == 'a' and not d_v:
1493 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1491 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1494
1492
1495 if l_type == 'd' and a_v:
1493 if l_type == 'd' and a_v:
1496 return ' '.join(map(map_getter, ['tr', 'br']))
1494 return ' '.join(map(map_getter, ['tr', 'br']))
1497
1495
1498 if l_type == 'd' and not a_v:
1496 if l_type == 'd' and not a_v:
1499 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1497 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1500
1498
1501 a, d = stats['added'], stats['deleted']
1499 a, d = stats['added'], stats['deleted']
1502 width = 100
1500 width = 100
1503
1501
1504 if stats['binary']: # binary operations like chmod/rename etc
1502 if stats['binary']: # binary operations like chmod/rename etc
1505 lbl = []
1503 lbl = []
1506 bin_op = 0 # undefined
1504 bin_op = 0 # undefined
1507
1505
1508 # prefix with bin for binary files
1506 # prefix with bin for binary files
1509 if BIN_FILENODE in stats['ops']:
1507 if BIN_FILENODE in stats['ops']:
1510 lbl += ['bin']
1508 lbl += ['bin']
1511
1509
1512 if NEW_FILENODE in stats['ops']:
1510 if NEW_FILENODE in stats['ops']:
1513 lbl += [_('new file')]
1511 lbl += [_('new file')]
1514 bin_op = NEW_FILENODE
1512 bin_op = NEW_FILENODE
1515 elif MOD_FILENODE in stats['ops']:
1513 elif MOD_FILENODE in stats['ops']:
1516 lbl += [_('mod')]
1514 lbl += [_('mod')]
1517 bin_op = MOD_FILENODE
1515 bin_op = MOD_FILENODE
1518 elif DEL_FILENODE in stats['ops']:
1516 elif DEL_FILENODE in stats['ops']:
1519 lbl += [_('del')]
1517 lbl += [_('del')]
1520 bin_op = DEL_FILENODE
1518 bin_op = DEL_FILENODE
1521 elif RENAMED_FILENODE in stats['ops']:
1519 elif RENAMED_FILENODE in stats['ops']:
1522 lbl += [_('rename')]
1520 lbl += [_('rename')]
1523 bin_op = RENAMED_FILENODE
1521 bin_op = RENAMED_FILENODE
1524
1522
1525 # chmod can go with other operations, so we add a + to lbl if needed
1523 # chmod can go with other operations, so we add a + to lbl if needed
1526 if CHMOD_FILENODE in stats['ops']:
1524 if CHMOD_FILENODE in stats['ops']:
1527 lbl += [_('chmod')]
1525 lbl += [_('chmod')]
1528 if bin_op == 0:
1526 if bin_op == 0:
1529 bin_op = CHMOD_FILENODE
1527 bin_op = CHMOD_FILENODE
1530
1528
1531 lbl = '+'.join(lbl)
1529 lbl = '+'.join(lbl)
1532 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1530 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1533 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1531 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1534 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1532 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1535 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1533 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1536
1534
1537 t = stats['added'] + stats['deleted']
1535 t = stats['added'] + stats['deleted']
1538 unit = float(width) / (t or 1)
1536 unit = float(width) / (t or 1)
1539
1537
1540 # needs > 9% of width to be visible or 0 to be hidden
1538 # needs > 9% of width to be visible or 0 to be hidden
1541 a_p = max(9, unit * a) if a > 0 else 0
1539 a_p = max(9, unit * a) if a > 0 else 0
1542 d_p = max(9, unit * d) if d > 0 else 0
1540 d_p = max(9, unit * d) if d > 0 else 0
1543 p_sum = a_p + d_p
1541 p_sum = a_p + d_p
1544
1542
1545 if p_sum > width:
1543 if p_sum > width:
1546 #adjust the percentage to be == 100% since we adjusted to 9
1544 #adjust the percentage to be == 100% since we adjusted to 9
1547 if a_p > d_p:
1545 if a_p > d_p:
1548 a_p = a_p - (p_sum - width)
1546 a_p = a_p - (p_sum - width)
1549 else:
1547 else:
1550 d_p = d_p - (p_sum - width)
1548 d_p = d_p - (p_sum - width)
1551
1549
1552 a_v = a if a > 0 else ''
1550 a_v = a if a > 0 else ''
1553 d_v = d if d > 0 else ''
1551 d_v = d if d > 0 else ''
1554
1552
1555 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1553 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1556 cgen('a', a_v, d_v), a_p, a_v
1554 cgen('a', a_v, d_v), a_p, a_v
1557 )
1555 )
1558 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1556 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1559 cgen('d', a_v, d_v), d_p, d_v
1557 cgen('d', a_v, d_v), d_p, d_v
1560 )
1558 )
1561 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1559 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1562
1560
1563
1561
1564 def urlify_text(text_, safe=True):
1562 def urlify_text(text_, safe=True):
1565 """
1563 """
1566 Extrac urls from text and make html links out of them
1564 Extrac urls from text and make html links out of them
1567
1565
1568 :param text_:
1566 :param text_:
1569 """
1567 """
1570
1568
1571 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1569 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1572 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1570 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1573
1571
1574 def url_func(match_obj):
1572 def url_func(match_obj):
1575 url_full = match_obj.groups()[0]
1573 url_full = match_obj.groups()[0]
1576 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1574 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1577 _newtext = url_pat.sub(url_func, text_)
1575 _newtext = url_pat.sub(url_func, text_)
1578 if safe:
1576 if safe:
1579 return literal(_newtext)
1577 return literal(_newtext)
1580 return _newtext
1578 return _newtext
1581
1579
1582
1580
1583 def urlify_commits(text_, repository):
1581 def urlify_commits(text_, repository):
1584 """
1582 """
1585 Extract commit ids from text and make link from them
1583 Extract commit ids from text and make link from them
1586
1584
1587 :param text_:
1585 :param text_:
1588 :param repository: repo name to build the URL with
1586 :param repository: repo name to build the URL with
1589 """
1587 """
1590 from pylons import url # doh, we need to re-import url to mock it later
1588 from pylons import url # doh, we need to re-import url to mock it later
1591 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1589 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1592
1590
1593 def url_func(match_obj):
1591 def url_func(match_obj):
1594 commit_id = match_obj.groups()[1]
1592 commit_id = match_obj.groups()[1]
1595 pref = match_obj.groups()[0]
1593 pref = match_obj.groups()[0]
1596 suf = match_obj.groups()[2]
1594 suf = match_obj.groups()[2]
1597
1595
1598 tmpl = (
1596 tmpl = (
1599 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1597 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1600 '%(commit_id)s</a>%(suf)s'
1598 '%(commit_id)s</a>%(suf)s'
1601 )
1599 )
1602 return tmpl % {
1600 return tmpl % {
1603 'pref': pref,
1601 'pref': pref,
1604 'cls': 'revision-link',
1602 'cls': 'revision-link',
1605 'url': url('changeset_home', repo_name=repository,
1603 'url': url('changeset_home', repo_name=repository,
1606 revision=commit_id),
1604 revision=commit_id),
1607 'commit_id': commit_id,
1605 'commit_id': commit_id,
1608 'suf': suf
1606 'suf': suf
1609 }
1607 }
1610
1608
1611 newtext = URL_PAT.sub(url_func, text_)
1609 newtext = URL_PAT.sub(url_func, text_)
1612
1610
1613 return newtext
1611 return newtext
1614
1612
1615
1613
1616 def _process_url_func(match_obj, repo_name, uid, entry):
1614 def _process_url_func(match_obj, repo_name, uid, entry):
1617 pref = ''
1615 pref = ''
1618 if match_obj.group().startswith(' '):
1616 if match_obj.group().startswith(' '):
1619 pref = ' '
1617 pref = ' '
1620
1618
1621 issue_id = ''.join(match_obj.groups())
1619 issue_id = ''.join(match_obj.groups())
1622 tmpl = (
1620 tmpl = (
1623 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1621 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1624 '%(issue-prefix)s%(id-repr)s'
1622 '%(issue-prefix)s%(id-repr)s'
1625 '</a>')
1623 '</a>')
1626
1624
1627 (repo_name_cleaned,
1625 (repo_name_cleaned,
1628 parent_group_name) = RepoGroupModel().\
1626 parent_group_name) = RepoGroupModel().\
1629 _get_group_name_and_parent(repo_name)
1627 _get_group_name_and_parent(repo_name)
1630
1628
1631 # variables replacement
1629 # variables replacement
1632 named_vars = {
1630 named_vars = {
1633 'id': issue_id,
1631 'id': issue_id,
1634 'repo': repo_name,
1632 'repo': repo_name,
1635 'repo_name': repo_name_cleaned,
1633 'repo_name': repo_name_cleaned,
1636 'group_name': parent_group_name
1634 'group_name': parent_group_name
1637 }
1635 }
1638 # named regex variables
1636 # named regex variables
1639 named_vars.update(match_obj.groupdict())
1637 named_vars.update(match_obj.groupdict())
1640 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1638 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1641
1639
1642 return tmpl % {
1640 return tmpl % {
1643 'pref': pref,
1641 'pref': pref,
1644 'cls': 'issue-tracker-link',
1642 'cls': 'issue-tracker-link',
1645 'url': _url,
1643 'url': _url,
1646 'id-repr': issue_id,
1644 'id-repr': issue_id,
1647 'issue-prefix': entry['pref'],
1645 'issue-prefix': entry['pref'],
1648 'serv': entry['url'],
1646 'serv': entry['url'],
1649 }
1647 }
1650
1648
1651
1649
1652 def process_patterns(text_string, repo_name, config):
1650 def process_patterns(text_string, repo_name, config):
1653 repo = None
1651 repo = None
1654 if repo_name:
1652 if repo_name:
1655 # Retrieving repo_name to avoid invalid repo_name to explode on
1653 # Retrieving repo_name to avoid invalid repo_name to explode on
1656 # IssueTrackerSettingsModel but still passing invalid name further down
1654 # IssueTrackerSettingsModel but still passing invalid name further down
1657 repo = Repository.get_by_repo_name(repo_name)
1655 repo = Repository.get_by_repo_name(repo_name)
1658
1656
1659 settings_model = IssueTrackerSettingsModel(repo=repo)
1657 settings_model = IssueTrackerSettingsModel(repo=repo)
1660 active_entries = settings_model.get_settings()
1658 active_entries = settings_model.get_settings()
1661
1659
1662 newtext = text_string
1660 newtext = text_string
1663 for uid, entry in active_entries.items():
1661 for uid, entry in active_entries.items():
1664 url_func = partial(
1662 url_func = partial(
1665 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1663 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1666
1664
1667 log.debug('found issue tracker entry with uid %s' % (uid,))
1665 log.debug('found issue tracker entry with uid %s' % (uid,))
1668
1666
1669 if not (entry['pat'] and entry['url']):
1667 if not (entry['pat'] and entry['url']):
1670 log.debug('skipping due to missing data')
1668 log.debug('skipping due to missing data')
1671 continue
1669 continue
1672
1670
1673 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1671 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1674 % (uid, entry['pat'], entry['url'], entry['pref']))
1672 % (uid, entry['pat'], entry['url'], entry['pref']))
1675
1673
1676 try:
1674 try:
1677 pattern = re.compile(r'%s' % entry['pat'])
1675 pattern = re.compile(r'%s' % entry['pat'])
1678 except re.error:
1676 except re.error:
1679 log.exception(
1677 log.exception(
1680 'issue tracker pattern: `%s` failed to compile',
1678 'issue tracker pattern: `%s` failed to compile',
1681 entry['pat'])
1679 entry['pat'])
1682 continue
1680 continue
1683
1681
1684 newtext = pattern.sub(url_func, newtext)
1682 newtext = pattern.sub(url_func, newtext)
1685 log.debug('processed prefix:uid `%s`' % (uid,))
1683 log.debug('processed prefix:uid `%s`' % (uid,))
1686
1684
1687 return newtext
1685 return newtext
1688
1686
1689
1687
1690 def urlify_commit_message(commit_text, repository=None):
1688 def urlify_commit_message(commit_text, repository=None):
1691 """
1689 """
1692 Parses given text message and makes proper links.
1690 Parses given text message and makes proper links.
1693 issues are linked to given issue-server, and rest is a commit link
1691 issues are linked to given issue-server, and rest is a commit link
1694
1692
1695 :param commit_text:
1693 :param commit_text:
1696 :param repository:
1694 :param repository:
1697 """
1695 """
1698 from pylons import url # doh, we need to re-import url to mock it later
1696 from pylons import url # doh, we need to re-import url to mock it later
1699 from rhodecode import CONFIG
1697 from rhodecode import CONFIG
1700
1698
1701 def escaper(string):
1699 def escaper(string):
1702 return string.replace('<', '&lt;').replace('>', '&gt;')
1700 return string.replace('<', '&lt;').replace('>', '&gt;')
1703
1701
1704 newtext = escaper(commit_text)
1702 newtext = escaper(commit_text)
1705 # urlify commits - extract commit ids and make link out of them, if we have
1703 # urlify commits - extract commit ids and make link out of them, if we have
1706 # the scope of repository present.
1704 # the scope of repository present.
1707 if repository:
1705 if repository:
1708 newtext = urlify_commits(newtext, repository)
1706 newtext = urlify_commits(newtext, repository)
1709
1707
1710 # extract http/https links and make them real urls
1708 # extract http/https links and make them real urls
1711 newtext = urlify_text(newtext, safe=False)
1709 newtext = urlify_text(newtext, safe=False)
1712
1710
1713 # process issue tracker patterns
1711 # process issue tracker patterns
1714 newtext = process_patterns(newtext, repository or '', CONFIG)
1712 newtext = process_patterns(newtext, repository or '', CONFIG)
1715
1713
1716 return literal(newtext)
1714 return literal(newtext)
1717
1715
1718
1716
1719 def rst(source, mentions=False):
1717 def rst(source, mentions=False):
1720 return literal('<div class="rst-block">%s</div>' %
1718 return literal('<div class="rst-block">%s</div>' %
1721 MarkupRenderer.rst(source, mentions=mentions))
1719 MarkupRenderer.rst(source, mentions=mentions))
1722
1720
1723
1721
1724 def markdown(source, mentions=False):
1722 def markdown(source, mentions=False):
1725 return literal('<div class="markdown-block">%s</div>' %
1723 return literal('<div class="markdown-block">%s</div>' %
1726 MarkupRenderer.markdown(source, flavored=False,
1724 MarkupRenderer.markdown(source, flavored=False,
1727 mentions=mentions))
1725 mentions=mentions))
1728
1726
1729 def renderer_from_filename(filename, exclude=None):
1727 def renderer_from_filename(filename, exclude=None):
1730 from rhodecode.config.conf import MARKDOWN_EXTS, RST_EXTS
1728 from rhodecode.config.conf import MARKDOWN_EXTS, RST_EXTS
1731
1729
1732 def _filter(elements):
1730 def _filter(elements):
1733 if isinstance(exclude, (list, tuple)):
1731 if isinstance(exclude, (list, tuple)):
1734 return [x for x in elements if x not in exclude]
1732 return [x for x in elements if x not in exclude]
1735 return elements
1733 return elements
1736
1734
1737 if filename.endswith(tuple(_filter([x[0] for x in MARKDOWN_EXTS if x[0]]))):
1735 if filename.endswith(tuple(_filter([x[0] for x in MARKDOWN_EXTS if x[0]]))):
1738 return 'markdown'
1736 return 'markdown'
1739 if filename.endswith(tuple(_filter([x[0] for x in RST_EXTS if x[0]]))):
1737 if filename.endswith(tuple(_filter([x[0] for x in RST_EXTS if x[0]]))):
1740 return 'rst'
1738 return 'rst'
1741
1739
1742
1740
1743 def render(source, renderer='rst', mentions=False):
1741 def render(source, renderer='rst', mentions=False):
1744 if renderer == 'rst':
1742 if renderer == 'rst':
1745 return rst(source, mentions=mentions)
1743 return rst(source, mentions=mentions)
1746 if renderer == 'markdown':
1744 if renderer == 'markdown':
1747 return markdown(source, mentions=mentions)
1745 return markdown(source, mentions=mentions)
1748
1746
1749
1747
1750 def commit_status(repo, commit_id):
1748 def commit_status(repo, commit_id):
1751 return ChangesetStatusModel().get_status(repo, commit_id)
1749 return ChangesetStatusModel().get_status(repo, commit_id)
1752
1750
1753
1751
1754 def commit_status_lbl(commit_status):
1752 def commit_status_lbl(commit_status):
1755 return dict(ChangesetStatus.STATUSES).get(commit_status)
1753 return dict(ChangesetStatus.STATUSES).get(commit_status)
1756
1754
1757
1755
1758 def commit_time(repo_name, commit_id):
1756 def commit_time(repo_name, commit_id):
1759 repo = Repository.get_by_repo_name(repo_name)
1757 repo = Repository.get_by_repo_name(repo_name)
1760 commit = repo.get_commit(commit_id=commit_id)
1758 commit = repo.get_commit(commit_id=commit_id)
1761 return commit.date
1759 return commit.date
1762
1760
1763
1761
1764 def get_permission_name(key):
1762 def get_permission_name(key):
1765 return dict(Permission.PERMS).get(key)
1763 return dict(Permission.PERMS).get(key)
1766
1764
1767
1765
1768 def journal_filter_help():
1766 def journal_filter_help():
1769 return _(
1767 return _(
1770 'Example filter terms:\n' +
1768 'Example filter terms:\n' +
1771 ' repository:vcs\n' +
1769 ' repository:vcs\n' +
1772 ' username:marcin\n' +
1770 ' username:marcin\n' +
1773 ' action:*push*\n' +
1771 ' action:*push*\n' +
1774 ' ip:127.0.0.1\n' +
1772 ' ip:127.0.0.1\n' +
1775 ' date:20120101\n' +
1773 ' date:20120101\n' +
1776 ' date:[20120101100000 TO 20120102]\n' +
1774 ' date:[20120101100000 TO 20120102]\n' +
1777 '\n' +
1775 '\n' +
1778 'Generate wildcards using \'*\' character:\n' +
1776 'Generate wildcards using \'*\' character:\n' +
1779 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1777 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1780 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1778 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1781 '\n' +
1779 '\n' +
1782 'Optional AND / OR operators in queries\n' +
1780 'Optional AND / OR operators in queries\n' +
1783 ' "repository:vcs OR repository:test"\n' +
1781 ' "repository:vcs OR repository:test"\n' +
1784 ' "username:test AND repository:test*"\n'
1782 ' "username:test AND repository:test*"\n'
1785 )
1783 )
1786
1784
1787
1785
1788 def not_mapped_error(repo_name):
1786 def not_mapped_error(repo_name):
1789 flash(_('%s repository is not mapped to db perhaps'
1787 flash(_('%s repository is not mapped to db perhaps'
1790 ' it was created or renamed from the filesystem'
1788 ' it was created or renamed from the filesystem'
1791 ' please run the application again'
1789 ' please run the application again'
1792 ' in order to rescan repositories') % repo_name, category='error')
1790 ' in order to rescan repositories') % repo_name, category='error')
1793
1791
1794
1792
1795 def ip_range(ip_addr):
1793 def ip_range(ip_addr):
1796 from rhodecode.model.db import UserIpMap
1794 from rhodecode.model.db import UserIpMap
1797 s, e = UserIpMap._get_ip_range(ip_addr)
1795 s, e = UserIpMap._get_ip_range(ip_addr)
1798 return '%s - %s' % (s, e)
1796 return '%s - %s' % (s, e)
1799
1797
1800
1798
1801 def form(url, method='post', needs_csrf_token=True, **attrs):
1799 def form(url, method='post', needs_csrf_token=True, **attrs):
1802 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1800 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1803 if method.lower() != 'get' and needs_csrf_token:
1801 if method.lower() != 'get' and needs_csrf_token:
1804 raise Exception(
1802 raise Exception(
1805 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1803 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1806 'CSRF token. If the endpoint does not require such token you can ' +
1804 'CSRF token. If the endpoint does not require such token you can ' +
1807 'explicitly set the parameter needs_csrf_token to false.')
1805 'explicitly set the parameter needs_csrf_token to false.')
1808
1806
1809 return wh_form(url, method=method, **attrs)
1807 return wh_form(url, method=method, **attrs)
1810
1808
1811
1809
1812 def secure_form(url, method="POST", multipart=False, **attrs):
1810 def secure_form(url, method="POST", multipart=False, **attrs):
1813 """Start a form tag that points the action to an url. This
1811 """Start a form tag that points the action to an url. This
1814 form tag will also include the hidden field containing
1812 form tag will also include the hidden field containing
1815 the auth token.
1813 the auth token.
1816
1814
1817 The url options should be given either as a string, or as a
1815 The url options should be given either as a string, or as a
1818 ``url()`` function. The method for the form defaults to POST.
1816 ``url()`` function. The method for the form defaults to POST.
1819
1817
1820 Options:
1818 Options:
1821
1819
1822 ``multipart``
1820 ``multipart``
1823 If set to True, the enctype is set to "multipart/form-data".
1821 If set to True, the enctype is set to "multipart/form-data".
1824 ``method``
1822 ``method``
1825 The method to use when submitting the form, usually either
1823 The method to use when submitting the form, usually either
1826 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1824 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1827 hidden input with name _method is added to simulate the verb
1825 hidden input with name _method is added to simulate the verb
1828 over POST.
1826 over POST.
1829
1827
1830 """
1828 """
1831 from webhelpers.pylonslib.secure_form import insecure_form
1829 from webhelpers.pylonslib.secure_form import insecure_form
1832 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1830 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1833 form = insecure_form(url, method, multipart, **attrs)
1831 form = insecure_form(url, method, multipart, **attrs)
1834 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1832 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1835 return literal("%s\n%s" % (form, token))
1833 return literal("%s\n%s" % (form, token))
1836
1834
1837 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1835 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1838 select_html = select(name, selected, options, **attrs)
1836 select_html = select(name, selected, options, **attrs)
1839 select2 = """
1837 select2 = """
1840 <script>
1838 <script>
1841 $(document).ready(function() {
1839 $(document).ready(function() {
1842 $('#%s').select2({
1840 $('#%s').select2({
1843 containerCssClass: 'drop-menu',
1841 containerCssClass: 'drop-menu',
1844 dropdownCssClass: 'drop-menu-dropdown',
1842 dropdownCssClass: 'drop-menu-dropdown',
1845 dropdownAutoWidth: true%s
1843 dropdownAutoWidth: true%s
1846 });
1844 });
1847 });
1845 });
1848 </script>
1846 </script>
1849 """
1847 """
1850 filter_option = """,
1848 filter_option = """,
1851 minimumResultsForSearch: -1
1849 minimumResultsForSearch: -1
1852 """
1850 """
1853 input_id = attrs.get('id') or name
1851 input_id = attrs.get('id') or name
1854 filter_enabled = "" if enable_filter else filter_option
1852 filter_enabled = "" if enable_filter else filter_option
1855 select_script = literal(select2 % (input_id, filter_enabled))
1853 select_script = literal(select2 % (input_id, filter_enabled))
1856
1854
1857 return literal(select_html+select_script)
1855 return literal(select_html+select_script)
1858
1856
1859
1857
1860 def get_visual_attr(tmpl_context_var, attr_name):
1858 def get_visual_attr(tmpl_context_var, attr_name):
1861 """
1859 """
1862 A safe way to get a variable from visual variable of template context
1860 A safe way to get a variable from visual variable of template context
1863
1861
1864 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1862 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1865 :param attr_name: name of the attribute we fetch from the c.visual
1863 :param attr_name: name of the attribute we fetch from the c.visual
1866 """
1864 """
1867 visual = getattr(tmpl_context_var, 'visual', None)
1865 visual = getattr(tmpl_context_var, 'visual', None)
1868 if not visual:
1866 if not visual:
1869 return
1867 return
1870 else:
1868 else:
1871 return getattr(visual, attr_name, None)
1869 return getattr(visual, attr_name, None)
1872
1870
1873
1871
1874 def get_last_path_part(file_node):
1872 def get_last_path_part(file_node):
1875 if not file_node.path:
1873 if not file_node.path:
1876 return u''
1874 return u''
1877
1875
1878 path = safe_unicode(file_node.path.split('/')[-1])
1876 path = safe_unicode(file_node.path.split('/')[-1])
1879 return u'../' + path
1877 return u'../' + path
1880
1878
1881
1879
1882 def route_path(*args, **kwds):
1880 def route_path(*args, **kwds):
1883 """
1881 """
1884 Wrapper around pyramids `route_path` function. It is used to generate
1882 Wrapper around pyramids `route_path` function. It is used to generate
1885 URLs from within pylons views or templates. This will be removed when
1883 URLs from within pylons views or templates. This will be removed when
1886 pyramid migration if finished.
1884 pyramid migration if finished.
1887 """
1885 """
1888 req = get_current_request()
1886 req = get_current_request()
1889 return req.route_path(*args, **kwds)
1887 return req.route_path(*args, **kwds)
1890
1888
1891
1889
1892 def resource_path(*args, **kwds):
1890 def resource_path(*args, **kwds):
1893 """
1891 """
1894 Wrapper around pyramids `route_path` function. It is used to generate
1892 Wrapper around pyramids `route_path` function. It is used to generate
1895 URLs from within pylons views or templates. This will be removed when
1893 URLs from within pylons views or templates. This will be removed when
1896 pyramid migration if finished.
1894 pyramid migration if finished.
1897 """
1895 """
1898 req = get_current_request()
1896 req = get_current_request()
1899 return req.resource_path(*args, **kwds)
1897 return req.resource_path(*args, **kwds)
General Comments 0
You need to be logged in to leave comments. Login now