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