##// END OF EJS Templates
fixed typo
marcink -
r4014:7c0eff86 default
parent child Browse files
Show More
@@ -1,655 +1,655 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import sys
29 29 import time
30 30 import uuid
31 31 import datetime
32 32 import traceback
33 33 import webob
34 34
35 35 from pylons.i18n.translation import _, ungettext
36 36 from rhodecode.lib.vcs.utils.lazy import LazyProperty
37 37 from rhodecode.lib.compat import json
38 38
39 39
40 40 def __get_lem():
41 41 """
42 42 Get language extension map based on what's inside pygments lexers
43 43 """
44 44 from pygments import lexers
45 45 from string import lower
46 46 from collections import defaultdict
47 47
48 48 d = defaultdict(lambda: [])
49 49
50 50 def __clean(s):
51 51 s = s.lstrip('*')
52 52 s = s.lstrip('.')
53 53
54 54 if s.find('[') != -1:
55 55 exts = []
56 56 start, stop = s.find('['), s.find(']')
57 57
58 58 for suffix in s[start + 1:stop]:
59 59 exts.append(s[:s.find('[')] + suffix)
60 60 return map(lower, exts)
61 61 else:
62 62 return map(lower, [s])
63 63
64 64 for lx, t in sorted(lexers.LEXERS.items()):
65 65 m = map(__clean, t[-2])
66 66 if m:
67 67 m = reduce(lambda x, y: x + y, m)
68 68 for ext in m:
69 69 desc = lx.replace('Lexer', '')
70 70 d[ext].append(desc)
71 71
72 72 return dict(d)
73 73
74 74
75 75 def str2bool(_str):
76 76 """
77 77 returs True/False value from given string, it tries to translate the
78 78 string into boolean
79 79
80 80 :param _str: string value to translate into boolean
81 81 :rtype: boolean
82 82 :returns: boolean from given string
83 83 """
84 84 if _str is None:
85 85 return False
86 86 if _str in (True, False):
87 87 return _str
88 88 _str = str(_str).strip().lower()
89 89 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
90 90
91 91
92 92 def aslist(obj, sep=None, strip=True):
93 93 """
94 94 Returns given string separated by sep as list
95 95
96 96 :param obj:
97 97 :param sep:
98 98 :param strip:
99 99 """
100 100 if isinstance(obj, (basestring)):
101 101 lst = obj.split(sep)
102 102 if strip:
103 103 lst = [v.strip() for v in lst]
104 104 return lst
105 105 elif isinstance(obj, (list, tuple)):
106 106 return obj
107 107 elif obj is None:
108 108 return []
109 109 else:
110 110 return [obj]
111 111
112 112
113 113 def convert_line_endings(line, mode):
114 114 """
115 115 Converts a given line "line end" accordingly to given mode
116 116
117 117 Available modes are::
118 118 0 - Unix
119 119 1 - Mac
120 120 2 - DOS
121 121
122 122 :param line: given line to convert
123 123 :param mode: mode to convert to
124 124 :rtype: str
125 125 :return: converted line according to mode
126 126 """
127 127 from string import replace
128 128
129 129 if mode == 0:
130 130 line = replace(line, '\r\n', '\n')
131 131 line = replace(line, '\r', '\n')
132 132 elif mode == 1:
133 133 line = replace(line, '\r\n', '\r')
134 134 line = replace(line, '\n', '\r')
135 135 elif mode == 2:
136 136 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
137 137 return line
138 138
139 139
140 140 def detect_mode(line, default):
141 141 """
142 142 Detects line break for given line, if line break couldn't be found
143 143 given default value is returned
144 144
145 145 :param line: str line
146 146 :param default: default
147 147 :rtype: int
148 148 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
149 149 """
150 150 if line.endswith('\r\n'):
151 151 return 2
152 152 elif line.endswith('\n'):
153 153 return 0
154 154 elif line.endswith('\r'):
155 155 return 1
156 156 else:
157 157 return default
158 158
159 159
160 160 def generate_api_key(username, salt=None):
161 161 """
162 162 Generates unique API key for given username, if salt is not given
163 163 it'll be generated from some random string
164 164
165 165 :param username: username as string
166 166 :param salt: salt to hash generate KEY
167 167 :rtype: str
168 168 :returns: sha1 hash from username+salt
169 169 """
170 170 from tempfile import _RandomNameSequence
171 171 import hashlib
172 172
173 173 if salt is None:
174 174 salt = _RandomNameSequence().next()
175 175
176 176 return hashlib.sha1(username + salt).hexdigest()
177 177
178 178
179 179 def safe_int(val, default=None):
180 180 """
181 181 Returns int() of val if val is not convertable to int use default
182 182 instead
183 183
184 184 :param val:
185 185 :param default:
186 186 """
187 187
188 188 try:
189 189 val = int(val)
190 190 except (ValueError, TypeError):
191 191 val = default
192 192
193 193 return val
194 194
195 195
196 196 def safe_unicode(str_, from_encoding=None):
197 197 """
198 198 safe unicode function. Does few trick to turn str_ into unicode
199 199
200 200 In case of UnicodeDecode error we try to return it with encoding detected
201 201 by chardet library if it fails fallback to unicode with errors replaced
202 202
203 203 :param str_: string to decode
204 204 :rtype: unicode
205 205 :returns: unicode object
206 206 """
207 207 if isinstance(str_, unicode):
208 208 return str_
209 209
210 210 if not from_encoding:
211 211 import rhodecode
212 212 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
213 213 'utf8'), sep=',')
214 214 from_encoding = DEFAULT_ENCODINGS
215 215
216 216 if not isinstance(from_encoding, (list, tuple)):
217 217 from_encoding = [from_encoding]
218 218
219 219 try:
220 220 return unicode(str_)
221 221 except UnicodeDecodeError:
222 222 pass
223 223
224 224 for enc in from_encoding:
225 225 try:
226 226 return unicode(str_, enc)
227 227 except UnicodeDecodeError:
228 228 pass
229 229
230 230 try:
231 231 import chardet
232 232 encoding = chardet.detect(str_)['encoding']
233 233 if encoding is None:
234 234 raise Exception()
235 235 return str_.decode(encoding)
236 236 except (ImportError, UnicodeDecodeError, Exception):
237 237 return unicode(str_, from_encoding[0], 'replace')
238 238
239 239
240 240 def safe_str(unicode_, to_encoding=None):
241 241 """
242 242 safe str function. Does few trick to turn unicode_ into string
243 243
244 244 In case of UnicodeEncodeError we try to return it with encoding detected
245 245 by chardet library if it fails fallback to string with errors replaced
246 246
247 247 :param unicode_: unicode to encode
248 248 :rtype: str
249 249 :returns: str object
250 250 """
251 251
252 252 # if it's not basestr cast to str
253 253 if not isinstance(unicode_, basestring):
254 254 return str(unicode_)
255 255
256 256 if isinstance(unicode_, str):
257 257 return unicode_
258 258
259 259 if not to_encoding:
260 260 import rhodecode
261 261 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
262 262 'utf8'), sep=',')
263 263 to_encoding = DEFAULT_ENCODINGS
264 264
265 265 if not isinstance(to_encoding, (list, tuple)):
266 266 to_encoding = [to_encoding]
267 267
268 268 for enc in to_encoding:
269 269 try:
270 270 return unicode_.encode(enc)
271 271 except UnicodeEncodeError:
272 272 pass
273 273
274 274 try:
275 275 import chardet
276 276 encoding = chardet.detect(unicode_)['encoding']
277 277 if encoding is None:
278 278 raise UnicodeEncodeError()
279 279
280 280 return unicode_.encode(encoding)
281 281 except (ImportError, UnicodeEncodeError):
282 282 return unicode_.encode(to_encoding[0], 'replace')
283 283
284 284
285 285 def remove_suffix(s, suffix):
286 286 if s.endswith(suffix):
287 287 s = s[:-1 * len(suffix)]
288 288 return s
289 289
290 290
291 291 def remove_prefix(s, prefix):
292 292 if s.startswith(prefix):
293 293 s = s[len(prefix):]
294 294 return s
295 295
296 296
297 297 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
298 298 """
299 299 Custom engine_from_config functions that makes sure we use NullPool for
300 300 file based sqlite databases. This prevents errors on sqlite. This only
301 301 applies to sqlalchemy versions < 0.7.0
302 302
303 303 """
304 304 import sqlalchemy
305 305 from sqlalchemy import engine_from_config as efc
306 306 import logging
307 307
308 308 if int(sqlalchemy.__version__.split('.')[1]) < 7:
309 309
310 310 # This solution should work for sqlalchemy < 0.7.0, and should use
311 311 # proxy=TimerProxy() for execution time profiling
312 312
313 313 from sqlalchemy.pool import NullPool
314 314 url = configuration[prefix + 'url']
315 315
316 316 if url.startswith('sqlite'):
317 317 kwargs.update({'poolclass': NullPool})
318 318 return efc(configuration, prefix, **kwargs)
319 319 else:
320 320 import time
321 321 from sqlalchemy import event
322 322 from sqlalchemy.engine import Engine
323 323
324 324 log = logging.getLogger('sqlalchemy.engine')
325 325 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
326 326 engine = efc(configuration, prefix, **kwargs)
327 327
328 328 def color_sql(sql):
329 329 COLOR_SEQ = "\033[1;%dm"
330 330 COLOR_SQL = YELLOW
331 331 normal = '\x1b[0m'
332 332 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
333 333
334 334 if configuration['debug']:
335 335 #attach events only for debug configuration
336 336
337 337 def before_cursor_execute(conn, cursor, statement,
338 338 parameters, context, executemany):
339 339 context._query_start_time = time.time()
340 340 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
341 341
342 342 def after_cursor_execute(conn, cursor, statement,
343 343 parameters, context, executemany):
344 344 total = time.time() - context._query_start_time
345 345 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
346 346
347 347 event.listen(engine, "before_cursor_execute",
348 348 before_cursor_execute)
349 349 event.listen(engine, "after_cursor_execute",
350 350 after_cursor_execute)
351 351
352 352 return engine
353 353
354 354
355 355 def age(prevdate, show_short_version=False, now=None):
356 356 """
357 357 turns a datetime into an age string.
358 358 If show_short_version is True, then it will generate a not so accurate but shorter string,
359 359 example: 2days ago, instead of 2 days and 23 hours ago.
360 360
361 361 :param prevdate: datetime object
362 362 :param show_short_version: if it should aproximate the date and return a shorter string
363 363 :rtype: unicode
364 364 :returns: unicode words describing age
365 365 """
366 366 now = now or datetime.datetime.now()
367 367 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
368 368 deltas = {}
369 369 future = False
370 370
371 371 if prevdate > now:
372 372 now, prevdate = prevdate, now
373 373 future = True
374 374 if future:
375 375 prevdate = prevdate.replace(microsecond=0)
376 376 # Get date parts deltas
377 377 from dateutil import relativedelta
378 378 for part in order:
379 379 d = relativedelta.relativedelta(now, prevdate)
380 380 deltas[part] = getattr(d, part + 's')
381 381
382 382 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
383 383 # not 1 hour, -59 minutes and -59 seconds)
384 384 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
385 385 part = order[num]
386 386 carry_part = order[num - 1]
387 387
388 388 if deltas[part] < 0:
389 389 deltas[part] += length
390 390 deltas[carry_part] -= 1
391 391
392 392 # Same thing for days except that the increment depends on the (variable)
393 393 # number of days in the month
394 394 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
395 395 if deltas['day'] < 0:
396 396 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
397 397 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
398 398 deltas['day'] += 29
399 399 else:
400 400 deltas['day'] += month_lengths[prevdate.month - 1]
401 401
402 402 deltas['month'] -= 1
403 403
404 404 if deltas['month'] < 0:
405 405 deltas['month'] += 12
406 406 deltas['year'] -= 1
407 407
408 408 # Format the result
409 409 fmt_funcs = {
410 410 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
411 411 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
412 412 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
413 413 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
414 414 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
415 415 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
416 416 }
417 417
418 418 for i, part in enumerate(order):
419 419 value = deltas[part]
420 420 if value == 0:
421 421 continue
422 422
423 423 if i < 5:
424 424 sub_part = order[i + 1]
425 425 sub_value = deltas[sub_part]
426 426 else:
427 427 sub_value = 0
428 428
429 429 if sub_value == 0 or show_short_version:
430 430 if future:
431 431 return _(u'in %s') % fmt_funcs[part](value)
432 432 else:
433 433 return _(u'%s ago') % fmt_funcs[part](value)
434 434 if future:
435 435 return _(u'in %s and %s') % (fmt_funcs[part](value),
436 436 fmt_funcs[sub_part](sub_value))
437 437 else:
438 438 return _(u'%s and %s ago') % (fmt_funcs[part](value),
439 439 fmt_funcs[sub_part](sub_value))
440 440
441 441 return _(u'just now')
442 442
443 443
444 444 def uri_filter(uri):
445 445 """
446 446 Removes user:password from given url string
447 447
448 448 :param uri:
449 449 :rtype: unicode
450 450 :returns: filtered list of strings
451 451 """
452 452 if not uri:
453 453 return ''
454 454
455 455 proto = ''
456 456
457 457 for pat in ('https://', 'http://'):
458 458 if uri.startswith(pat):
459 459 uri = uri[len(pat):]
460 460 proto = pat
461 461 break
462 462
463 463 # remove passwords and username
464 464 uri = uri[uri.find('@') + 1:]
465 465
466 466 # get the port
467 467 cred_pos = uri.find(':')
468 468 if cred_pos == -1:
469 469 host, port = uri, None
470 470 else:
471 471 host, port = uri[:cred_pos], uri[cred_pos + 1:]
472 472
473 473 return filter(None, [proto, host, port])
474 474
475 475
476 476 def credentials_filter(uri):
477 477 """
478 478 Returns a url with removed credentials
479 479
480 480 :param uri:
481 481 """
482 482
483 483 uri = uri_filter(uri)
484 484 #check if we have port
485 485 if len(uri) > 2 and uri[2]:
486 486 uri[2] = ':' + uri[2]
487 487
488 488 return ''.join(uri)
489 489
490 490
491 491 def get_changeset_safe(repo, rev):
492 492 """
493 493 Safe version of get_changeset if this changeset doesn't exists for a
494 494 repo it returns a Dummy one instead
495 495
496 496 :param repo:
497 497 :param rev:
498 498 """
499 499 from rhodecode.lib.vcs.backends.base import BaseRepository
500 500 from rhodecode.lib.vcs.exceptions import RepositoryError
501 501 from rhodecode.lib.vcs.backends.base import EmptyChangeset
502 502 if not isinstance(repo, BaseRepository):
503 503 raise Exception('You must pass an Repository '
504 504 'object as first argument got %s', type(repo))
505 505
506 506 try:
507 507 cs = repo.get_changeset(rev)
508 508 except RepositoryError:
509 509 cs = EmptyChangeset(requested_revision=rev)
510 510 return cs
511 511
512 512
513 513 def datetime_to_time(dt):
514 514 if dt:
515 515 return time.mktime(dt.timetuple())
516 516
517 517
518 518 def time_to_datetime(tm):
519 519 if tm:
520 520 if isinstance(tm, basestring):
521 521 try:
522 522 tm = float(tm)
523 523 except ValueError:
524 524 return
525 525 return datetime.datetime.fromtimestamp(tm)
526 526
527 527 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
528 528
529 529
530 530 def extract_mentioned_users(s):
531 531 """
532 532 Returns unique usernames from given string s that have @mention
533 533
534 534 :param s: string to get mentions
535 535 """
536 536 usrs = set()
537 537 for username in re.findall(MENTIONS_REGEX, s):
538 538 usrs.add(username)
539 539
540 540 return sorted(list(usrs), key=lambda k: k.lower())
541 541
542 542
543 543 class AttributeDict(dict):
544 544 def __getattr__(self, attr):
545 545 return self.get(attr, None)
546 546 __setattr__ = dict.__setitem__
547 547 __delattr__ = dict.__delitem__
548 548
549 549
550 550 def fix_PATH(os_=None):
551 551 """
552 552 Get current active python path, and append it to PATH variable to fix issues
553 553 of subprocess calls and different python versions
554 554 """
555 555 if os_ is None:
556 556 import os
557 557 else:
558 558 os = os_
559 559
560 560 cur_path = os.path.split(sys.executable)[0]
561 561 if not os.environ['PATH'].startswith(cur_path):
562 562 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
563 563
564 564
565 565 def obfuscate_url_pw(engine):
566 566 _url = engine or ''
567 567 from sqlalchemy.engine import url as sa_url
568 568 try:
569 569 _url = sa_url.make_url(engine)
570 570 if _url.password:
571 571 _url.password = 'XXXXX'
572 572 except Exception:
573 573 pass
574 574 return str(_url)
575 575
576 576
577 577 def get_server_url(environ):
578 578 req = webob.Request(environ)
579 579 return req.host_url + req.script_name
580 580
581 581
582 582 def _extract_extras(env=None):
583 583 """
584 584 Extracts the rc extras data from os.environ, and wraps it into named
585 585 AttributeDict object
586 586 """
587 587 if not env:
588 588 env = os.environ
589 589
590 590 try:
591 591 rc_extras = json.loads(env['RC_SCM_DATA'])
592 592 except Exception:
593 593 print os.environ
594 594 print >> sys.stderr, traceback.format_exc()
595 595 rc_extras = {}
596 596
597 597 try:
598 598 for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
599 599 'action', 'ip']:
600 600 rc_extras[k]
601 601 except KeyError, e:
602 602 raise Exception('Missing key %s in os.environ %s' % (e, rc_extras))
603 603
604 604 return AttributeDict(rc_extras)
605 605
606 606
607 607 def _set_extras(extras):
608 608 os.environ['RC_SCM_DATA'] = json.dumps(extras)
609 609
610 610
611 611 def unique_id(hexlen=32):
612 612 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
613 613 return suuid(truncate_to=hexlen, alphabet=alphabet)
614 614
615 615
616 616 def suuid(url=None, truncate_to=22, alphabet=None):
617 617 """
618 618 Generate and return a short URL safe UUID.
619 619
620 620 If the url parameter is provided, set the namespace to the provided
621 621 URL and generate a UUID.
622 622
623 623 :param url to get the uuid for
624 624 :truncate_to: truncate the basic 22 UUID to shorter version
625 625
626 626 The IDs won't be universally unique any longer, but the probability of
627 627 a collision will still be very low.
628 628 """
629 629 # Define our alphabet.
630 630 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
631 631
632 632 # If no URL is given, generate a random UUID.
633 633 if url is None:
634 634 unique_id = uuid.uuid4().int
635 635 else:
636 636 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
637 637
638 638 alphabet_length = len(_ALPHABET)
639 639 output = []
640 640 while unique_id > 0:
641 641 digit = unique_id % alphabet_length
642 642 output.append(_ALPHABET[digit])
643 643 unique_id = int(unique_id / alphabet_length)
644 644 return "".join(output)[:truncate_to]
645 645
646 646 def get_current_rhodecode_user():
647 647 """
648 Get's rhodecode user from threadlocal tmpl_context variable if it's
648 Gets rhodecode user from threadlocal tmpl_context variable if it's
649 649 defined, else returns None.
650 650 """
651 651 from pylons import tmpl_context
652 652 if hasattr(tmpl_context, 'rhodecode_user'):
653 653 return tmpl_context.rhodecode_user
654 654
655 655 return None
General Comments 0
You need to be logged in to leave comments. Login now