~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Martin Albisetti
  • Author(s): Robert Collins
  • Date: 2008-07-11 12:23:22 UTC
  • Revision ID: argentina@gmail.com-20080711122322-oo9jjdrflkt1v0tf
Replaced deprecated code in 1.6. Bug #247550

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
#
19
19
 
 
20
try:
 
21
    from xml.etree import ElementTree as ET
 
22
except ImportError:
 
23
    from elementtree import ElementTree as ET
 
24
 
20
25
import base64
21
26
import cgi
22
27
import datetime
23
28
import logging
24
29
import re
25
 
import sha
26
30
import struct
27
 
import sys
28
31
import threading
29
32
import time
30
 
import traceback
31
 
 
32
 
import turbogears
33
33
 
34
34
 
35
35
log = logging.getLogger("loggerhead.controllers")
36
36
 
37
 
 
38
 
def timespan(delta):
39
 
    if delta.days > 730:
40
 
        # good grief!
41
 
        return '%d years' % (int(delta.days // 365.25),)
42
 
    if delta.days >= 3:
43
 
        return '%d days' % delta.days
44
 
    seg = []
45
 
    if delta.days > 0:
46
 
        if delta.days == 1:
47
 
            seg.append('1 day')
48
 
        else:
49
 
            seg.append('%d days' % delta.days)
50
 
    hrs = delta.seconds // 3600
51
 
    mins = (delta.seconds % 3600) // 60
52
 
    if hrs > 0:
53
 
        if hrs == 1:
54
 
            seg.append('1 hour')
55
 
        else:
56
 
            seg.append('%d hours' % hrs)
57
 
    if delta.days == 0:
58
 
        if mins > 0:
59
 
            if mins == 1:
60
 
                seg.append('1 minute')
61
 
            else:
62
 
                seg.append('%d minutes' % mins)
63
 
        elif hrs == 0:
64
 
            seg.append('less than a minute')
65
 
    return ', '.join(seg)
66
 
 
67
 
 
68
 
def ago(timestamp):
69
 
    now = datetime.datetime.now()
70
 
    return timespan(now - timestamp) + ' ago'
71
 
 
72
 
 
73
37
def fix_year(year):
74
38
    if year < 70:
75
39
        year += 2000
77
41
        year += 1900
78
42
    return year
79
43
 
 
44
# Display of times.
 
45
 
 
46
# date_day -- just the day
 
47
# date_time -- full date with time
 
48
#
 
49
# displaydate -- for use in sentences
 
50
# approximatedate -- for use in tables
 
51
#
 
52
# displaydate and approximatedate return an elementtree <span> Element
 
53
# with the full date in a tooltip.
 
54
 
 
55
def date_day(value):
 
56
    return value.strftime('%Y-%m-%d')
 
57
 
 
58
 
 
59
def date_time(value):
 
60
    return value.strftime('%Y-%m-%d %T')
 
61
 
 
62
 
 
63
def _displaydate(date):
 
64
    delta = abs(datetime.datetime.now() - date)
 
65
    if delta > datetime.timedelta(1, 0, 0):
 
66
        # far in the past or future, display the date
 
67
        return 'on ' + date_day(date)
 
68
    return _approximatedate(date)
 
69
 
 
70
 
 
71
def _approximatedate(date):
 
72
    delta = datetime.datetime.now() - date
 
73
    if abs(delta) > datetime.timedelta(1, 0, 0):
 
74
        # far in the past or future, display the date
 
75
        return date_day(date)
 
76
    future = delta < datetime.timedelta(0, 0, 0)
 
77
    delta = abs(delta)
 
78
    days = delta.days
 
79
    hours = delta.seconds / 3600
 
80
    minutes = (delta.seconds - (3600*hours)) / 60
 
81
    seconds = delta.seconds % 60
 
82
    result = ''
 
83
    if future:
 
84
        result += 'in '
 
85
    if days != 0:
 
86
        amount = days
 
87
        unit = 'day'
 
88
    elif hours != 0:
 
89
        amount = hours
 
90
        unit = 'hour'
 
91
    elif minutes != 0:
 
92
        amount = minutes
 
93
        unit = 'minute'
 
94
    else:
 
95
        amount = seconds
 
96
        unit = 'second'
 
97
    if amount != 1:
 
98
        unit += 's'
 
99
    result += '%s %s' % (amount, unit)
 
100
    if not future:
 
101
        result += ' ago'
 
102
        return result
 
103
 
 
104
 
 
105
def _wrap_with_date_time_title(date, formatted_date):
 
106
    elem = ET.Element("span")
 
107
    elem.text = formatted_date
 
108
    elem.set("title", date_time(date))
 
109
    return elem
 
110
 
 
111
 
 
112
def approximatedate(date):
 
113
    #FIXME: Returns an object instead of a string
 
114
    return _wrap_with_date_time_title(date, _approximatedate(date))
 
115
 
 
116
 
 
117
def displaydate(date):
 
118
    return _wrap_with_date_time_title(date, _displaydate(date))
 
119
 
80
120
 
81
121
class Container (object):
82
122
    """
88
128
                setattr(self, key, value)
89
129
        for key, value in kw.iteritems():
90
130
            setattr(self, key, value)
91
 
    
 
131
 
92
132
    def __repr__(self):
93
133
        out = '{ '
94
134
        for key, value in self.__dict__.iteritems():
99
139
        return out
100
140
 
101
141
 
102
 
def clean_revid(revid):
103
 
    if revid == 'missing':
104
 
        return revid
105
 
    return sha.new(revid).hexdigest()
106
 
 
107
 
 
108
 
def obfuscate(text):
109
 
    return ''.join([ '&#%d;' % ord(c) for c in text ])
110
 
 
111
 
 
112
142
def trunc(text, limit=10):
113
143
    if len(text) <= limit:
114
144
        return text
115
145
    return text[:limit] + '...'
116
146
 
117
147
 
118
 
def to_utf8(s):
119
 
    if isinstance(s, unicode):
120
 
        return s.encode('utf-8')
121
 
    return s
122
 
 
123
 
 
124
148
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
125
149
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
126
150
 
143
167
        return '%s at %s' % (username, domains[-2])
144
168
    return '%s at %s' % (username, domains[0])
145
169
 
146
 
    
147
 
def triple_factors(min_value=1):
148
 
    factors = (1, 3)
149
 
    index = 0
150
 
    n = 1
151
 
    while True:
152
 
        if n >= min_value:
153
 
            yield n * factors[index]
154
 
        index += 1
155
 
        if index >= len(factors):
156
 
            index = 0
157
 
            n *= 10
158
 
 
159
 
 
160
 
def scan_range(pos, max, pagesize=1):
161
 
    """
162
 
    given a position in a maximum range, return a list of negative and positive
163
 
    jump factors for an hgweb-style triple-factor geometric scan.
164
 
    
165
 
    for example, with pos=20 and max=500, the range would be:
166
 
    [ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
167
 
    
168
 
    i admit this is a very strange way of jumping through revisions.  i didn't
169
 
    invent it. :)
170
 
    """
171
 
    out = []
172
 
    for n in triple_factors(pagesize + 1):
173
 
        if n > max:
174
 
            return out
175
 
        if pos + n < max:
176
 
            out.append(n)
177
 
        if pos - n >= 0:
178
 
            out.insert(0, -n)
179
 
 
180
170
 
181
171
# only do this if unicode turns out to be a problem
182
172
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
223
213
    return '-rw-r--r--'
224
214
 
225
215
 
226
 
def if_present(format, value):
227
 
    """
228
 
    format a value using a format string, if the value exists and is not None.
229
 
    """
230
 
    if value is None:
231
 
        return ''
232
 
    return format % value
233
 
 
234
 
 
235
216
def b64(s):
236
217
    s = base64.encodestring(s).replace('\n', '')
237
218
    while (len(s) > 0) and (s[-1] == '='):
272
253
        divisor = MEG
273
254
    else:
274
255
        divisor = KILO
275
 
    
 
256
 
276
257
    dot = size % divisor
277
258
    base = size - dot
278
259
    dot = dot * 10 // divisor
280
261
    if dot >= 10:
281
262
        base += 1
282
263
        dot -= 10
283
 
    
 
264
 
284
265
    out = str(base)
285
266
    if (base < 100) and (dot != 0):
286
267
        out += '.%d' % (dot,)
291
272
    elif divisor == GIG:
292
273
        out += 'G'
293
274
    return out
294
 
    
 
275
 
295
276
 
296
277
def fill_in_navigation(navigation):
297
278
    """
305
286
    navigation.count = len(navigation.revid_list)
306
287
    navigation.page_position = navigation.position // navigation.pagesize + 1
307
288
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
308
 
    
 
289
 
309
290
    def get_offset(offset):
310
291
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
311
292
            return None
312
293
        return navigation.revid_list[navigation.position + offset]
313
 
    
 
294
 
314
295
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
315
296
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
316
 
    
317
 
    params = { 'file_id': navigation.file_id }
 
297
    prev_page_revno = navigation.history.get_revno(
 
298
            navigation.prev_page_revid)
 
299
    next_page_revno = navigation.history.get_revno(
 
300
            navigation.next_page_revid)
 
301
    start_revno = navigation.history.get_revno(navigation.start_revid)
 
302
 
 
303
    params = { 'filter_file_id': navigation.filter_file_id }
318
304
    if getattr(navigation, 'query', None) is not None:
319
305
        params['q'] = navigation.query
320
 
    else:
321
 
        params['start_revid'] = navigation.start_revid
322
 
        
 
306
 
 
307
    if getattr(navigation, 'start_revid', None) is not None:
 
308
        params['start_revid'] = start_revno
 
309
 
323
310
    if navigation.prev_page_revid:
324
 
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
 
311
        navigation.prev_page_url = navigation.branch.context_url(
 
312
            [navigation.scan_url, prev_page_revno], **params)
325
313
    if navigation.next_page_revid:
326
 
        navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
327
 
 
328
 
 
329
 
def log_exception(log):
330
 
    for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
331
 
        log.debug(line)
 
314
        navigation.next_page_url = navigation.branch.context_url(
 
315
            [navigation.scan_url, next_page_revno], **params)
332
316
 
333
317
 
334
318
def decorator(unbound):
361
345
 
362
346
 
363
347
@decorator
364
 
def strip_whitespace(f):
365
 
    def _f(*a, **kw):
366
 
        out = f(*a, **kw)
367
 
        orig_len = len(out)
368
 
        out = re.sub(r'\n\s+', '\n', out)
369
 
        out = re.sub(r'[ \t]+', ' ', out)
370
 
        out = re.sub(r'\s+\n', '\n', out)
371
 
        new_len = len(out)
372
 
        log.debug('Saved %sB (%d%%) by stripping whitespace.',
373
 
                  human_size(orig_len - new_len),
374
 
                  round(100.0 - float(new_len) * 100.0 / float(orig_len)))
375
 
        return out
376
 
    return _f
377
 
 
378
 
 
379
 
@decorator
380
348
def lsprof(f):
381
349
    def _f(*a, **kw):
382
350
        from loggerhead.lsprof import profile
402
370
#         current location along the navigation path (while browsing)
403
371
#     - starting revid (start_revid)
404
372
#         the current beginning of navigation (navigation continues back to
405
 
#         the original revision) -- this may not be along the primary revision
406
 
#         path since the user may have navigated into a branch
 
373
#         the original revision) -- this defines an 'alternate mainline'
 
374
#         when the user navigates into a branch.
407
375
#     - file_id
 
376
#         the file being looked at
 
377
#     - filter_file_id
408
378
#         if navigating the revisions that touched a file
409
379
#     - q (query)
410
380
#         if navigating the revisions that matched a search query
421
391
#         for re-ordering an existing page by different sort
422
392
 
423
393
t_context = threading.local()
424
 
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
 
394
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
 
395
          'compare_revid', 'sort')
425
396
 
426
397
 
427
398
def set_context(map):
430
401
 
431
402
def get_context(**overrides):
432
403
    """
 
404
    Soon to be deprecated.
 
405
 
 
406
 
433
407
    return a context map that may be overriden by specific values passed in,
434
408
    but only contains keys from the list of valid context keys.
435
 
    
 
409
 
436
410
    if 'clear' is set, only the 'remember' context value will be added, and
437
411
    all other context will be omitted.
438
412
    """