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