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