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