##// END OF EJS Templates
files: fixed unicode problems in specially encoded paths handler.
super-admin -
r4751:88359629 default
parent child
Show More
@@ -1,2149 +1,2149
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import base64
27 import base64
28 import collections
28 import collections
29
29
30 import os
30 import os
31 import random
31 import random
32 import hashlib
32 import hashlib
33 import StringIO
33 import StringIO
34 import textwrap
34 import textwrap
35 import urllib
35 import urllib
36 import math
36 import math
37 import logging
37 import logging
38 import re
38 import re
39 import time
39 import time
40 import string
40 import string
41 import hashlib
41 import hashlib
42 import regex
42 import regex
43 from collections import OrderedDict
43 from collections import OrderedDict
44
44
45 import pygments
45 import pygments
46 import itertools
46 import itertools
47 import fnmatch
47 import fnmatch
48 import bleach
48 import bleach
49
49
50 from pyramid import compat
50 from pyramid import compat
51 from datetime import datetime
51 from datetime import datetime
52 from functools import partial
52 from functools import partial
53 from pygments.formatters.html import HtmlFormatter
53 from pygments.formatters.html import HtmlFormatter
54 from pygments.lexers import (
54 from pygments.lexers import (
55 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
55 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
56
56
57 from pyramid.threadlocal import get_current_request
57 from pyramid.threadlocal import get_current_request
58 from tempita import looper
58 from tempita import looper
59 from webhelpers2.html import literal, HTML, escape
59 from webhelpers2.html import literal, HTML, escape
60 from webhelpers2.html._autolink import _auto_link_urls
60 from webhelpers2.html._autolink import _auto_link_urls
61 from webhelpers2.html.tools import (
61 from webhelpers2.html.tools import (
62 button_to, highlight, js_obfuscate, strip_links, strip_tags)
62 button_to, highlight, js_obfuscate, strip_links, strip_tags)
63
63
64 from webhelpers2.text import (
64 from webhelpers2.text import (
65 chop_at, collapse, convert_accented_entities,
65 chop_at, collapse, convert_accented_entities,
66 convert_misc_entities, lchop, plural, rchop, remove_formatting,
66 convert_misc_entities, lchop, plural, rchop, remove_formatting,
67 replace_whitespace, urlify, truncate, wrap_paragraphs)
67 replace_whitespace, urlify, truncate, wrap_paragraphs)
68 from webhelpers2.date import time_ago_in_words
68 from webhelpers2.date import time_ago_in_words
69
69
70 from webhelpers2.html.tags import (
70 from webhelpers2.html.tags import (
71 _input, NotGiven, _make_safe_id_component as safeid,
71 _input, NotGiven, _make_safe_id_component as safeid,
72 form as insecure_form,
72 form as insecure_form,
73 auto_discovery_link, checkbox, end_form, file,
73 auto_discovery_link, checkbox, end_form, file,
74 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
74 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
75 select as raw_select, stylesheet_link, submit, text, password, textarea,
75 select as raw_select, stylesheet_link, submit, text, password, textarea,
76 ul, radio, Options)
76 ul, radio, Options)
77
77
78 from webhelpers2.number import format_byte_size
78 from webhelpers2.number import format_byte_size
79
79
80 from rhodecode.lib.action_parser import action_parser
80 from rhodecode.lib.action_parser import action_parser
81 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
81 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
82 from rhodecode.lib.ext_json import json
82 from rhodecode.lib.ext_json import json
83 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
83 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
84 from rhodecode.lib.utils2 import (
84 from rhodecode.lib.utils2 import (
85 str2bool, safe_unicode, safe_str,
85 str2bool, safe_unicode, safe_str,
86 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
86 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
87 AttributeDict, safe_int, md5, md5_safe, get_host_info)
87 AttributeDict, safe_int, md5, md5_safe, get_host_info)
88 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
88 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
89 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
89 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
90 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
90 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
91 from rhodecode.lib.vcs.conf.settings import ARCHIVE_SPECS
91 from rhodecode.lib.vcs.conf.settings import ARCHIVE_SPECS
92 from rhodecode.lib.index.search_utils import get_matching_line_offsets
92 from rhodecode.lib.index.search_utils import get_matching_line_offsets
93 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
93 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
94 from rhodecode.model.changeset_status import ChangesetStatusModel
94 from rhodecode.model.changeset_status import ChangesetStatusModel
95 from rhodecode.model.db import Permission, User, Repository, UserApiKeys, FileStore
95 from rhodecode.model.db import Permission, User, Repository, UserApiKeys, FileStore
96 from rhodecode.model.repo_group import RepoGroupModel
96 from rhodecode.model.repo_group import RepoGroupModel
97 from rhodecode.model.settings import IssueTrackerSettingsModel
97 from rhodecode.model.settings import IssueTrackerSettingsModel
98
98
99
99
100 log = logging.getLogger(__name__)
100 log = logging.getLogger(__name__)
101
101
102
102
103 DEFAULT_USER = User.DEFAULT_USER
103 DEFAULT_USER = User.DEFAULT_USER
104 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
104 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
105
105
106
106
107 def asset(path, ver=None, **kwargs):
107 def asset(path, ver=None, **kwargs):
108 """
108 """
109 Helper to generate a static asset file path for rhodecode assets
109 Helper to generate a static asset file path for rhodecode assets
110
110
111 eg. h.asset('images/image.png', ver='3923')
111 eg. h.asset('images/image.png', ver='3923')
112
112
113 :param path: path of asset
113 :param path: path of asset
114 :param ver: optional version query param to append as ?ver=
114 :param ver: optional version query param to append as ?ver=
115 """
115 """
116 request = get_current_request()
116 request = get_current_request()
117 query = {}
117 query = {}
118 query.update(kwargs)
118 query.update(kwargs)
119 if ver:
119 if ver:
120 query = {'ver': ver}
120 query = {'ver': ver}
121 return request.static_path(
121 return request.static_path(
122 'rhodecode:public/{}'.format(path), _query=query)
122 'rhodecode:public/{}'.format(path), _query=query)
123
123
124
124
125 default_html_escape_table = {
125 default_html_escape_table = {
126 ord('&'): u'&amp;',
126 ord('&'): u'&amp;',
127 ord('<'): u'&lt;',
127 ord('<'): u'&lt;',
128 ord('>'): u'&gt;',
128 ord('>'): u'&gt;',
129 ord('"'): u'&quot;',
129 ord('"'): u'&quot;',
130 ord("'"): u'&#39;',
130 ord("'"): u'&#39;',
131 }
131 }
132
132
133
133
134 def html_escape(text, html_escape_table=default_html_escape_table):
134 def html_escape(text, html_escape_table=default_html_escape_table):
135 """Produce entities within text."""
135 """Produce entities within text."""
136 return text.translate(html_escape_table)
136 return text.translate(html_escape_table)
137
137
138
138
139 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
139 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
140 """
140 """
141 Truncate string ``s`` at the first occurrence of ``sub``.
141 Truncate string ``s`` at the first occurrence of ``sub``.
142
142
143 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
143 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
144 """
144 """
145 suffix_if_chopped = suffix_if_chopped or ''
145 suffix_if_chopped = suffix_if_chopped or ''
146 pos = s.find(sub)
146 pos = s.find(sub)
147 if pos == -1:
147 if pos == -1:
148 return s
148 return s
149
149
150 if inclusive:
150 if inclusive:
151 pos += len(sub)
151 pos += len(sub)
152
152
153 chopped = s[:pos]
153 chopped = s[:pos]
154 left = s[pos:].strip()
154 left = s[pos:].strip()
155
155
156 if left and suffix_if_chopped:
156 if left and suffix_if_chopped:
157 chopped += suffix_if_chopped
157 chopped += suffix_if_chopped
158
158
159 return chopped
159 return chopped
160
160
161
161
162 def shorter(text, size=20, prefix=False):
162 def shorter(text, size=20, prefix=False):
163 postfix = '...'
163 postfix = '...'
164 if len(text) > size:
164 if len(text) > size:
165 if prefix:
165 if prefix:
166 # shorten in front
166 # shorten in front
167 return postfix + text[-(size - len(postfix)):]
167 return postfix + text[-(size - len(postfix)):]
168 else:
168 else:
169 return text[:size - len(postfix)] + postfix
169 return text[:size - len(postfix)] + postfix
170 return text
170 return text
171
171
172
172
173 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
173 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
174 """
174 """
175 Reset button
175 Reset button
176 """
176 """
177 return _input(type, name, value, id, attrs)
177 return _input(type, name, value, id, attrs)
178
178
179
179
180 def select(name, selected_values, options, id=NotGiven, **attrs):
180 def select(name, selected_values, options, id=NotGiven, **attrs):
181
181
182 if isinstance(options, (list, tuple)):
182 if isinstance(options, (list, tuple)):
183 options_iter = options
183 options_iter = options
184 # Handle old value,label lists ... where value also can be value,label lists
184 # Handle old value,label lists ... where value also can be value,label lists
185 options = Options()
185 options = Options()
186 for opt in options_iter:
186 for opt in options_iter:
187 if isinstance(opt, tuple) and len(opt) == 2:
187 if isinstance(opt, tuple) and len(opt) == 2:
188 value, label = opt
188 value, label = opt
189 elif isinstance(opt, basestring):
189 elif isinstance(opt, basestring):
190 value = label = opt
190 value = label = opt
191 else:
191 else:
192 raise ValueError('invalid select option type %r' % type(opt))
192 raise ValueError('invalid select option type %r' % type(opt))
193
193
194 if isinstance(value, (list, tuple)):
194 if isinstance(value, (list, tuple)):
195 option_group = options.add_optgroup(label)
195 option_group = options.add_optgroup(label)
196 for opt2 in value:
196 for opt2 in value:
197 if isinstance(opt2, tuple) and len(opt2) == 2:
197 if isinstance(opt2, tuple) and len(opt2) == 2:
198 group_value, group_label = opt2
198 group_value, group_label = opt2
199 elif isinstance(opt2, basestring):
199 elif isinstance(opt2, basestring):
200 group_value = group_label = opt2
200 group_value = group_label = opt2
201 else:
201 else:
202 raise ValueError('invalid select option type %r' % type(opt2))
202 raise ValueError('invalid select option type %r' % type(opt2))
203
203
204 option_group.add_option(group_label, group_value)
204 option_group.add_option(group_label, group_value)
205 else:
205 else:
206 options.add_option(label, value)
206 options.add_option(label, value)
207
207
208 return raw_select(name, selected_values, options, id=id, **attrs)
208 return raw_select(name, selected_values, options, id=id, **attrs)
209
209
210
210
211 def branding(name, length=40):
211 def branding(name, length=40):
212 return truncate(name, length, indicator="")
212 return truncate(name, length, indicator="")
213
213
214
214
215 def FID(raw_id, path):
215 def FID(raw_id, path):
216 """
216 """
217 Creates a unique ID for filenode based on it's hash of path and commit
217 Creates a unique ID for filenode based on it's hash of path and commit
218 it's safe to use in urls
218 it's safe to use in urls
219
219
220 :param raw_id:
220 :param raw_id:
221 :param path:
221 :param path:
222 """
222 """
223
223
224 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
224 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
225
225
226
226
227 class _GetError(object):
227 class _GetError(object):
228 """Get error from form_errors, and represent it as span wrapped error
228 """Get error from form_errors, and represent it as span wrapped error
229 message
229 message
230
230
231 :param field_name: field to fetch errors for
231 :param field_name: field to fetch errors for
232 :param form_errors: form errors dict
232 :param form_errors: form errors dict
233 """
233 """
234
234
235 def __call__(self, field_name, form_errors):
235 def __call__(self, field_name, form_errors):
236 tmpl = """<span class="error_msg">%s</span>"""
236 tmpl = """<span class="error_msg">%s</span>"""
237 if form_errors and field_name in form_errors:
237 if form_errors and field_name in form_errors:
238 return literal(tmpl % form_errors.get(field_name))
238 return literal(tmpl % form_errors.get(field_name))
239
239
240
240
241 get_error = _GetError()
241 get_error = _GetError()
242
242
243
243
244 class _ToolTip(object):
244 class _ToolTip(object):
245
245
246 def __call__(self, tooltip_title, trim_at=50):
246 def __call__(self, tooltip_title, trim_at=50):
247 """
247 """
248 Special function just to wrap our text into nice formatted
248 Special function just to wrap our text into nice formatted
249 autowrapped text
249 autowrapped text
250
250
251 :param tooltip_title:
251 :param tooltip_title:
252 """
252 """
253 tooltip_title = escape(tooltip_title)
253 tooltip_title = escape(tooltip_title)
254 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
254 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
255 return tooltip_title
255 return tooltip_title
256
256
257
257
258 tooltip = _ToolTip()
258 tooltip = _ToolTip()
259
259
260 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
260 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
261
261
262
262
263 def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None,
263 def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None,
264 limit_items=False, linkify_last_item=False, hide_last_item=False,
264 limit_items=False, linkify_last_item=False, hide_last_item=False,
265 copy_path_icon=True):
265 copy_path_icon=True):
266 if isinstance(file_path, str):
266 if isinstance(file_path, str):
267 file_path = safe_unicode(file_path)
267 file_path = safe_unicode(file_path)
268
268
269 if at_ref:
269 if at_ref:
270 route_qry = {'at': at_ref}
270 route_qry = {'at': at_ref}
271 default_landing_ref = at_ref or landing_ref_name or commit_id
271 default_landing_ref = at_ref or landing_ref_name or commit_id
272 else:
272 else:
273 route_qry = None
273 route_qry = None
274 default_landing_ref = commit_id
274 default_landing_ref = commit_id
275
275
276 # first segment is a `HOME` link to repo files root location
276 # first segment is a `HOME` link to repo files root location
277 root_name = literal(u'<i class="icon-home"></i>')
277 root_name = literal(u'<i class="icon-home"></i>')
278
278
279 url_segments = [
279 url_segments = [
280 link_to(
280 link_to(
281 root_name,
281 root_name,
282 repo_files_by_ref_url(
282 repo_files_by_ref_url(
283 repo_name,
283 repo_name,
284 repo_type,
284 repo_type,
285 f_path=None, # None here is a special case for SVN repos,
285 f_path=None, # None here is a special case for SVN repos,
286 # that won't prefix with a ref
286 # that won't prefix with a ref
287 ref_name=default_landing_ref,
287 ref_name=default_landing_ref,
288 commit_id=commit_id,
288 commit_id=commit_id,
289 query=route_qry
289 query=route_qry
290 )
290 )
291 )]
291 )]
292
292
293 path_segments = file_path.split('/')
293 path_segments = file_path.split('/')
294 last_cnt = len(path_segments) - 1
294 last_cnt = len(path_segments) - 1
295 for cnt, segment in enumerate(path_segments):
295 for cnt, segment in enumerate(path_segments):
296 if not segment:
296 if not segment:
297 continue
297 continue
298 segment_html = escape(segment)
298 segment_html = escape(segment)
299
299
300 last_item = cnt == last_cnt
300 last_item = cnt == last_cnt
301
301
302 if last_item and hide_last_item:
302 if last_item and hide_last_item:
303 # iterate over and hide last element
303 # iterate over and hide last element
304 continue
304 continue
305
305
306 if last_item and linkify_last_item is False:
306 if last_item and linkify_last_item is False:
307 # plain version
307 # plain version
308 url_segments.append(segment_html)
308 url_segments.append(segment_html)
309 else:
309 else:
310 url_segments.append(
310 url_segments.append(
311 link_to(
311 link_to(
312 segment_html,
312 segment_html,
313 repo_files_by_ref_url(
313 repo_files_by_ref_url(
314 repo_name,
314 repo_name,
315 repo_type,
315 repo_type,
316 f_path='/'.join(path_segments[:cnt + 1]),
316 f_path='/'.join(path_segments[:cnt + 1]),
317 ref_name=default_landing_ref,
317 ref_name=default_landing_ref,
318 commit_id=commit_id,
318 commit_id=commit_id,
319 query=route_qry
319 query=route_qry
320 ),
320 ),
321 ))
321 ))
322
322
323 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
323 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
324 if limit_items and len(limited_url_segments) < len(url_segments):
324 if limit_items and len(limited_url_segments) < len(url_segments):
325 url_segments = limited_url_segments
325 url_segments = limited_url_segments
326
326
327 full_path = file_path
327 full_path = file_path
328 if copy_path_icon:
328 if copy_path_icon:
329 icon = files_icon.format(escape(full_path))
329 icon = files_icon.format(escape(full_path))
330 else:
330 else:
331 icon = ''
331 icon = ''
332
332
333 if file_path == '':
333 if file_path == '':
334 return root_name
334 return root_name
335 else:
335 else:
336 return literal(' / '.join(url_segments) + icon)
336 return literal(' / '.join(url_segments) + icon)
337
337
338
338
339 def files_url_data(request):
339 def files_url_data(request):
340 import urllib
340 import urllib
341 matchdict = request.matchdict
341 matchdict = request.matchdict
342
342
343 if 'f_path' not in matchdict:
343 if 'f_path' not in matchdict:
344 matchdict['f_path'] = ''
344 matchdict['f_path'] = ''
345 else:
345 else:
346 matchdict['f_path'] = urllib.quote(matchdict['f_path'])
346 matchdict['f_path'] = urllib.quote(safe_str(matchdict['f_path']))
347 if 'commit_id' not in matchdict:
347 if 'commit_id' not in matchdict:
348 matchdict['commit_id'] = 'tip'
348 matchdict['commit_id'] = 'tip'
349
349
350 return json.dumps(matchdict)
350 return json.dumps(matchdict)
351
351
352
352
353 def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ):
353 def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ):
354 _is_svn = is_svn(db_repo_type)
354 _is_svn = is_svn(db_repo_type)
355 final_f_path = f_path
355 final_f_path = f_path
356
356
357 if _is_svn:
357 if _is_svn:
358 """
358 """
359 For SVN the ref_name cannot be used as a commit_id, it needs to be prefixed with
359 For SVN the ref_name cannot be used as a commit_id, it needs to be prefixed with
360 actually commit_id followed by the ref_name. This should be done only in case
360 actually commit_id followed by the ref_name. This should be done only in case
361 This is a initial landing url, without additional paths.
361 This is a initial landing url, without additional paths.
362
362
363 like: /1000/tags/1.0.0/?at=tags/1.0.0
363 like: /1000/tags/1.0.0/?at=tags/1.0.0
364 """
364 """
365
365
366 if ref_name and ref_name != 'tip':
366 if ref_name and ref_name != 'tip':
367 # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it
367 # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it
368 # for SVN we only do this magic prefix if it's root, .eg landing revision
368 # for SVN we only do this magic prefix if it's root, .eg landing revision
369 # of files link. If we are in the tree we don't need this since we traverse the url
369 # of files link. If we are in the tree we don't need this since we traverse the url
370 # that has everything stored
370 # that has everything stored
371 if f_path in ['', '/']:
371 if f_path in ['', '/']:
372 final_f_path = '/'.join([ref_name, f_path])
372 final_f_path = '/'.join([ref_name, f_path])
373
373
374 # SVN always needs a commit_id explicitly, without a named REF
374 # SVN always needs a commit_id explicitly, without a named REF
375 default_commit_id = commit_id
375 default_commit_id = commit_id
376 else:
376 else:
377 """
377 """
378 For git and mercurial we construct a new URL using the names instead of commit_id
378 For git and mercurial we construct a new URL using the names instead of commit_id
379 like: /master/some_path?at=master
379 like: /master/some_path?at=master
380 """
380 """
381 # We currently do not support branches with slashes
381 # We currently do not support branches with slashes
382 if '/' in ref_name:
382 if '/' in ref_name:
383 default_commit_id = commit_id
383 default_commit_id = commit_id
384 else:
384 else:
385 default_commit_id = ref_name
385 default_commit_id = ref_name
386
386
387 # sometimes we pass f_path as None, to indicate explicit no prefix,
387 # sometimes we pass f_path as None, to indicate explicit no prefix,
388 # we translate it to string to not have None
388 # we translate it to string to not have None
389 final_f_path = final_f_path or ''
389 final_f_path = final_f_path or ''
390
390
391 files_url = route_path(
391 files_url = route_path(
392 'repo_files',
392 'repo_files',
393 repo_name=db_repo_name,
393 repo_name=db_repo_name,
394 commit_id=default_commit_id,
394 commit_id=default_commit_id,
395 f_path=final_f_path,
395 f_path=final_f_path,
396 _query=query
396 _query=query
397 )
397 )
398 return files_url
398 return files_url
399
399
400
400
401 def code_highlight(code, lexer, formatter, use_hl_filter=False):
401 def code_highlight(code, lexer, formatter, use_hl_filter=False):
402 """
402 """
403 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
403 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
404
404
405 If ``outfile`` is given and a valid file object (an object
405 If ``outfile`` is given and a valid file object (an object
406 with a ``write`` method), the result will be written to it, otherwise
406 with a ``write`` method), the result will be written to it, otherwise
407 it is returned as a string.
407 it is returned as a string.
408 """
408 """
409 if use_hl_filter:
409 if use_hl_filter:
410 # add HL filter
410 # add HL filter
411 from rhodecode.lib.index import search_utils
411 from rhodecode.lib.index import search_utils
412 lexer.add_filter(search_utils.ElasticSearchHLFilter())
412 lexer.add_filter(search_utils.ElasticSearchHLFilter())
413 return pygments.format(pygments.lex(code, lexer), formatter)
413 return pygments.format(pygments.lex(code, lexer), formatter)
414
414
415
415
416 class CodeHtmlFormatter(HtmlFormatter):
416 class CodeHtmlFormatter(HtmlFormatter):
417 """
417 """
418 My code Html Formatter for source codes
418 My code Html Formatter for source codes
419 """
419 """
420
420
421 def wrap(self, source, outfile):
421 def wrap(self, source, outfile):
422 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
422 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
423
423
424 def _wrap_code(self, source):
424 def _wrap_code(self, source):
425 for cnt, it in enumerate(source):
425 for cnt, it in enumerate(source):
426 i, t = it
426 i, t = it
427 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
427 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
428 yield i, t
428 yield i, t
429
429
430 def _wrap_tablelinenos(self, inner):
430 def _wrap_tablelinenos(self, inner):
431 dummyoutfile = StringIO.StringIO()
431 dummyoutfile = StringIO.StringIO()
432 lncount = 0
432 lncount = 0
433 for t, line in inner:
433 for t, line in inner:
434 if t:
434 if t:
435 lncount += 1
435 lncount += 1
436 dummyoutfile.write(line)
436 dummyoutfile.write(line)
437
437
438 fl = self.linenostart
438 fl = self.linenostart
439 mw = len(str(lncount + fl - 1))
439 mw = len(str(lncount + fl - 1))
440 sp = self.linenospecial
440 sp = self.linenospecial
441 st = self.linenostep
441 st = self.linenostep
442 la = self.lineanchors
442 la = self.lineanchors
443 aln = self.anchorlinenos
443 aln = self.anchorlinenos
444 nocls = self.noclasses
444 nocls = self.noclasses
445 if sp:
445 if sp:
446 lines = []
446 lines = []
447
447
448 for i in range(fl, fl + lncount):
448 for i in range(fl, fl + lncount):
449 if i % st == 0:
449 if i % st == 0:
450 if i % sp == 0:
450 if i % sp == 0:
451 if aln:
451 if aln:
452 lines.append('<a href="#%s%d" class="special">%*d</a>' %
452 lines.append('<a href="#%s%d" class="special">%*d</a>' %
453 (la, i, mw, i))
453 (la, i, mw, i))
454 else:
454 else:
455 lines.append('<span class="special">%*d</span>' % (mw, i))
455 lines.append('<span class="special">%*d</span>' % (mw, i))
456 else:
456 else:
457 if aln:
457 if aln:
458 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
458 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
459 else:
459 else:
460 lines.append('%*d' % (mw, i))
460 lines.append('%*d' % (mw, i))
461 else:
461 else:
462 lines.append('')
462 lines.append('')
463 ls = '\n'.join(lines)
463 ls = '\n'.join(lines)
464 else:
464 else:
465 lines = []
465 lines = []
466 for i in range(fl, fl + lncount):
466 for i in range(fl, fl + lncount):
467 if i % st == 0:
467 if i % st == 0:
468 if aln:
468 if aln:
469 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
469 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
470 else:
470 else:
471 lines.append('%*d' % (mw, i))
471 lines.append('%*d' % (mw, i))
472 else:
472 else:
473 lines.append('')
473 lines.append('')
474 ls = '\n'.join(lines)
474 ls = '\n'.join(lines)
475
475
476 # in case you wonder about the seemingly redundant <div> here: since the
476 # in case you wonder about the seemingly redundant <div> here: since the
477 # content in the other cell also is wrapped in a div, some browsers in
477 # content in the other cell also is wrapped in a div, some browsers in
478 # some configurations seem to mess up the formatting...
478 # some configurations seem to mess up the formatting...
479 if nocls:
479 if nocls:
480 yield 0, ('<table class="%stable">' % self.cssclass +
480 yield 0, ('<table class="%stable">' % self.cssclass +
481 '<tr><td><div class="linenodiv" '
481 '<tr><td><div class="linenodiv" '
482 'style="background-color: #f0f0f0; padding-right: 10px">'
482 'style="background-color: #f0f0f0; padding-right: 10px">'
483 '<pre style="line-height: 125%">' +
483 '<pre style="line-height: 125%">' +
484 ls + '</pre></div></td><td id="hlcode" class="code">')
484 ls + '</pre></div></td><td id="hlcode" class="code">')
485 else:
485 else:
486 yield 0, ('<table class="%stable">' % self.cssclass +
486 yield 0, ('<table class="%stable">' % self.cssclass +
487 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
487 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
488 ls + '</pre></div></td><td id="hlcode" class="code">')
488 ls + '</pre></div></td><td id="hlcode" class="code">')
489 yield 0, dummyoutfile.getvalue()
489 yield 0, dummyoutfile.getvalue()
490 yield 0, '</td></tr></table>'
490 yield 0, '</td></tr></table>'
491
491
492
492
493 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
493 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
494 def __init__(self, **kw):
494 def __init__(self, **kw):
495 # only show these line numbers if set
495 # only show these line numbers if set
496 self.only_lines = kw.pop('only_line_numbers', [])
496 self.only_lines = kw.pop('only_line_numbers', [])
497 self.query_terms = kw.pop('query_terms', [])
497 self.query_terms = kw.pop('query_terms', [])
498 self.max_lines = kw.pop('max_lines', 5)
498 self.max_lines = kw.pop('max_lines', 5)
499 self.line_context = kw.pop('line_context', 3)
499 self.line_context = kw.pop('line_context', 3)
500 self.url = kw.pop('url', None)
500 self.url = kw.pop('url', None)
501
501
502 super(CodeHtmlFormatter, self).__init__(**kw)
502 super(CodeHtmlFormatter, self).__init__(**kw)
503
503
504 def _wrap_code(self, source):
504 def _wrap_code(self, source):
505 for cnt, it in enumerate(source):
505 for cnt, it in enumerate(source):
506 i, t = it
506 i, t = it
507 t = '<pre>%s</pre>' % t
507 t = '<pre>%s</pre>' % t
508 yield i, t
508 yield i, t
509
509
510 def _wrap_tablelinenos(self, inner):
510 def _wrap_tablelinenos(self, inner):
511 yield 0, '<table class="code-highlight %stable">' % self.cssclass
511 yield 0, '<table class="code-highlight %stable">' % self.cssclass
512
512
513 last_shown_line_number = 0
513 last_shown_line_number = 0
514 current_line_number = 1
514 current_line_number = 1
515
515
516 for t, line in inner:
516 for t, line in inner:
517 if not t:
517 if not t:
518 yield t, line
518 yield t, line
519 continue
519 continue
520
520
521 if current_line_number in self.only_lines:
521 if current_line_number in self.only_lines:
522 if last_shown_line_number + 1 != current_line_number:
522 if last_shown_line_number + 1 != current_line_number:
523 yield 0, '<tr>'
523 yield 0, '<tr>'
524 yield 0, '<td class="line">...</td>'
524 yield 0, '<td class="line">...</td>'
525 yield 0, '<td id="hlcode" class="code"></td>'
525 yield 0, '<td id="hlcode" class="code"></td>'
526 yield 0, '</tr>'
526 yield 0, '</tr>'
527
527
528 yield 0, '<tr>'
528 yield 0, '<tr>'
529 if self.url:
529 if self.url:
530 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
530 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
531 self.url, current_line_number, current_line_number)
531 self.url, current_line_number, current_line_number)
532 else:
532 else:
533 yield 0, '<td class="line"><a href="">%i</a></td>' % (
533 yield 0, '<td class="line"><a href="">%i</a></td>' % (
534 current_line_number)
534 current_line_number)
535 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
535 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
536 yield 0, '</tr>'
536 yield 0, '</tr>'
537
537
538 last_shown_line_number = current_line_number
538 last_shown_line_number = current_line_number
539
539
540 current_line_number += 1
540 current_line_number += 1
541
541
542 yield 0, '</table>'
542 yield 0, '</table>'
543
543
544
544
545 def hsv_to_rgb(h, s, v):
545 def hsv_to_rgb(h, s, v):
546 """ Convert hsv color values to rgb """
546 """ Convert hsv color values to rgb """
547
547
548 if s == 0.0:
548 if s == 0.0:
549 return v, v, v
549 return v, v, v
550 i = int(h * 6.0) # XXX assume int() truncates!
550 i = int(h * 6.0) # XXX assume int() truncates!
551 f = (h * 6.0) - i
551 f = (h * 6.0) - i
552 p = v * (1.0 - s)
552 p = v * (1.0 - s)
553 q = v * (1.0 - s * f)
553 q = v * (1.0 - s * f)
554 t = v * (1.0 - s * (1.0 - f))
554 t = v * (1.0 - s * (1.0 - f))
555 i = i % 6
555 i = i % 6
556 if i == 0:
556 if i == 0:
557 return v, t, p
557 return v, t, p
558 if i == 1:
558 if i == 1:
559 return q, v, p
559 return q, v, p
560 if i == 2:
560 if i == 2:
561 return p, v, t
561 return p, v, t
562 if i == 3:
562 if i == 3:
563 return p, q, v
563 return p, q, v
564 if i == 4:
564 if i == 4:
565 return t, p, v
565 return t, p, v
566 if i == 5:
566 if i == 5:
567 return v, p, q
567 return v, p, q
568
568
569
569
570 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
570 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
571 """
571 """
572 Generator for getting n of evenly distributed colors using
572 Generator for getting n of evenly distributed colors using
573 hsv color and golden ratio. It always return same order of colors
573 hsv color and golden ratio. It always return same order of colors
574
574
575 :param n: number of colors to generate
575 :param n: number of colors to generate
576 :param saturation: saturation of returned colors
576 :param saturation: saturation of returned colors
577 :param lightness: lightness of returned colors
577 :param lightness: lightness of returned colors
578 :returns: RGB tuple
578 :returns: RGB tuple
579 """
579 """
580
580
581 golden_ratio = 0.618033988749895
581 golden_ratio = 0.618033988749895
582 h = 0.22717784590367374
582 h = 0.22717784590367374
583
583
584 for _ in xrange(n):
584 for _ in xrange(n):
585 h += golden_ratio
585 h += golden_ratio
586 h %= 1
586 h %= 1
587 HSV_tuple = [h, saturation, lightness]
587 HSV_tuple = [h, saturation, lightness]
588 RGB_tuple = hsv_to_rgb(*HSV_tuple)
588 RGB_tuple = hsv_to_rgb(*HSV_tuple)
589 yield map(lambda x: str(int(x * 256)), RGB_tuple)
589 yield map(lambda x: str(int(x * 256)), RGB_tuple)
590
590
591
591
592 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
592 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
593 """
593 """
594 Returns a function which when called with an argument returns a unique
594 Returns a function which when called with an argument returns a unique
595 color for that argument, eg.
595 color for that argument, eg.
596
596
597 :param n: number of colors to generate
597 :param n: number of colors to generate
598 :param saturation: saturation of returned colors
598 :param saturation: saturation of returned colors
599 :param lightness: lightness of returned colors
599 :param lightness: lightness of returned colors
600 :returns: css RGB string
600 :returns: css RGB string
601
601
602 >>> color_hash = color_hasher()
602 >>> color_hash = color_hasher()
603 >>> color_hash('hello')
603 >>>