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