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