##// END OF EJS Templates
Rewrite of the age() utility function so it can be translated.
Vincent Duvert -
r2303:7090e394 beta
parent child Browse files
Show More
@@ -1,407 +1,445 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 re
27 from datetime import datetime
28 from pylons.i18n.translation import _, ungettext
27 29 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28 30
29 31
30 32 def __get_lem():
31 33 """
32 34 Get language extension map based on what's inside pygments lexers
33 35 """
34 36 from pygments import lexers
35 37 from string import lower
36 38 from collections import defaultdict
37 39
38 40 d = defaultdict(lambda: [])
39 41
40 42 def __clean(s):
41 43 s = s.lstrip('*')
42 44 s = s.lstrip('.')
43 45
44 46 if s.find('[') != -1:
45 47 exts = []
46 48 start, stop = s.find('['), s.find(']')
47 49
48 50 for suffix in s[start + 1:stop]:
49 51 exts.append(s[:s.find('[')] + suffix)
50 52 return map(lower, exts)
51 53 else:
52 54 return map(lower, [s])
53 55
54 56 for lx, t in sorted(lexers.LEXERS.items()):
55 57 m = map(__clean, t[-2])
56 58 if m:
57 59 m = reduce(lambda x, y: x + y, m)
58 60 for ext in m:
59 61 desc = lx.replace('Lexer', '')
60 62 d[ext].append(desc)
61 63
62 64 return dict(d)
63 65
64 66 def str2bool(_str):
65 67 """
66 68 returs True/False value from given string, it tries to translate the
67 69 string into boolean
68 70
69 71 :param _str: string value to translate into boolean
70 72 :rtype: boolean
71 73 :returns: boolean from given string
72 74 """
73 75 if _str is None:
74 76 return False
75 77 if _str in (True, False):
76 78 return _str
77 79 _str = str(_str).strip().lower()
78 80 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
79 81
80 82
81 83 def convert_line_endings(line, mode):
82 84 """
83 85 Converts a given line "line end" accordingly to given mode
84 86
85 87 Available modes are::
86 88 0 - Unix
87 89 1 - Mac
88 90 2 - DOS
89 91
90 92 :param line: given line to convert
91 93 :param mode: mode to convert to
92 94 :rtype: str
93 95 :return: converted line according to mode
94 96 """
95 97 from string import replace
96 98
97 99 if mode == 0:
98 100 line = replace(line, '\r\n', '\n')
99 101 line = replace(line, '\r', '\n')
100 102 elif mode == 1:
101 103 line = replace(line, '\r\n', '\r')
102 104 line = replace(line, '\n', '\r')
103 105 elif mode == 2:
104 106 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
105 107 return line
106 108
107 109
108 110 def detect_mode(line, default):
109 111 """
110 112 Detects line break for given line, if line break couldn't be found
111 113 given default value is returned
112 114
113 115 :param line: str line
114 116 :param default: default
115 117 :rtype: int
116 118 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
117 119 """
118 120 if line.endswith('\r\n'):
119 121 return 2
120 122 elif line.endswith('\n'):
121 123 return 0
122 124 elif line.endswith('\r'):
123 125 return 1
124 126 else:
125 127 return default
126 128
127 129
128 130 def generate_api_key(username, salt=None):
129 131 """
130 132 Generates unique API key for given username, if salt is not given
131 133 it'll be generated from some random string
132 134
133 135 :param username: username as string
134 136 :param salt: salt to hash generate KEY
135 137 :rtype: str
136 138 :returns: sha1 hash from username+salt
137 139 """
138 140 from tempfile import _RandomNameSequence
139 141 import hashlib
140 142
141 143 if salt is None:
142 144 salt = _RandomNameSequence().next()
143 145
144 146 return hashlib.sha1(username + salt).hexdigest()
145 147
146 148
147 149 def safe_unicode(str_, from_encoding=None):
148 150 """
149 151 safe unicode function. Does few trick to turn str_ into unicode
150 152
151 153 In case of UnicodeDecode error we try to return it with encoding detected
152 154 by chardet library if it fails fallback to unicode with errors replaced
153 155
154 156 :param str_: string to decode
155 157 :rtype: unicode
156 158 :returns: unicode object
157 159 """
158 160 if isinstance(str_, unicode):
159 161 return str_
160 162
161 163 if not from_encoding:
162 164 import rhodecode
163 165 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
164 166 from_encoding = DEFAULT_ENCODING
165 167
166 168 try:
167 169 return unicode(str_)
168 170 except UnicodeDecodeError:
169 171 pass
170 172
171 173 try:
172 174 return unicode(str_, from_encoding)
173 175 except UnicodeDecodeError:
174 176 pass
175 177
176 178 try:
177 179 import chardet
178 180 encoding = chardet.detect(str_)['encoding']
179 181 if encoding is None:
180 182 raise Exception()
181 183 return str_.decode(encoding)
182 184 except (ImportError, UnicodeDecodeError, Exception):
183 185 return unicode(str_, from_encoding, 'replace')
184 186
185 187
186 188 def safe_str(unicode_, to_encoding=None):
187 189 """
188 190 safe str function. Does few trick to turn unicode_ into string
189 191
190 192 In case of UnicodeEncodeError we try to return it with encoding detected
191 193 by chardet library if it fails fallback to string with errors replaced
192 194
193 195 :param unicode_: unicode to encode
194 196 :rtype: str
195 197 :returns: str object
196 198 """
197 199
198 200 # if it's not basestr cast to str
199 201 if not isinstance(unicode_, basestring):
200 202 return str(unicode_)
201 203
202 204 if isinstance(unicode_, str):
203 205 return unicode_
204 206
205 207 if not to_encoding:
206 208 import rhodecode
207 209 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
208 210 to_encoding = DEFAULT_ENCODING
209 211
210 212 try:
211 213 return unicode_.encode(to_encoding)
212 214 except UnicodeEncodeError:
213 215 pass
214 216
215 217 try:
216 218 import chardet
217 219 encoding = chardet.detect(unicode_)['encoding']
218 220 if encoding is None:
219 221 raise UnicodeEncodeError()
220 222
221 223 return unicode_.encode(encoding)
222 224 except (ImportError, UnicodeEncodeError):
223 225 return unicode_.encode(to_encoding, 'replace')
224 226
225 227 return safe_str
226 228
227 229
228 230 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
229 231 """
230 232 Custom engine_from_config functions that makes sure we use NullPool for
231 233 file based sqlite databases. This prevents errors on sqlite. This only
232 234 applies to sqlalchemy versions < 0.7.0
233 235
234 236 """
235 237 import sqlalchemy
236 238 from sqlalchemy import engine_from_config as efc
237 239 import logging
238 240
239 241 if int(sqlalchemy.__version__.split('.')[1]) < 7:
240 242
241 243 # This solution should work for sqlalchemy < 0.7.0, and should use
242 244 # proxy=TimerProxy() for execution time profiling
243 245
244 246 from sqlalchemy.pool import NullPool
245 247 url = configuration[prefix + 'url']
246 248
247 249 if url.startswith('sqlite'):
248 250 kwargs.update({'poolclass': NullPool})
249 251 return efc(configuration, prefix, **kwargs)
250 252 else:
251 253 import time
252 254 from sqlalchemy import event
253 255 from sqlalchemy.engine import Engine
254 256
255 257 log = logging.getLogger('sqlalchemy.engine')
256 258 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
257 259 engine = efc(configuration, prefix, **kwargs)
258 260
259 261 def color_sql(sql):
260 262 COLOR_SEQ = "\033[1;%dm"
261 263 COLOR_SQL = YELLOW
262 264 normal = '\x1b[0m'
263 265 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
264 266
265 267 if configuration['debug']:
266 268 #attach events only for debug configuration
267 269
268 270 def before_cursor_execute(conn, cursor, statement,
269 271 parameters, context, executemany):
270 272 context._query_start_time = time.time()
271 273 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
272 274
273 275
274 276 def after_cursor_execute(conn, cursor, statement,
275 277 parameters, context, executemany):
276 278 total = time.time() - context._query_start_time
277 279 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
278 280
279 281 event.listen(engine, "before_cursor_execute",
280 282 before_cursor_execute)
281 283 event.listen(engine, "after_cursor_execute",
282 284 after_cursor_execute)
283 285
284 286 return engine
285 287
286 288
287 def age(curdate):
289 def age(prevdate):
288 290 """
289 291 turns a datetime into an age string.
290 292
291 :param curdate: datetime object
293 :param prevdate: datetime object
292 294 :rtype: unicode
293 295 :returns: unicode words describing age
294 296 """
295 297
296 from datetime import datetime
297 from webhelpers.date import time_ago_in_words
298 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
299 deltas = {}
300
301 # Get date parts deltas
302 now = datetime.now()
303 for part in order:
304 deltas[part] = getattr(now, part) - getattr(prevdate, part)
298 305
299 _ = lambda s: s
306 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
307 # not 1 hour, -59 minutes and -59 seconds)
300 308
301 if not curdate:
302 return ''
309 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
310 part = order[num]
311 carry_part = order[num - 1]
312
313 if deltas[part] < 0:
314 deltas[part] += length
315 deltas[carry_part] -= 1
303 316
304 agescales = [(_(u"year"), 3600 * 24 * 365),
305 (_(u"month"), 3600 * 24 * 30),
306 (_(u"day"), 3600 * 24),
307 (_(u"hour"), 3600),
308 (_(u"minute"), 60),
309 (_(u"second"), 1), ]
310
311 age = datetime.now() - curdate
312 age_seconds = (age.days * agescales[2][1]) + age.seconds
313 pos = 1
314 for scale in agescales:
315 if scale[1] <= age_seconds:
316 if pos == 6:
317 pos = 5
318 return '%s %s' % (time_ago_in_words(curdate,
319 agescales[pos][0]), _('ago'))
320 pos += 1
317 # Same thing for days except that the increment depends on the (variable)
318 # number of days in the month
319 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
320 if deltas['day'] < 0:
321 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
322 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
323 deltas['day'] += 29
324 else:
325 deltas['day'] += month_lengths[prevdate.month - 1]
326
327 deltas['month'] -= 1
328
329 if deltas['month'] < 0:
330 deltas['month'] += 12
331 deltas['year'] -= 1
332
333 # Format the result
334 fmt_funcs = {
335 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
336 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
337 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
338 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
339 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
340 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
341 }
342
343 for i, part in enumerate(order):
344 value = deltas[part]
345 if value == 0:
346 continue
347
348 if i < 5:
349 sub_part = order[i + 1]
350 sub_value = deltas[sub_part]
351 else:
352 sub_value = 0
353
354 if sub_value == 0:
355 return _(u'%s ago') % fmt_funcs[part](value)
356
357 return _(u'%s and %s ago') % (fmt_funcs[part](value),
358 fmt_funcs[sub_part](sub_value))
321 359
322 360 return _(u'just now')
323 361
324 362
325 363 def uri_filter(uri):
326 364 """
327 365 Removes user:password from given url string
328 366
329 367 :param uri:
330 368 :rtype: unicode
331 369 :returns: filtered list of strings
332 370 """
333 371 if not uri:
334 372 return ''
335 373
336 374 proto = ''
337 375
338 376 for pat in ('https://', 'http://'):
339 377 if uri.startswith(pat):
340 378 uri = uri[len(pat):]
341 379 proto = pat
342 380 break
343 381
344 382 # remove passwords and username
345 383 uri = uri[uri.find('@') + 1:]
346 384
347 385 # get the port
348 386 cred_pos = uri.find(':')
349 387 if cred_pos == -1:
350 388 host, port = uri, None
351 389 else:
352 390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
353 391
354 392 return filter(None, [proto, host, port])
355 393
356 394
357 395 def credentials_filter(uri):
358 396 """
359 397 Returns a url with removed credentials
360 398
361 399 :param uri:
362 400 """
363 401
364 402 uri = uri_filter(uri)
365 403 #check if we have port
366 404 if len(uri) > 2 and uri[2]:
367 405 uri[2] = ':' + uri[2]
368 406
369 407 return ''.join(uri)
370 408
371 409
372 410 def get_changeset_safe(repo, rev):
373 411 """
374 412 Safe version of get_changeset if this changeset doesn't exists for a
375 413 repo it returns a Dummy one instead
376 414
377 415 :param repo:
378 416 :param rev:
379 417 """
380 418 from rhodecode.lib.vcs.backends.base import BaseRepository
381 419 from rhodecode.lib.vcs.exceptions import RepositoryError
382 420 if not isinstance(repo, BaseRepository):
383 421 raise Exception('You must pass an Repository '
384 422 'object as first argument got %s', type(repo))
385 423
386 424 try:
387 425 cs = repo.get_changeset(rev)
388 426 except RepositoryError:
389 427 from rhodecode.lib.utils import EmptyChangeset
390 428 cs = EmptyChangeset(requested_revision=rev)
391 429 return cs
392 430
393 431
394 432 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
395 433
396 434
397 435 def extract_mentioned_users(s):
398 436 """
399 437 Returns unique usernames from given string s that have @mention
400 438
401 439 :param s: string to get mentions
402 440 """
403 441 usrs = set()
404 442 for username in re.findall(MENTIONS_REGEX, s):
405 443 usrs.add(username)
406 444
407 445 return sorted(list(usrs), key=lambda k: k.lower())
General Comments 0
You need to be logged in to leave comments. Login now