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