~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Michael Hudson
  • Date: 2007-10-29 16:19:30 UTC
  • mto: This revision was merged to the branch mainline in revision 141.
  • Revision ID: michael.hudson@canonical.com-20071029161930-oxqrd4rd8j1oz3hx
add do nothing check target

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#
2
 
# Copyright (C) 2008  Canonical Ltd.
3
 
#                     (Authored by Martin Albisetti <argentina@gmail.com)
4
2
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
5
3
# Copyright (C) 2006  Goffredo Baroncelli <kreijack@inwind.it>
6
4
#
19
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
18
#
21
19
 
22
 
try:
23
 
    from xml.etree import ElementTree as ET
24
 
except ImportError:
25
 
    from elementtree import ElementTree as ET
26
 
 
27
20
import base64
28
21
import cgi
29
22
import datetime
30
23
import logging
31
24
import re
 
25
import sha
32
26
import struct
 
27
import sys
33
28
import threading
34
29
import time
35
 
import types
 
30
import traceback
 
31
 
 
32
import turbogears
 
33
 
36
34
 
37
35
log = logging.getLogger("loggerhead.controllers")
38
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
 
39
73
def fix_year(year):
40
74
    if year < 70:
41
75
        year += 2000
43
77
        year += 1900
44
78
    return year
45
79
 
46
 
# Display of times.
47
 
 
48
 
# date_day -- just the day
49
 
# date_time -- full date with time
50
 
#
51
 
# displaydate -- for use in sentences
52
 
# approximatedate -- for use in tables
53
 
#
54
 
# displaydate and approximatedate return an elementtree <span> Element
55
 
# with the full date in a tooltip.
56
 
 
57
 
def date_day(value):
58
 
    return value.strftime('%Y-%m-%d')
59
 
 
60
 
 
61
 
def date_time(value):
62
 
    return value.strftime('%Y-%m-%d %T')
63
 
 
64
 
 
65
 
def _displaydate(date):
66
 
    delta = abs(datetime.datetime.now() - date)
67
 
    if delta > datetime.timedelta(1, 0, 0):
68
 
        # far in the past or future, display the date
69
 
        return 'on ' + date_day(date)
70
 
    return _approximatedate(date)
71
 
 
72
 
 
73
 
def _approximatedate(date):
74
 
    delta = datetime.datetime.now() - date
75
 
    if abs(delta) > datetime.timedelta(1, 0, 0):
76
 
        # far in the past or future, display the date
77
 
        return date_day(date)
78
 
    future = delta < datetime.timedelta(0, 0, 0)
79
 
    delta = abs(delta)
80
 
    days = delta.days
81
 
    hours = delta.seconds / 3600
82
 
    minutes = (delta.seconds - (3600*hours)) / 60
83
 
    seconds = delta.seconds % 60
84
 
    result = ''
85
 
    if future:
86
 
        result += 'in '
87
 
    if days != 0:
88
 
        amount = days
89
 
        unit = 'day'
90
 
    elif hours != 0:
91
 
        amount = hours
92
 
        unit = 'hour'
93
 
    elif minutes != 0:
94
 
        amount = minutes
95
 
        unit = 'minute'
96
 
    else:
97
 
        amount = seconds
98
 
        unit = 'second'
99
 
    if amount != 1:
100
 
        unit += 's'
101
 
    result += '%s %s' % (amount, unit)
102
 
    if not future:
103
 
        result += ' ago'
104
 
        return result
105
 
 
106
 
 
107
 
def _wrap_with_date_time_title(date, formatted_date):
108
 
    elem = ET.Element("span")
109
 
    elem.text = formatted_date
110
 
    elem.set("title", date_time(date))
111
 
    return elem
112
 
 
113
 
 
114
 
def approximatedate(date):
115
 
    #FIXME: Returns an object instead of a string
116
 
    return _wrap_with_date_time_title(date, _approximatedate(date))
117
 
 
118
 
 
119
 
def displaydate(date):
120
 
    return _wrap_with_date_time_title(date, _displaydate(date))
121
 
 
122
80
 
123
81
class Container (object):
124
82
    """
130
88
                setattr(self, key, value)
131
89
        for key, value in kw.iteritems():
132
90
            setattr(self, key, value)
133
 
 
 
91
    
134
92
    def __repr__(self):
135
93
        out = '{ '
136
94
        for key, value in self.__dict__.iteritems():
141
99
        return out
142
100
 
143
101
 
 
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
 
144
112
def trunc(text, limit=10):
145
113
    if len(text) <= limit:
146
114
        return text
147
115
    return text[:limit] + '...'
148
116
 
149
117
 
 
118
def to_utf8(s):
 
119
    if isinstance(s, unicode):
 
120
        return s.encode('utf-8')
 
121
    return s
 
122
 
 
123
 
150
124
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
151
125
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
152
126
 
169
143
        return '%s at %s' % (username, domains[-2])
170
144
    return '%s at %s' % (username, domains[0])
171
145
 
 
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
 
172
180
 
173
181
# only do this if unicode turns out to be a problem
174
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
184
192
    s = s.replace(' ', '&nbsp;')
185
193
    return s
186
194
 
 
195
 
 
196
 
187
197
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
188
198
 
189
 
def fill_div(s):
190
 
    """
191
 
    CSS is stupid. In some cases we need to replace an empty value with
192
 
    a non breaking space (&nbsp;). There has to be a better way of doing this.
193
 
 
194
 
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
195
 
    """
196
 
    
197
 
 
198
 
    if s is None:
199
 
        return '&nbsp;'
200
 
    elif isinstance(s, int):
201
 
        return s
202
 
    elif not s.strip():
203
 
        return '&nbsp;'
204
 
    else:
205
 
        try:
206
 
            s = s.decode('utf-8')
207
 
        except UnicodeDecodeError:
208
 
            s = s.decode('iso-8859-15')
209
 
        return s
210
 
 
211
 
 
212
199
def fixed_width(s):
213
200
    """
214
201
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
236
223
    return '-rw-r--r--'
237
224
 
238
225
 
 
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
 
239
235
def b64(s):
240
236
    s = base64.encodestring(s).replace('\n', '')
241
237
    while (len(s) > 0) and (s[-1] == '='):
276
272
        divisor = MEG
277
273
    else:
278
274
        divisor = KILO
279
 
 
 
275
    
280
276
    dot = size % divisor
281
277
    base = size - dot
282
278
    dot = dot * 10 // divisor
284
280
    if dot >= 10:
285
281
        base += 1
286
282
        dot -= 10
287
 
 
 
283
    
288
284
    out = str(base)
289
285
    if (base < 100) and (dot != 0):
290
286
        out += '.%d' % (dot,)
295
291
    elif divisor == GIG:
296
292
        out += 'G'
297
293
    return out
298
 
 
 
294
    
299
295
 
300
296
def fill_in_navigation(navigation):
301
297
    """
309
305
    navigation.count = len(navigation.revid_list)
310
306
    navigation.page_position = navigation.position // navigation.pagesize + 1
311
307
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
312
 
 
 
308
    
313
309
    def get_offset(offset):
314
310
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
315
311
            return None
316
312
        return navigation.revid_list[navigation.position + offset]
317
 
 
318
 
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
 
313
    
319
314
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
320
315
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
321
 
    prev_page_revno = navigation.history.get_revno(
322
 
            navigation.prev_page_revid)
323
 
    next_page_revno = navigation.history.get_revno(
324
 
            navigation.next_page_revid)
325
 
    start_revno = navigation.history.get_revno(navigation.start_revid)
326
 
 
327
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
316
    
 
317
    params = { 'file_id': navigation.file_id }
328
318
    if getattr(navigation, 'query', None) is not None:
329
319
        params['q'] = navigation.query
330
 
 
331
 
    if getattr(navigation, 'start_revid', None) is not None:
332
 
        params['start_revid'] = start_revno
333
 
 
 
320
    else:
 
321
        params['start_revid'] = navigation.start_revid
 
322
        
334
323
    if navigation.prev_page_revid:
335
 
        navigation.prev_page_url = navigation.branch.context_url(
336
 
            [navigation.scan_url, prev_page_revno], **params)
 
324
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
337
325
    if navigation.next_page_revid:
338
 
        navigation.next_page_url = navigation.branch.context_url(
339
 
            [navigation.scan_url, next_page_revno], **params)
 
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)
340
332
 
341
333
 
342
334
def decorator(unbound):
369
361
 
370
362
 
371
363
@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
372
380
def lsprof(f):
373
381
    def _f(*a, **kw):
374
382
        from loggerhead.lsprof import profile
394
402
#         current location along the navigation path (while browsing)
395
403
#     - starting revid (start_revid)
396
404
#         the current beginning of navigation (navigation continues back to
397
 
#         the original revision) -- this defines an 'alternate mainline'
398
 
#         when the user navigates into a branch.
 
405
#         the original revision) -- this may not be along the primary revision
 
406
#         path since the user may have navigated into a branch
399
407
#     - file_id
400
 
#         the file being looked at
401
 
#     - filter_file_id
402
408
#         if navigating the revisions that touched a file
403
409
#     - q (query)
404
410
#         if navigating the revisions that matched a search query
415
421
#         for re-ordering an existing page by different sort
416
422
 
417
423
t_context = threading.local()
418
 
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
419
 
          'compare_revid', 'sort')
 
424
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
420
425
 
421
426
 
422
427
def set_context(map):
425
430
 
426
431
def get_context(**overrides):
427
432
    """
428
 
    Soon to be deprecated.
429
 
 
430
 
 
431
433
    return a context map that may be overriden by specific values passed in,
432
434
    but only contains keys from the list of valid context keys.
433
 
 
 
435
    
434
436
    if 'clear' is set, only the 'remember' context value will be added, and
435
437
    all other context will be omitted.
436
438
    """