##// END OF EJS Templates
clone-uri: fixed the problems with key mismatch that caused errors on summary page.
milka -
r4667:7c6563e6 default
parent child Browse files
Show More
@@ -1,1071 +1,1074 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-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 """
22 """
23 Some simple helper functions
23 Some simple helper functions
24 """
24 """
25
25
26 import collections
26 import collections
27 import datetime
27 import datetime
28 import dateutil.relativedelta
28 import dateutil.relativedelta
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import re
31 import re
32 import sys
32 import sys
33 import time
33 import time
34 import urllib
34 import urllib
35 import urlobject
35 import urlobject
36 import uuid
36 import uuid
37 import getpass
37 import getpass
38 from functools import update_wrapper, partial
38 from functools import update_wrapper, partial
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 sqlalchemy.exc
43 import sqlalchemy.exc
44 import sqlalchemy.sql
44 import sqlalchemy.sql
45 import webob
45 import webob
46 import pyramid.threadlocal
46 import pyramid.threadlocal
47 from pyramid import compat
47 from pyramid import compat
48 from pyramid.settings import asbool
48 from pyramid.settings import asbool
49
49
50 import rhodecode
50 import rhodecode
51 from rhodecode.translation import _, _pluralize
51 from rhodecode.translation import _, _pluralize
52
52
53
53
54 def md5(s):
54 def md5(s):
55 return hashlib.md5(s).hexdigest()
55 return hashlib.md5(s).hexdigest()
56
56
57
57
58 def md5_safe(s):
58 def md5_safe(s):
59 return md5(safe_str(s))
59 return md5(safe_str(s))
60
60
61
61
62 def sha1(s):
62 def sha1(s):
63 return hashlib.sha1(s).hexdigest()
63 return hashlib.sha1(s).hexdigest()
64
64
65
65
66 def sha1_safe(s):
66 def sha1_safe(s):
67 return sha1(safe_str(s))
67 return sha1(safe_str(s))
68
68
69
69
70 def __get_lem(extra_mapping=None):
70 def __get_lem(extra_mapping=None):
71 """
71 """
72 Get language extension map based on what's inside pygments lexers
72 Get language extension map based on what's inside pygments lexers
73 """
73 """
74 d = collections.defaultdict(lambda: [])
74 d = collections.defaultdict(lambda: [])
75
75
76 def __clean(s):
76 def __clean(s):
77 s = s.lstrip('*')
77 s = s.lstrip('*')
78 s = s.lstrip('.')
78 s = s.lstrip('.')
79
79
80 if s.find('[') != -1:
80 if s.find('[') != -1:
81 exts = []
81 exts = []
82 start, stop = s.find('['), s.find(']')
82 start, stop = s.find('['), s.find(']')
83
83
84 for suffix in s[start + 1:stop]:
84 for suffix in s[start + 1:stop]:
85 exts.append(s[:s.find('[')] + suffix)
85 exts.append(s[:s.find('[')] + suffix)
86 return [e.lower() for e in exts]
86 return [e.lower() for e in exts]
87 else:
87 else:
88 return [s.lower()]
88 return [s.lower()]
89
89
90 for lx, t in sorted(pygments.lexers.LEXERS.items()):
90 for lx, t in sorted(pygments.lexers.LEXERS.items()):
91 m = map(__clean, t[-2])
91 m = map(__clean, t[-2])
92 if m:
92 if m:
93 m = reduce(lambda x, y: x + y, m)
93 m = reduce(lambda x, y: x + y, m)
94 for ext in m:
94 for ext in m:
95 desc = lx.replace('Lexer', '')
95 desc = lx.replace('Lexer', '')
96 d[ext].append(desc)
96 d[ext].append(desc)
97
97
98 data = dict(d)
98 data = dict(d)
99
99
100 extra_mapping = extra_mapping or {}
100 extra_mapping = extra_mapping or {}
101 if extra_mapping:
101 if extra_mapping:
102 for k, v in extra_mapping.items():
102 for k, v in extra_mapping.items():
103 if k not in data:
103 if k not in data:
104 # register new mapping2lexer
104 # register new mapping2lexer
105 data[k] = [v]
105 data[k] = [v]
106
106
107 return data
107 return data
108
108
109
109
110 def str2bool(_str):
110 def str2bool(_str):
111 """
111 """
112 returns True/False value from given string, it tries to translate the
112 returns True/False value from given string, it tries to translate the
113 string into boolean
113 string into boolean
114
114
115 :param _str: string value to translate into boolean
115 :param _str: string value to translate into boolean
116 :rtype: boolean
116 :rtype: boolean
117 :returns: boolean from given string
117 :returns: boolean from given string
118 """
118 """
119 if _str is None:
119 if _str is None:
120 return False
120 return False
121 if _str in (True, False):
121 if _str in (True, False):
122 return _str
122 return _str
123 _str = str(_str).strip().lower()
123 _str = str(_str).strip().lower()
124 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
124 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
125
125
126
126
127 def aslist(obj, sep=None, strip=True):
127 def aslist(obj, sep=None, strip=True):
128 """
128 """
129 Returns given string separated by sep as list
129 Returns given string separated by sep as list
130
130
131 :param obj:
131 :param obj:
132 :param sep:
132 :param sep:
133 :param strip:
133 :param strip:
134 """
134 """
135 if isinstance(obj, (basestring,)):
135 if isinstance(obj, (basestring,)):
136 lst = obj.split(sep)
136 lst = obj.split(sep)
137 if strip:
137 if strip:
138 lst = [v.strip() for v in lst]
138 lst = [v.strip() for v in lst]
139 return lst
139 return lst
140 elif isinstance(obj, (list, tuple)):
140 elif isinstance(obj, (list, tuple)):
141 return obj
141 return obj
142 elif obj is None:
142 elif obj is None:
143 return []
143 return []
144 else:
144 else:
145 return [obj]
145 return [obj]
146
146
147
147
148 def convert_line_endings(line, mode):
148 def convert_line_endings(line, mode):
149 """
149 """
150 Converts a given line "line end" accordingly to given mode
150 Converts a given line "line end" accordingly to given mode
151
151
152 Available modes are::
152 Available modes are::
153 0 - Unix
153 0 - Unix
154 1 - Mac
154 1 - Mac
155 2 - DOS
155 2 - DOS
156
156
157 :param line: given line to convert
157 :param line: given line to convert
158 :param mode: mode to convert to
158 :param mode: mode to convert to
159 :rtype: str
159 :rtype: str
160 :return: converted line according to mode
160 :return: converted line according to mode
161 """
161 """
162 if mode == 0:
162 if mode == 0:
163 line = line.replace('\r\n', '\n')
163 line = line.replace('\r\n', '\n')
164 line = line.replace('\r', '\n')
164 line = line.replace('\r', '\n')
165 elif mode == 1:
165 elif mode == 1:
166 line = line.replace('\r\n', '\r')
166 line = line.replace('\r\n', '\r')
167 line = line.replace('\n', '\r')
167 line = line.replace('\n', '\r')
168 elif mode == 2:
168 elif mode == 2:
169 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
169 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
170 return line
170 return line
171
171
172
172
173 def detect_mode(line, default):
173 def detect_mode(line, default):
174 """
174 """
175 Detects line break for given line, if line break couldn't be found
175 Detects line break for given line, if line break couldn't be found
176 given default value is returned
176 given default value is returned
177
177
178 :param line: str line
178 :param line: str line
179 :param default: default
179 :param default: default
180 :rtype: int
180 :rtype: int
181 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
181 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
182 """
182 """
183 if line.endswith('\r\n'):
183 if line.endswith('\r\n'):
184 return 2
184 return 2
185 elif line.endswith('\n'):
185 elif line.endswith('\n'):
186 return 0
186 return 0
187 elif line.endswith('\r'):
187 elif line.endswith('\r'):
188 return 1
188 return 1
189 else:
189 else:
190 return default
190 return default
191
191
192
192
193 def safe_int(val, default=None):
193 def safe_int(val, default=None):
194 """
194 """
195 Returns int() of val if val is not convertable to int use default
195 Returns int() of val if val is not convertable to int use default
196 instead
196 instead
197
197
198 :param val:
198 :param val:
199 :param default:
199 :param default:
200 """
200 """
201
201
202 try:
202 try:
203 val = int(val)
203 val = int(val)
204 except (ValueError, TypeError):
204 except (ValueError, TypeError):
205 val = default
205 val = default
206
206
207 return val
207 return val
208
208
209
209
210 def safe_unicode(str_, from_encoding=None, use_chardet=False):
210 def safe_unicode(str_, from_encoding=None, use_chardet=False):
211 """
211 """
212 safe unicode function. Does few trick to turn str_ into unicode
212 safe unicode function. Does few trick to turn str_ into unicode
213
213
214 In case of UnicodeDecode error, we try to return it with encoding detected
214 In case of UnicodeDecode error, we try to return it with encoding detected
215 by chardet library if it fails fallback to unicode with errors replaced
215 by chardet library if it fails fallback to unicode with errors replaced
216
216
217 :param str_: string to decode
217 :param str_: string to decode
218 :rtype: unicode
218 :rtype: unicode
219 :returns: unicode object
219 :returns: unicode object
220 """
220 """
221 if isinstance(str_, unicode):
221 if isinstance(str_, unicode):
222 return str_
222 return str_
223
223
224 if not from_encoding:
224 if not from_encoding:
225 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
225 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
226 'utf8'), sep=',')
226 'utf8'), sep=',')
227 from_encoding = DEFAULT_ENCODINGS
227 from_encoding = DEFAULT_ENCODINGS
228
228
229 if not isinstance(from_encoding, (list, tuple)):
229 if not isinstance(from_encoding, (list, tuple)):
230 from_encoding = [from_encoding]
230 from_encoding = [from_encoding]
231
231
232 try:
232 try:
233 return unicode(str_)
233 return unicode(str_)
234 except UnicodeDecodeError:
234 except UnicodeDecodeError:
235 pass
235 pass
236
236
237 for enc in from_encoding:
237 for enc in from_encoding:
238 try:
238 try:
239 return unicode(str_, enc)
239 return unicode(str_, enc)
240 except UnicodeDecodeError:
240 except UnicodeDecodeError:
241 pass
241 pass
242
242
243 if use_chardet:
243 if use_chardet:
244 try:
244 try:
245 import chardet
245 import chardet
246 encoding = chardet.detect(str_)['encoding']
246 encoding = chardet.detect(str_)['encoding']
247 if encoding is None:
247 if encoding is None:
248 raise Exception()
248 raise Exception()
249 return str_.decode(encoding)
249 return str_.decode(encoding)
250 except (ImportError, UnicodeDecodeError, Exception):
250 except (ImportError, UnicodeDecodeError, Exception):
251 return unicode(str_, from_encoding[0], 'replace')
251 return unicode(str_, from_encoding[0], 'replace')
252 else:
252 else:
253 return unicode(str_, from_encoding[0], 'replace')
253 return unicode(str_, from_encoding[0], 'replace')
254
254
255 def safe_str(unicode_, to_encoding=None, use_chardet=False):
255 def safe_str(unicode_, to_encoding=None, use_chardet=False):
256 """
256 """
257 safe str function. Does few trick to turn unicode_ into string
257 safe str function. Does few trick to turn unicode_ into string
258
258
259 In case of UnicodeEncodeError, we try to return it with encoding detected
259 In case of UnicodeEncodeError, we try to return it with encoding detected
260 by chardet library if it fails fallback to string with errors replaced
260 by chardet library if it fails fallback to string with errors replaced
261
261
262 :param unicode_: unicode to encode
262 :param unicode_: unicode to encode
263 :rtype: str
263 :rtype: str
264 :returns: str object
264 :returns: str object
265 """
265 """
266
266
267 # if it's not basestr cast to str
267 # if it's not basestr cast to str
268 if not isinstance(unicode_, compat.string_types):
268 if not isinstance(unicode_, compat.string_types):
269 return str(unicode_)
269 return str(unicode_)
270
270
271 if isinstance(unicode_, str):
271 if isinstance(unicode_, str):
272 return unicode_
272 return unicode_
273
273
274 if not to_encoding:
274 if not to_encoding:
275 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
275 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
276 'utf8'), sep=',')
276 'utf8'), sep=',')
277 to_encoding = DEFAULT_ENCODINGS
277 to_encoding = DEFAULT_ENCODINGS
278
278
279 if not isinstance(to_encoding, (list, tuple)):
279 if not isinstance(to_encoding, (list, tuple)):
280 to_encoding = [to_encoding]
280 to_encoding = [to_encoding]
281
281
282 for enc in to_encoding:
282 for enc in to_encoding:
283 try:
283 try:
284 return unicode_.encode(enc)
284 return unicode_.encode(enc)
285 except UnicodeEncodeError:
285 except UnicodeEncodeError:
286 pass
286 pass
287
287
288 if use_chardet:
288 if use_chardet:
289 try:
289 try:
290 import chardet
290 import chardet
291 encoding = chardet.detect(unicode_)['encoding']
291 encoding = chardet.detect(unicode_)['encoding']
292 if encoding is None:
292 if encoding is None:
293 raise UnicodeEncodeError()
293 raise UnicodeEncodeError()
294
294
295 return unicode_.encode(encoding)
295 return unicode_.encode(encoding)
296 except (ImportError, UnicodeEncodeError):
296 except (ImportError, UnicodeEncodeError):
297 return unicode_.encode(to_encoding[0], 'replace')
297 return unicode_.encode(to_encoding[0], 'replace')
298 else:
298 else:
299 return unicode_.encode(to_encoding[0], 'replace')
299 return unicode_.encode(to_encoding[0], 'replace')
300
300
301
301
302 def remove_suffix(s, suffix):
302 def remove_suffix(s, suffix):
303 if s.endswith(suffix):
303 if s.endswith(suffix):
304 s = s[:-1 * len(suffix)]
304 s = s[:-1 * len(suffix)]
305 return s
305 return s
306
306
307
307
308 def remove_prefix(s, prefix):
308 def remove_prefix(s, prefix):
309 if s.startswith(prefix):
309 if s.startswith(prefix):
310 s = s[len(prefix):]
310 s = s[len(prefix):]
311 return s
311 return s
312
312
313
313
314 def find_calling_context(ignore_modules=None):
314 def find_calling_context(ignore_modules=None):
315 """
315 """
316 Look through the calling stack and return the frame which called
316 Look through the calling stack and return the frame which called
317 this function and is part of core module ( ie. rhodecode.* )
317 this function and is part of core module ( ie. rhodecode.* )
318
318
319 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
319 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
320 """
320 """
321
321
322 ignore_modules = ignore_modules or []
322 ignore_modules = ignore_modules or []
323
323
324 f = sys._getframe(2)
324 f = sys._getframe(2)
325 while f.f_back is not None:
325 while f.f_back is not None:
326 name = f.f_globals.get('__name__')
326 name = f.f_globals.get('__name__')
327 if name and name.startswith(__name__.split('.')[0]):
327 if name and name.startswith(__name__.split('.')[0]):
328 if name not in ignore_modules:
328 if name not in ignore_modules:
329 return f
329 return f
330 f = f.f_back
330 f = f.f_back
331 return None
331 return None
332
332
333
333
334 def ping_connection(connection, branch):
334 def ping_connection(connection, branch):
335 if branch:
335 if branch:
336 # "branch" refers to a sub-connection of a connection,
336 # "branch" refers to a sub-connection of a connection,
337 # we don't want to bother pinging on these.
337 # we don't want to bother pinging on these.
338 return
338 return
339
339
340 # turn off "close with result". This flag is only used with
340 # turn off "close with result". This flag is only used with
341 # "connectionless" execution, otherwise will be False in any case
341 # "connectionless" execution, otherwise will be False in any case
342 save_should_close_with_result = connection.should_close_with_result
342 save_should_close_with_result = connection.should_close_with_result
343 connection.should_close_with_result = False
343 connection.should_close_with_result = False
344
344
345 try:
345 try:
346 # run a SELECT 1. use a core select() so that
346 # run a SELECT 1. use a core select() so that
347 # the SELECT of a scalar value without a table is
347 # the SELECT of a scalar value without a table is
348 # appropriately formatted for the backend
348 # appropriately formatted for the backend
349 connection.scalar(sqlalchemy.sql.select([1]))
349 connection.scalar(sqlalchemy.sql.select([1]))
350 except sqlalchemy.exc.DBAPIError as err:
350 except sqlalchemy.exc.DBAPIError as err:
351 # catch SQLAlchemy's DBAPIError, which is a wrapper
351 # catch SQLAlchemy's DBAPIError, which is a wrapper
352 # for the DBAPI's exception. It includes a .connection_invalidated
352 # for the DBAPI's exception. It includes a .connection_invalidated
353 # attribute which specifies if this connection is a "disconnect"
353 # attribute which specifies if this connection is a "disconnect"
354 # condition, which is based on inspection of the original exception
354 # condition, which is based on inspection of the original exception
355 # by the dialect in use.
355 # by the dialect in use.
356 if err.connection_invalidated:
356 if err.connection_invalidated:
357 # run the same SELECT again - the connection will re-validate
357 # run the same SELECT again - the connection will re-validate
358 # itself and establish a new connection. The disconnect detection
358 # itself and establish a new connection. The disconnect detection
359 # here also causes the whole connection pool to be invalidated
359 # here also causes the whole connection pool to be invalidated
360 # so that all stale connections are discarded.
360 # so that all stale connections are discarded.
361 connection.scalar(sqlalchemy.sql.select([1]))
361 connection.scalar(sqlalchemy.sql.select([1]))
362 else:
362 else:
363 raise
363 raise
364 finally:
364 finally:
365 # restore "close with result"
365 # restore "close with result"
366 connection.should_close_with_result = save_should_close_with_result
366 connection.should_close_with_result = save_should_close_with_result
367
367
368
368
369 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
369 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
370 """Custom engine_from_config functions."""
370 """Custom engine_from_config functions."""
371 log = logging.getLogger('sqlalchemy.engine')
371 log = logging.getLogger('sqlalchemy.engine')
372 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
372 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
373 debug = asbool(configuration.pop('sqlalchemy.db1.debug_query', None))
373 debug = asbool(configuration.pop('sqlalchemy.db1.debug_query', None))
374
374
375 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
375 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
376
376
377 def color_sql(sql):
377 def color_sql(sql):
378 color_seq = '\033[1;33m' # This is yellow: code 33
378 color_seq = '\033[1;33m' # This is yellow: code 33
379 normal = '\x1b[0m'
379 normal = '\x1b[0m'
380 return ''.join([color_seq, sql, normal])
380 return ''.join([color_seq, sql, normal])
381
381
382 if use_ping_connection:
382 if use_ping_connection:
383 log.debug('Adding ping_connection on the engine config.')
383 log.debug('Adding ping_connection on the engine config.')
384 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
384 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
385
385
386 if debug:
386 if debug:
387 # attach events only for debug configuration
387 # attach events only for debug configuration
388 def before_cursor_execute(conn, cursor, statement,
388 def before_cursor_execute(conn, cursor, statement,
389 parameters, context, executemany):
389 parameters, context, executemany):
390 setattr(conn, 'query_start_time', time.time())
390 setattr(conn, 'query_start_time', time.time())
391 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
391 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
392 calling_context = find_calling_context(ignore_modules=[
392 calling_context = find_calling_context(ignore_modules=[
393 'rhodecode.lib.caching_query',
393 'rhodecode.lib.caching_query',
394 'rhodecode.model.settings',
394 'rhodecode.model.settings',
395 ])
395 ])
396 if calling_context:
396 if calling_context:
397 log.info(color_sql('call context %s:%s' % (
397 log.info(color_sql('call context %s:%s' % (
398 calling_context.f_code.co_filename,
398 calling_context.f_code.co_filename,
399 calling_context.f_lineno,
399 calling_context.f_lineno,
400 )))
400 )))
401
401
402 def after_cursor_execute(conn, cursor, statement,
402 def after_cursor_execute(conn, cursor, statement,
403 parameters, context, executemany):
403 parameters, context, executemany):
404 delattr(conn, 'query_start_time')
404 delattr(conn, 'query_start_time')
405
405
406 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
406 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
407 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
407 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
408
408
409 return engine
409 return engine
410
410
411
411
412 def get_encryption_key(config):
412 def get_encryption_key(config):
413 secret = config.get('rhodecode.encrypted_values.secret')
413 secret = config.get('rhodecode.encrypted_values.secret')
414 default = config['beaker.session.secret']
414 default = config['beaker.session.secret']
415 return secret or default
415 return secret or default
416
416
417
417
418 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
418 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
419 short_format=False):
419 short_format=False):
420 """
420 """
421 Turns a datetime into an age string.
421 Turns a datetime into an age string.
422 If show_short_version is True, this generates a shorter string with
422 If show_short_version is True, this generates a shorter string with
423 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
423 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
424
424
425 * IMPORTANT*
425 * IMPORTANT*
426 Code of this function is written in special way so it's easier to
426 Code of this function is written in special way so it's easier to
427 backport it to javascript. If you mean to update it, please also update
427 backport it to javascript. If you mean to update it, please also update
428 `jquery.timeago-extension.js` file
428 `jquery.timeago-extension.js` file
429
429
430 :param prevdate: datetime object
430 :param prevdate: datetime object
431 :param now: get current time, if not define we use
431 :param now: get current time, if not define we use
432 `datetime.datetime.now()`
432 `datetime.datetime.now()`
433 :param show_short_version: if it should approximate the date and
433 :param show_short_version: if it should approximate the date and
434 return a shorter string
434 return a shorter string
435 :param show_suffix:
435 :param show_suffix:
436 :param short_format: show short format, eg 2D instead of 2 days
436 :param short_format: show short format, eg 2D instead of 2 days
437 :rtype: unicode
437 :rtype: unicode
438 :returns: unicode words describing age
438 :returns: unicode words describing age
439 """
439 """
440
440
441 def _get_relative_delta(now, prevdate):
441 def _get_relative_delta(now, prevdate):
442 base = dateutil.relativedelta.relativedelta(now, prevdate)
442 base = dateutil.relativedelta.relativedelta(now, prevdate)
443 return {
443 return {
444 'year': base.years,
444 'year': base.years,
445 'month': base.months,
445 'month': base.months,
446 'day': base.days,
446 'day': base.days,
447 'hour': base.hours,
447 'hour': base.hours,
448 'minute': base.minutes,
448 'minute': base.minutes,
449 'second': base.seconds,
449 'second': base.seconds,
450 }
450 }
451
451
452 def _is_leap_year(year):
452 def _is_leap_year(year):
453 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
453 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
454
454
455 def get_month(prevdate):
455 def get_month(prevdate):
456 return prevdate.month
456 return prevdate.month
457
457
458 def get_year(prevdate):
458 def get_year(prevdate):
459 return prevdate.year
459 return prevdate.year
460
460
461 now = now or datetime.datetime.now()
461 now = now or datetime.datetime.now()
462 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
462 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
463 deltas = {}
463 deltas = {}
464 future = False
464 future = False
465
465
466 if prevdate > now:
466 if prevdate > now:
467 now_old = now
467 now_old = now
468 now = prevdate
468 now = prevdate
469 prevdate = now_old
469 prevdate = now_old
470 future = True
470 future = True
471 if future:
471 if future:
472 prevdate = prevdate.replace(microsecond=0)
472 prevdate = prevdate.replace(microsecond=0)
473 # Get date parts deltas
473 # Get date parts deltas
474 for part in order:
474 for part in order:
475 rel_delta = _get_relative_delta(now, prevdate)
475 rel_delta = _get_relative_delta(now, prevdate)
476 deltas[part] = rel_delta[part]
476 deltas[part] = rel_delta[part]
477
477
478 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
478 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
479 # not 1 hour, -59 minutes and -59 seconds)
479 # not 1 hour, -59 minutes and -59 seconds)
480 offsets = [[5, 60], [4, 60], [3, 24]]
480 offsets = [[5, 60], [4, 60], [3, 24]]
481 for element in offsets: # seconds, minutes, hours
481 for element in offsets: # seconds, minutes, hours
482 num = element[0]
482 num = element[0]
483 length = element[1]
483 length = element[1]
484
484
485 part = order[num]
485 part = order[num]
486 carry_part = order[num - 1]
486 carry_part = order[num - 1]
487
487
488 if deltas[part] < 0:
488 if deltas[part] < 0:
489 deltas[part] += length
489 deltas[part] += length
490 deltas[carry_part] -= 1
490 deltas[carry_part] -= 1
491
491
492 # Same thing for days except that the increment depends on the (variable)
492 # Same thing for days except that the increment depends on the (variable)
493 # number of days in the month
493 # number of days in the month
494 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
494 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
495 if deltas['day'] < 0:
495 if deltas['day'] < 0:
496 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
496 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
497 deltas['day'] += 29
497 deltas['day'] += 29
498 else:
498 else:
499 deltas['day'] += month_lengths[get_month(prevdate) - 1]
499 deltas['day'] += month_lengths[get_month(prevdate) - 1]
500
500
501 deltas['month'] -= 1
501 deltas['month'] -= 1
502
502
503 if deltas['month'] < 0:
503 if deltas['month'] < 0:
504 deltas['month'] += 12
504 deltas['month'] += 12
505 deltas['year'] -= 1
505 deltas['year'] -= 1
506
506
507 # Format the result
507 # Format the result
508 if short_format:
508 if short_format:
509 fmt_funcs = {
509 fmt_funcs = {
510 'year': lambda d: u'%dy' % d,
510 'year': lambda d: u'%dy' % d,
511 'month': lambda d: u'%dm' % d,
511 'month': lambda d: u'%dm' % d,
512 'day': lambda d: u'%dd' % d,
512 'day': lambda d: u'%dd' % d,
513 'hour': lambda d: u'%dh' % d,
513 'hour': lambda d: u'%dh' % d,
514 'minute': lambda d: u'%dmin' % d,
514 'minute': lambda d: u'%dmin' % d,
515 'second': lambda d: u'%dsec' % d,
515 'second': lambda d: u'%dsec' % d,
516 }
516 }
517 else:
517 else:
518 fmt_funcs = {
518 fmt_funcs = {
519 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
519 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
520 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
520 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
521 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
521 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
522 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
522 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
523 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
523 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
524 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
524 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
525 }
525 }
526
526
527 i = 0
527 i = 0
528 for part in order:
528 for part in order:
529 value = deltas[part]
529 value = deltas[part]
530 if value != 0:
530 if value != 0:
531
531
532 if i < 5:
532 if i < 5:
533 sub_part = order[i + 1]
533 sub_part = order[i + 1]
534 sub_value = deltas[sub_part]
534 sub_value = deltas[sub_part]
535 else:
535 else:
536 sub_value = 0
536 sub_value = 0
537
537
538 if sub_value == 0 or show_short_version:
538 if sub_value == 0 or show_short_version:
539 _val = fmt_funcs[part](value)
539 _val = fmt_funcs[part](value)
540 if future:
540 if future:
541 if show_suffix:
541 if show_suffix:
542 return _(u'in ${ago}', mapping={'ago': _val})
542 return _(u'in ${ago}', mapping={'ago': _val})
543 else:
543 else:
544 return _(_val)
544 return _(_val)
545
545
546 else:
546 else:
547 if show_suffix:
547 if show_suffix:
548 return _(u'${ago} ago', mapping={'ago': _val})
548 return _(u'${ago} ago', mapping={'ago': _val})
549 else:
549 else:
550 return _(_val)
550 return _(_val)
551
551
552 val = fmt_funcs[part](value)
552 val = fmt_funcs[part](value)
553 val_detail = fmt_funcs[sub_part](sub_value)
553 val_detail = fmt_funcs[sub_part](sub_value)
554 mapping = {'val': val, 'detail': val_detail}
554 mapping = {'val': val, 'detail': val_detail}
555
555
556 if short_format:
556 if short_format:
557 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
557 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
558 if show_suffix:
558 if show_suffix:
559 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
559 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
560 if future:
560 if future:
561 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
561 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
562 else:
562 else:
563 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
563 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
564 if show_suffix:
564 if show_suffix:
565 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
565 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
566 if future:
566 if future:
567 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
567 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
568
568
569 return datetime_tmpl
569 return datetime_tmpl
570 i += 1
570 i += 1
571 return _(u'just now')
571 return _(u'just now')
572
572
573
573
574 def age_from_seconds(seconds):
574 def age_from_seconds(seconds):
575 seconds = safe_int(seconds) or 0
575 seconds = safe_int(seconds) or 0
576 prevdate = time_to_datetime(time.time() + seconds)
576 prevdate = time_to_datetime(time.time() + seconds)
577 return age(prevdate, show_suffix=False, show_short_version=True)
577 return age(prevdate, show_suffix=False, show_short_version=True)
578
578
579
579
580 def cleaned_uri(uri):
580 def cleaned_uri(uri):
581 """
581 """
582 Quotes '[' and ']' from uri if there is only one of them.
582 Quotes '[' and ']' from uri if there is only one of them.
583 according to RFC3986 we cannot use such chars in uri
583 according to RFC3986 we cannot use such chars in uri
584 :param uri:
584 :param uri:
585 :return: uri without this chars
585 :return: uri without this chars
586 """
586 """
587 return urllib.quote(uri, safe='@$:/')
587 return urllib.quote(uri, safe='@$:/')
588
588
589
589
590 def credentials_filter(uri):
590 def credentials_filter(uri):
591 """
591 """
592 Returns a url with removed credentials
592 Returns a url with removed credentials
593
593
594 :param uri:
594 :param uri:
595 """
595 """
596 import urlobject
596 import urlobject
597 if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue):
598 return 'InvalidDecryptionKey'
599
597 url_obj = urlobject.URLObject(cleaned_uri(uri))
600 url_obj = urlobject.URLObject(cleaned_uri(uri))
598 url_obj = url_obj.without_password().without_username()
601 url_obj = url_obj.without_password().without_username()
599
602
600 return url_obj
603 return url_obj
601
604
602
605
603 def get_host_info(request):
606 def get_host_info(request):
604 """
607 """
605 Generate host info, to obtain full url e.g https://server.com
608 Generate host info, to obtain full url e.g https://server.com
606 use this
609 use this
607 `{scheme}://{netloc}`
610 `{scheme}://{netloc}`
608 """
611 """
609 if not request:
612 if not request:
610 return {}
613 return {}
611
614
612 qualified_home_url = request.route_url('home')
615 qualified_home_url = request.route_url('home')
613 parsed_url = urlobject.URLObject(qualified_home_url)
616 parsed_url = urlobject.URLObject(qualified_home_url)
614 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
617 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
615
618
616 return {
619 return {
617 'scheme': parsed_url.scheme,
620 'scheme': parsed_url.scheme,
618 'netloc': parsed_url.netloc+decoded_path,
621 'netloc': parsed_url.netloc+decoded_path,
619 'hostname': parsed_url.hostname,
622 'hostname': parsed_url.hostname,
620 }
623 }
621
624
622
625
623 def get_clone_url(request, uri_tmpl, repo_name, repo_id, repo_type, **override):
626 def get_clone_url(request, uri_tmpl, repo_name, repo_id, repo_type, **override):
624 qualified_home_url = request.route_url('home')
627 qualified_home_url = request.route_url('home')
625 parsed_url = urlobject.URLObject(qualified_home_url)
628 parsed_url = urlobject.URLObject(qualified_home_url)
626 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
629 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
627
630
628 args = {
631 args = {
629 'scheme': parsed_url.scheme,
632 'scheme': parsed_url.scheme,
630 'user': '',
633 'user': '',
631 'sys_user': getpass.getuser(),
634 'sys_user': getpass.getuser(),
632 # path if we use proxy-prefix
635 # path if we use proxy-prefix
633 'netloc': parsed_url.netloc+decoded_path,
636 'netloc': parsed_url.netloc+decoded_path,
634 'hostname': parsed_url.hostname,
637 'hostname': parsed_url.hostname,
635 'prefix': decoded_path,
638 'prefix': decoded_path,
636 'repo': repo_name,
639 'repo': repo_name,
637 'repoid': str(repo_id),
640 'repoid': str(repo_id),
638 'repo_type': repo_type
641 'repo_type': repo_type
639 }
642 }
640 args.update(override)
643 args.update(override)
641 args['user'] = urllib.quote(safe_str(args['user']))
644 args['user'] = urllib.quote(safe_str(args['user']))
642
645
643 for k, v in args.items():
646 for k, v in args.items():
644 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
647 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
645
648
646 # special case for SVN clone url
649 # special case for SVN clone url
647 if repo_type == 'svn':
650 if repo_type == 'svn':
648 uri_tmpl = uri_tmpl.replace('ssh://', 'svn+ssh://')
651 uri_tmpl = uri_tmpl.replace('ssh://', 'svn+ssh://')
649
652
650 # remove leading @ sign if it's present. Case of empty user
653 # remove leading @ sign if it's present. Case of empty user
651 url_obj = urlobject.URLObject(uri_tmpl)
654 url_obj = urlobject.URLObject(uri_tmpl)
652 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
655 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
653
656
654 return safe_unicode(url)
657 return safe_unicode(url)
655
658
656
659
657 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
660 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
658 maybe_unreachable=False, reference_obj=None):
661 maybe_unreachable=False, reference_obj=None):
659 """
662 """
660 Safe version of get_commit if this commit doesn't exists for a
663 Safe version of get_commit if this commit doesn't exists for a
661 repository it returns a Dummy one instead
664 repository it returns a Dummy one instead
662
665
663 :param repo: repository instance
666 :param repo: repository instance
664 :param commit_id: commit id as str
667 :param commit_id: commit id as str
665 :param commit_idx: numeric commit index
668 :param commit_idx: numeric commit index
666 :param pre_load: optional list of commit attributes to load
669 :param pre_load: optional list of commit attributes to load
667 :param maybe_unreachable: translate unreachable commits on git repos
670 :param maybe_unreachable: translate unreachable commits on git repos
668 :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123"
671 :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123"
669 """
672 """
670 # TODO(skreft): remove these circular imports
673 # TODO(skreft): remove these circular imports
671 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
674 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
672 from rhodecode.lib.vcs.exceptions import RepositoryError
675 from rhodecode.lib.vcs.exceptions import RepositoryError
673 if not isinstance(repo, BaseRepository):
676 if not isinstance(repo, BaseRepository):
674 raise Exception('You must pass an Repository '
677 raise Exception('You must pass an Repository '
675 'object as first argument got %s', type(repo))
678 'object as first argument got %s', type(repo))
676
679
677 try:
680 try:
678 commit = repo.get_commit(
681 commit = repo.get_commit(
679 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
682 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
680 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
683 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
681 except (RepositoryError, LookupError):
684 except (RepositoryError, LookupError):
682 commit = EmptyCommit()
685 commit = EmptyCommit()
683 return commit
686 return commit
684
687
685
688
686 def datetime_to_time(dt):
689 def datetime_to_time(dt):
687 if dt:
690 if dt:
688 return time.mktime(dt.timetuple())
691 return time.mktime(dt.timetuple())
689
692
690
693
691 def time_to_datetime(tm):
694 def time_to_datetime(tm):
692 if tm:
695 if tm:
693 if isinstance(tm, compat.string_types):
696 if isinstance(tm, compat.string_types):
694 try:
697 try:
695 tm = float(tm)
698 tm = float(tm)
696 except ValueError:
699 except ValueError:
697 return
700 return
698 return datetime.datetime.fromtimestamp(tm)
701 return datetime.datetime.fromtimestamp(tm)
699
702
700
703
701 def time_to_utcdatetime(tm):
704 def time_to_utcdatetime(tm):
702 if tm:
705 if tm:
703 if isinstance(tm, compat.string_types):
706 if isinstance(tm, compat.string_types):
704 try:
707 try:
705 tm = float(tm)
708 tm = float(tm)
706 except ValueError:
709 except ValueError:
707 return
710 return
708 return datetime.datetime.utcfromtimestamp(tm)
711 return datetime.datetime.utcfromtimestamp(tm)
709
712
710
713
711 MENTIONS_REGEX = re.compile(
714 MENTIONS_REGEX = re.compile(
712 # ^@ or @ without any special chars in front
715 # ^@ or @ without any special chars in front
713 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
716 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
714 # main body starts with letter, then can be . - _
717 # main body starts with letter, then can be . - _
715 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
718 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
716 re.VERBOSE | re.MULTILINE)
719 re.VERBOSE | re.MULTILINE)
717
720
718
721
719 def extract_mentioned_users(s):
722 def extract_mentioned_users(s):
720 """
723 """
721 Returns unique usernames from given string s that have @mention
724 Returns unique usernames from given string s that have @mention
722
725
723 :param s: string to get mentions
726 :param s: string to get mentions
724 """
727 """
725 usrs = set()
728 usrs = set()
726 for username in MENTIONS_REGEX.findall(s):
729 for username in MENTIONS_REGEX.findall(s):
727 usrs.add(username)
730 usrs.add(username)
728
731
729 return sorted(list(usrs), key=lambda k: k.lower())
732 return sorted(list(usrs), key=lambda k: k.lower())
730
733
731
734
732 class AttributeDictBase(dict):
735 class AttributeDictBase(dict):
733 def __getstate__(self):
736 def __getstate__(self):
734 odict = self.__dict__ # get attribute dictionary
737 odict = self.__dict__ # get attribute dictionary
735 return odict
738 return odict
736
739
737 def __setstate__(self, dict):
740 def __setstate__(self, dict):
738 self.__dict__ = dict
741 self.__dict__ = dict
739
742
740 __setattr__ = dict.__setitem__
743 __setattr__ = dict.__setitem__
741 __delattr__ = dict.__delitem__
744 __delattr__ = dict.__delitem__
742
745
743
746
744 class StrictAttributeDict(AttributeDictBase):
747 class StrictAttributeDict(AttributeDictBase):
745 """
748 """
746 Strict Version of Attribute dict which raises an Attribute error when
749 Strict Version of Attribute dict which raises an Attribute error when
747 requested attribute is not set
750 requested attribute is not set
748 """
751 """
749 def __getattr__(self, attr):
752 def __getattr__(self, attr):
750 try:
753 try:
751 return self[attr]
754 return self[attr]
752 except KeyError:
755 except KeyError:
753 raise AttributeError('%s object has no attribute %s' % (
756 raise AttributeError('%s object has no attribute %s' % (
754 self.__class__, attr))
757 self.__class__, attr))
755
758
756
759
757 class AttributeDict(AttributeDictBase):
760 class AttributeDict(AttributeDictBase):
758 def __getattr__(self, attr):
761 def __getattr__(self, attr):
759 return self.get(attr, None)
762 return self.get(attr, None)
760
763
761
764
762
765
763 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
766 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
764 def __init__(self, default_factory=None, *args, **kwargs):
767 def __init__(self, default_factory=None, *args, **kwargs):
765 # in python3 you can omit the args to super
768 # in python3 you can omit the args to super
766 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
769 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
767 self.default_factory = default_factory
770 self.default_factory = default_factory
768
771
769
772
770 def fix_PATH(os_=None):
773 def fix_PATH(os_=None):
771 """
774 """
772 Get current active python path, and append it to PATH variable to fix
775 Get current active python path, and append it to PATH variable to fix
773 issues of subprocess calls and different python versions
776 issues of subprocess calls and different python versions
774 """
777 """
775 if os_ is None:
778 if os_ is None:
776 import os
779 import os
777 else:
780 else:
778 os = os_
781 os = os_
779
782
780 cur_path = os.path.split(sys.executable)[0]
783 cur_path = os.path.split(sys.executable)[0]
781 if not os.environ['PATH'].startswith(cur_path):
784 if not os.environ['PATH'].startswith(cur_path):
782 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
785 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
783
786
784
787
785 def obfuscate_url_pw(engine):
788 def obfuscate_url_pw(engine):
786 _url = engine or ''
789 _url = engine or ''
787 try:
790 try:
788 _url = sqlalchemy.engine.url.make_url(engine)
791 _url = sqlalchemy.engine.url.make_url(engine)
789 if _url.password:
792 if _url.password:
790 _url.password = 'XXXXX'
793 _url.password = 'XXXXX'
791 except Exception:
794 except Exception:
792 pass
795 pass
793 return unicode(_url)
796 return unicode(_url)
794
797
795
798
796 def get_server_url(environ):
799 def get_server_url(environ):
797 req = webob.Request(environ)
800 req = webob.Request(environ)
798 return req.host_url + req.script_name
801 return req.host_url + req.script_name
799
802
800
803
801 def unique_id(hexlen=32):
804 def unique_id(hexlen=32):
802 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
805 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
803 return suuid(truncate_to=hexlen, alphabet=alphabet)
806 return suuid(truncate_to=hexlen, alphabet=alphabet)
804
807
805
808
806 def suuid(url=None, truncate_to=22, alphabet=None):
809 def suuid(url=None, truncate_to=22, alphabet=None):
807 """
810 """
808 Generate and return a short URL safe UUID.
811 Generate and return a short URL safe UUID.
809
812
810 If the url parameter is provided, set the namespace to the provided
813 If the url parameter is provided, set the namespace to the provided
811 URL and generate a UUID.
814 URL and generate a UUID.
812
815
813 :param url to get the uuid for
816 :param url to get the uuid for
814 :truncate_to: truncate the basic 22 UUID to shorter version
817 :truncate_to: truncate the basic 22 UUID to shorter version
815
818
816 The IDs won't be universally unique any longer, but the probability of
819 The IDs won't be universally unique any longer, but the probability of
817 a collision will still be very low.
820 a collision will still be very low.
818 """
821 """
819 # Define our alphabet.
822 # Define our alphabet.
820 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
823 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
821
824
822 # If no URL is given, generate a random UUID.
825 # If no URL is given, generate a random UUID.
823 if url is None:
826 if url is None:
824 unique_id = uuid.uuid4().int
827 unique_id = uuid.uuid4().int
825 else:
828 else:
826 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
829 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
827
830
828 alphabet_length = len(_ALPHABET)
831 alphabet_length = len(_ALPHABET)
829 output = []
832 output = []
830 while unique_id > 0:
833 while unique_id > 0:
831 digit = unique_id % alphabet_length
834 digit = unique_id % alphabet_length
832 output.append(_ALPHABET[digit])
835 output.append(_ALPHABET[digit])
833 unique_id = int(unique_id / alphabet_length)
836 unique_id = int(unique_id / alphabet_length)
834 return "".join(output)[:truncate_to]
837 return "".join(output)[:truncate_to]
835
838
836
839
837 def get_current_rhodecode_user(request=None):
840 def get_current_rhodecode_user(request=None):
838 """
841 """
839 Gets rhodecode user from request
842 Gets rhodecode user from request
840 """
843 """
841 pyramid_request = request or pyramid.threadlocal.get_current_request()
844 pyramid_request = request or pyramid.threadlocal.get_current_request()
842
845
843 # web case
846 # web case
844 if pyramid_request and hasattr(pyramid_request, 'user'):
847 if pyramid_request and hasattr(pyramid_request, 'user'):
845 return pyramid_request.user
848 return pyramid_request.user
846
849
847 # api case
850 # api case
848 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
851 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
849 return pyramid_request.rpc_user
852 return pyramid_request.rpc_user
850
853
851 return None
854 return None
852
855
853
856
854 def action_logger_generic(action, namespace=''):
857 def action_logger_generic(action, namespace=''):
855 """
858 """
856 A generic logger for actions useful to the system overview, tries to find
859 A generic logger for actions useful to the system overview, tries to find
857 an acting user for the context of the call otherwise reports unknown user
860 an acting user for the context of the call otherwise reports unknown user
858
861
859 :param action: logging message eg 'comment 5 deleted'
862 :param action: logging message eg 'comment 5 deleted'
860 :param type: string
863 :param type: string
861
864
862 :param namespace: namespace of the logging message eg. 'repo.comments'
865 :param namespace: namespace of the logging message eg. 'repo.comments'
863 :param type: string
866 :param type: string
864
867
865 """
868 """
866
869
867 logger_name = 'rhodecode.actions'
870 logger_name = 'rhodecode.actions'
868
871
869 if namespace:
872 if namespace:
870 logger_name += '.' + namespace
873 logger_name += '.' + namespace
871
874
872 log = logging.getLogger(logger_name)
875 log = logging.getLogger(logger_name)
873
876
874 # get a user if we can
877 # get a user if we can
875 user = get_current_rhodecode_user()
878 user = get_current_rhodecode_user()
876
879
877 logfunc = log.info
880 logfunc = log.info
878
881
879 if not user:
882 if not user:
880 user = '<unknown user>'
883 user = '<unknown user>'
881 logfunc = log.warning
884 logfunc = log.warning
882
885
883 logfunc('Logging action by {}: {}'.format(user, action))
886 logfunc('Logging action by {}: {}'.format(user, action))
884
887
885
888
886 def escape_split(text, sep=',', maxsplit=-1):
889 def escape_split(text, sep=',', maxsplit=-1):
887 r"""
890 r"""
888 Allows for escaping of the separator: e.g. arg='foo\, bar'
891 Allows for escaping of the separator: e.g. arg='foo\, bar'
889
892
890 It should be noted that the way bash et. al. do command line parsing, those
893 It should be noted that the way bash et. al. do command line parsing, those
891 single quotes are required.
894 single quotes are required.
892 """
895 """
893 escaped_sep = r'\%s' % sep
896 escaped_sep = r'\%s' % sep
894
897
895 if escaped_sep not in text:
898 if escaped_sep not in text:
896 return text.split(sep, maxsplit)
899 return text.split(sep, maxsplit)
897
900
898 before, _mid, after = text.partition(escaped_sep)
901 before, _mid, after = text.partition(escaped_sep)
899 startlist = before.split(sep, maxsplit) # a regular split is fine here
902 startlist = before.split(sep, maxsplit) # a regular split is fine here
900 unfinished = startlist[-1]
903 unfinished = startlist[-1]
901 startlist = startlist[:-1]
904 startlist = startlist[:-1]
902
905
903 # recurse because there may be more escaped separators
906 # recurse because there may be more escaped separators
904 endlist = escape_split(after, sep, maxsplit)
907 endlist = escape_split(after, sep, maxsplit)
905
908
906 # finish building the escaped value. we use endlist[0] becaue the first
909 # finish building the escaped value. we use endlist[0] becaue the first
907 # part of the string sent in recursion is the rest of the escaped value.
910 # part of the string sent in recursion is the rest of the escaped value.
908 unfinished += sep + endlist[0]
911 unfinished += sep + endlist[0]
909
912
910 return startlist + [unfinished] + endlist[1:] # put together all the parts
913 return startlist + [unfinished] + endlist[1:] # put together all the parts
911
914
912
915
913 class OptionalAttr(object):
916 class OptionalAttr(object):
914 """
917 """
915 Special Optional Option that defines other attribute. Example::
918 Special Optional Option that defines other attribute. Example::
916
919
917 def test(apiuser, userid=Optional(OAttr('apiuser')):
920 def test(apiuser, userid=Optional(OAttr('apiuser')):
918 user = Optional.extract(userid)
921 user = Optional.extract(userid)
919 # calls
922 # calls
920
923
921 """
924 """
922
925
923 def __init__(self, attr_name):
926 def __init__(self, attr_name):
924 self.attr_name = attr_name
927 self.attr_name = attr_name
925
928
926 def __repr__(self):
929 def __repr__(self):
927 return '<OptionalAttr:%s>' % self.attr_name
930 return '<OptionalAttr:%s>' % self.attr_name
928
931
929 def __call__(self):
932 def __call__(self):
930 return self
933 return self
931
934
932
935
933 # alias
936 # alias
934 OAttr = OptionalAttr
937 OAttr = OptionalAttr
935
938
936
939
937 class Optional(object):
940 class Optional(object):
938 """
941 """
939 Defines an optional parameter::
942 Defines an optional parameter::
940
943
941 param = param.getval() if isinstance(param, Optional) else param
944 param = param.getval() if isinstance(param, Optional) else param
942 param = param() if isinstance(param, Optional) else param
945 param = param() if isinstance(param, Optional) else param
943
946
944 is equivalent of::
947 is equivalent of::
945
948
946 param = Optional.extract(param)
949 param = Optional.extract(param)
947
950
948 """
951 """
949
952
950 def __init__(self, type_):
953 def __init__(self, type_):
951 self.type_ = type_
954 self.type_ = type_
952
955
953 def __repr__(self):
956 def __repr__(self):
954 return '<Optional:%s>' % self.type_.__repr__()
957 return '<Optional:%s>' % self.type_.__repr__()
955
958
956 def __call__(self):
959 def __call__(self):
957 return self.getval()
960 return self.getval()
958
961
959 def getval(self):
962 def getval(self):
960 """
963 """
961 returns value from this Optional instance
964 returns value from this Optional instance
962 """
965 """
963 if isinstance(self.type_, OAttr):
966 if isinstance(self.type_, OAttr):
964 # use params name
967 # use params name
965 return self.type_.attr_name
968 return self.type_.attr_name
966 return self.type_
969 return self.type_
967
970
968 @classmethod
971 @classmethod
969 def extract(cls, val):
972 def extract(cls, val):
970 """
973 """
971 Extracts value from Optional() instance
974 Extracts value from Optional() instance
972
975
973 :param val:
976 :param val:
974 :return: original value if it's not Optional instance else
977 :return: original value if it's not Optional instance else
975 value of instance
978 value of instance
976 """
979 """
977 if isinstance(val, cls):
980 if isinstance(val, cls):
978 return val.getval()
981 return val.getval()
979 return val
982 return val
980
983
981
984
982 def glob2re(pat):
985 def glob2re(pat):
983 """
986 """
984 Translate a shell PATTERN to a regular expression.
987 Translate a shell PATTERN to a regular expression.
985
988
986 There is no way to quote meta-characters.
989 There is no way to quote meta-characters.
987 """
990 """
988
991
989 i, n = 0, len(pat)
992 i, n = 0, len(pat)
990 res = ''
993 res = ''
991 while i < n:
994 while i < n:
992 c = pat[i]
995 c = pat[i]
993 i = i+1
996 i = i+1
994 if c == '*':
997 if c == '*':
995 #res = res + '.*'
998 #res = res + '.*'
996 res = res + '[^/]*'
999 res = res + '[^/]*'
997 elif c == '?':
1000 elif c == '?':
998 #res = res + '.'
1001 #res = res + '.'
999 res = res + '[^/]'
1002 res = res + '[^/]'
1000 elif c == '[':
1003 elif c == '[':
1001 j = i
1004 j = i
1002 if j < n and pat[j] == '!':
1005 if j < n and pat[j] == '!':
1003 j = j+1
1006 j = j+1
1004 if j < n and pat[j] == ']':
1007 if j < n and pat[j] == ']':
1005 j = j+1
1008 j = j+1
1006 while j < n and pat[j] != ']':
1009 while j < n and pat[j] != ']':
1007 j = j+1
1010 j = j+1
1008 if j >= n:
1011 if j >= n:
1009 res = res + '\\['
1012 res = res + '\\['
1010 else:
1013 else:
1011 stuff = pat[i:j].replace('\\','\\\\')
1014 stuff = pat[i:j].replace('\\','\\\\')
1012 i = j+1
1015 i = j+1
1013 if stuff[0] == '!':
1016 if stuff[0] == '!':
1014 stuff = '^' + stuff[1:]
1017 stuff = '^' + stuff[1:]
1015 elif stuff[0] == '^':
1018 elif stuff[0] == '^':
1016 stuff = '\\' + stuff
1019 stuff = '\\' + stuff
1017 res = '%s[%s]' % (res, stuff)
1020 res = '%s[%s]' % (res, stuff)
1018 else:
1021 else:
1019 res = res + re.escape(c)
1022 res = res + re.escape(c)
1020 return res + '\Z(?ms)'
1023 return res + '\Z(?ms)'
1021
1024
1022
1025
1023 def parse_byte_string(size_str):
1026 def parse_byte_string(size_str):
1024 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1027 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1025 if not match:
1028 if not match:
1026 raise ValueError('Given size:%s is invalid, please make sure '
1029 raise ValueError('Given size:%s is invalid, please make sure '
1027 'to use format of <num>(MB|KB)' % size_str)
1030 'to use format of <num>(MB|KB)' % size_str)
1028
1031
1029 _parts = match.groups()
1032 _parts = match.groups()
1030 num, type_ = _parts
1033 num, type_ = _parts
1031 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1034 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1032
1035
1033
1036
1034 class CachedProperty(object):
1037 class CachedProperty(object):
1035 """
1038 """
1036 Lazy Attributes. With option to invalidate the cache by running a method
1039 Lazy Attributes. With option to invalidate the cache by running a method
1037
1040
1038 class Foo():
1041 class Foo():
1039
1042
1040 @CachedProperty
1043 @CachedProperty
1041 def heavy_func():
1044 def heavy_func():
1042 return 'super-calculation'
1045 return 'super-calculation'
1043
1046
1044 foo = Foo()
1047 foo = Foo()
1045 foo.heavy_func() # first computions
1048 foo.heavy_func() # first computions
1046 foo.heavy_func() # fetch from cache
1049 foo.heavy_func() # fetch from cache
1047 foo._invalidate_prop_cache('heavy_func')
1050 foo._invalidate_prop_cache('heavy_func')
1048 # at this point calling foo.heavy_func() will be re-computed
1051 # at this point calling foo.heavy_func() will be re-computed
1049 """
1052 """
1050
1053
1051 def __init__(self, func, func_name=None):
1054 def __init__(self, func, func_name=None):
1052
1055
1053 if func_name is None:
1056 if func_name is None:
1054 func_name = func.__name__
1057 func_name = func.__name__
1055 self.data = (func, func_name)
1058 self.data = (func, func_name)
1056 update_wrapper(self, func)
1059 update_wrapper(self, func)
1057
1060
1058 def __get__(self, inst, class_):
1061 def __get__(self, inst, class_):
1059 if inst is None:
1062 if inst is None:
1060 return self
1063 return self
1061
1064
1062 func, func_name = self.data
1065 func, func_name = self.data
1063 value = func(inst)
1066 value = func(inst)
1064 inst.__dict__[func_name] = value
1067 inst.__dict__[func_name] = value
1065 if '_invalidate_prop_cache' not in inst.__dict__:
1068 if '_invalidate_prop_cache' not in inst.__dict__:
1066 inst.__dict__['_invalidate_prop_cache'] = partial(
1069 inst.__dict__['_invalidate_prop_cache'] = partial(
1067 self._invalidate_prop_cache, inst)
1070 self._invalidate_prop_cache, inst)
1068 return value
1071 return value
1069
1072
1070 def _invalidate_prop_cache(self, inst, name):
1073 def _invalidate_prop_cache(self, inst, name):
1071 inst.__dict__.pop(name, None)
1074 inst.__dict__.pop(name, None)
General Comments 0
You need to be logged in to leave comments. Login now