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