~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
 
    if value is not None:
63
 
        return value.strftime('%Y-%m-%d %T')
64
 
    else:
65
 
        return 'N/A'
66
 
 
67
 
 
68
 
def _displaydate(date):
69
 
    delta = abs(datetime.datetime.now() - date)
70
 
    if delta > datetime.timedelta(1, 0, 0):
71
 
        # far in the past or future, display the date
72
 
        return 'on ' + date_day(date)
73
 
    return _approximatedate(date)
74
 
 
75
 
 
76
 
def _approximatedate(date):
77
 
    delta = datetime.datetime.now() - date
78
 
    if abs(delta) > datetime.timedelta(1, 0, 0):
79
 
        # far in the past or future, display the date
80
 
        return date_day(date)
81
 
    future = delta < datetime.timedelta(0, 0, 0)
82
 
    delta = abs(delta)
83
 
    days = delta.days
84
 
    hours = delta.seconds / 3600
85
 
    minutes = (delta.seconds - (3600*hours)) / 60
86
 
    seconds = delta.seconds % 60
87
 
    result = ''
88
 
    if future:
89
 
        result += 'in '
90
 
    if days != 0:
91
 
        amount = days
92
 
        unit = 'day'
93
 
    elif hours != 0:
94
 
        amount = hours
95
 
        unit = 'hour'
96
 
    elif minutes != 0:
97
 
        amount = minutes
98
 
        unit = 'minute'
99
 
    else:
100
 
        amount = seconds
101
 
        unit = 'second'
102
 
    if amount != 1:
103
 
        unit += 's'
104
 
    result += '%s %s' % (amount, unit)
105
 
    if not future:
106
 
        result += ' ago'
107
 
        return result
108
 
 
109
 
 
110
 
def _wrap_with_date_time_title(date, formatted_date):
111
 
    elem = ET.Element("span")
112
 
    elem.text = formatted_date
113
 
    elem.set("title", date_time(date))
114
 
    return elem
115
 
 
116
 
 
117
 
def approximatedate(date):
118
 
    #FIXME: Returns an object instead of a string
119
 
    return _wrap_with_date_time_title(date, _approximatedate(date))
120
 
 
121
 
 
122
 
def displaydate(date):
123
 
    return _wrap_with_date_time_title(date, _displaydate(date))
124
 
 
125
80
 
126
81
class Container (object):
127
82
    """
133
88
                setattr(self, key, value)
134
89
        for key, value in kw.iteritems():
135
90
            setattr(self, key, value)
136
 
 
 
91
    
137
92
    def __repr__(self):
138
93
        out = '{ '
139
94
        for key, value in self.__dict__.iteritems():
144
99
        return out
145
100
 
146
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
 
147
112
def trunc(text, limit=10):
148
113
    if len(text) <= limit:
149
114
        return text
150
115
    return text[:limit] + '...'
151
116
 
152
117
 
 
118
def to_utf8(s):
 
119
    if isinstance(s, unicode):
 
120
        return s.encode('utf-8')
 
121
    return s
 
122
 
 
123
 
153
124
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
154
125
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
155
126
 
172
143
        return '%s at %s' % (username, domains[-2])
173
144
    return '%s at %s' % (username, domains[0])
174
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
 
175
180
 
176
181
# only do this if unicode turns out to be a problem
177
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
187
192
    s = s.replace(' ', '&nbsp;')
188
193
    return s
189
194
 
 
195
 
 
196
 
190
197
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
191
198
 
192
 
def fill_div(s):
193
 
    """
194
 
    CSS is stupid. In some cases we need to replace an empty value with
195
 
    a non breaking space (&nbsp;). There has to be a better way of doing this.
196
 
 
197
 
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
198
 
    """
199
 
    
200
 
 
201
 
    if s is None:
202
 
        return '&nbsp;'
203
 
    elif isinstance(s, int):
204
 
        return s
205
 
    elif not s.strip():
206
 
        return '&nbsp;'
207
 
    else:
208
 
        try:
209
 
            s = s.decode('utf-8')
210
 
        except UnicodeDecodeError:
211
 
            s = s.decode('iso-8859-15')
212
 
        return s
213
 
 
214
 
 
215
199
def fixed_width(s):
216
200
    """
217
201
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
239
223
    return '-rw-r--r--'
240
224
 
241
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
 
242
235
def b64(s):
243
236
    s = base64.encodestring(s).replace('\n', '')
244
237
    while (len(s) > 0) and (s[-1] == '='):
279
272
        divisor = MEG
280
273
    else:
281
274
        divisor = KILO
282
 
 
 
275
    
283
276
    dot = size % divisor
284
277
    base = size - dot
285
278
    dot = dot * 10 // divisor
287
280
    if dot >= 10:
288
281
        base += 1
289
282
        dot -= 10
290
 
 
 
283
    
291
284
    out = str(base)
292
285
    if (base < 100) and (dot != 0):
293
286
        out += '.%d' % (dot,)
298
291
    elif divisor == GIG:
299
292
        out += 'G'
300
293
    return out
301
 
 
 
294
    
302
295
 
303
296
def fill_in_navigation(navigation):
304
297
    """
312
305
    navigation.count = len(navigation.revid_list)
313
306
    navigation.page_position = navigation.position // navigation.pagesize + 1
314
307
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
315
 
 
 
308
    
316
309
    def get_offset(offset):
317
310
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
318
311
            return None
319
312
        return navigation.revid_list[navigation.position + offset]
320
 
 
321
 
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
 
313
    
322
314
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
323
315
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
324
 
    prev_page_revno = navigation.history.get_revno(
325
 
            navigation.prev_page_revid)
326
 
    next_page_revno = navigation.history.get_revno(
327
 
            navigation.next_page_revid)
328
 
    start_revno = navigation.history.get_revno(navigation.start_revid)
329
 
 
330
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
316
    
 
317
    params = { 'file_id': navigation.file_id }
331
318
    if getattr(navigation, 'query', None) is not None:
332
319
        params['q'] = navigation.query
333
 
 
334
 
    if getattr(navigation, 'start_revid', None) is not None:
335
 
        params['start_revid'] = start_revno
336
 
 
 
320
    else:
 
321
        params['start_revid'] = navigation.start_revid
 
322
        
337
323
    if navigation.prev_page_revid:
338
 
        navigation.prev_page_url = navigation.branch.context_url(
339
 
            [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))
340
325
    if navigation.next_page_revid:
341
 
        navigation.next_page_url = navigation.branch.context_url(
342
 
            [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)
343
332
 
344
333
 
345
334
def decorator(unbound):
372
361
 
373
362
 
374
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
375
380
def lsprof(f):
376
381
    def _f(*a, **kw):
377
382
        from loggerhead.lsprof import profile
397
402
#         current location along the navigation path (while browsing)
398
403
#     - starting revid (start_revid)
399
404
#         the current beginning of navigation (navigation continues back to
400
 
#         the original revision) -- this defines an 'alternate mainline'
401
 
#         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
402
407
#     - file_id
403
 
#         the file being looked at
404
 
#     - filter_file_id
405
408
#         if navigating the revisions that touched a file
406
409
#     - q (query)
407
410
#         if navigating the revisions that matched a search query
418
421
#         for re-ordering an existing page by different sort
419
422
 
420
423
t_context = threading.local()
421
 
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
422
 
          'compare_revid', 'sort')
 
424
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
423
425
 
424
426
 
425
427
def set_context(map):
428
430
 
429
431
def get_context(**overrides):
430
432
    """
431
 
    Soon to be deprecated.
432
 
 
433
 
 
434
433
    return a context map that may be overriden by specific values passed in,
435
434
    but only contains keys from the list of valid context keys.
436
 
 
 
435
    
437
436
    if 'clear' is set, only the 'remember' context value will be added, and
438
437
    all other context will be omitted.
439
438
    """