~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Michael Hudson
  • Date: 2008-02-27 03:36:09 UTC
  • mto: This revision was merged to the branch mainline in revision 146.
  • Revision ID: michael.hudson@canonical.com-20080227033609-6xz80buvasyekl6a
run reindent.py over the loggerhead package

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
 
36
32
 
37
33
log = logging.getLogger("loggerhead.controllers")
38
34
 
 
35
 
 
36
def timespan(delta):
 
37
    if delta.days > 730:
 
38
        # good grief!
 
39
        return '%d years' % (int(delta.days // 365.25),)
 
40
    if delta.days >= 3:
 
41
        return '%d days' % delta.days
 
42
    seg = []
 
43
    if delta.days > 0:
 
44
        if delta.days == 1:
 
45
            seg.append('1 day')
 
46
        else:
 
47
            seg.append('%d days' % delta.days)
 
48
    hrs = delta.seconds // 3600
 
49
    mins = (delta.seconds % 3600) // 60
 
50
    if hrs > 0:
 
51
        if hrs == 1:
 
52
            seg.append('1 hour')
 
53
        else:
 
54
            seg.append('%d hours' % hrs)
 
55
    if delta.days == 0:
 
56
        if mins > 0:
 
57
            if mins == 1:
 
58
                seg.append('1 minute')
 
59
            else:
 
60
                seg.append('%d minutes' % mins)
 
61
        elif hrs == 0:
 
62
            seg.append('less than a minute')
 
63
    return ', '.join(seg)
 
64
 
 
65
 
 
66
def ago(timestamp):
 
67
    now = datetime.datetime.now()
 
68
    return timespan(now - timestamp) + ' ago'
 
69
 
 
70
 
39
71
def fix_year(year):
40
72
    if year < 70:
41
73
        year += 2000
43
75
        year += 1900
44
76
    return year
45
77
 
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):
 
78
 
 
79
_g_format = '%Y-%m-%d @ %H:%M'
 
80
 
 
81
def format_date(date):
 
82
    if _g_format == 'fancy':
 
83
        return fancy_format_date(date)
 
84
    return date.strftime(_g_format)
 
85
 
 
86
def fancy_format_date(date):
77
87
    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'
 
88
    if delta.days > 300:
 
89
        return date.strftime('%d %b %Y')
99
90
    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))
 
91
        return date.strftime('%d %b %H:%M')
 
92
 
 
93
def set_date_format(format):
 
94
    global _g_format
 
95
    _g_format = format
124
96
 
125
97
 
126
98
class Container (object):
144
116
        return out
145
117
 
146
118
 
 
119
def clean_revid(revid):
 
120
    if revid == 'missing':
 
121
        return revid
 
122
    return sha.new(revid).hexdigest()
 
123
 
 
124
 
 
125
def obfuscate(text):
 
126
    return ''.join([ '&#%d;' % ord(c) for c in text ])
 
127
 
 
128
 
147
129
def trunc(text, limit=10):
148
130
    if len(text) <= limit:
149
131
        return text
150
132
    return text[:limit] + '...'
151
133
 
152
134
 
 
135
def to_utf8(s):
 
136
    if isinstance(s, unicode):
 
137
        return s.encode('utf-8')
 
138
    return s
 
139
 
 
140
 
153
141
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
154
142
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
155
143
 
173
161
    return '%s at %s' % (username, domains[0])
174
162
 
175
163
 
 
164
def triple_factors(min_value=1):
 
165
    factors = (1, 3)
 
166
    index = 0
 
167
    n = 1
 
168
    while True:
 
169
        if n >= min_value:
 
170
            yield n * factors[index]
 
171
        index += 1
 
172
        if index >= len(factors):
 
173
            index = 0
 
174
            n *= 10
 
175
 
 
176
 
 
177
def scan_range(pos, max, pagesize=1):
 
178
    """
 
179
    given a position in a maximum range, return a list of negative and positive
 
180
    jump factors for an hgweb-style triple-factor geometric scan.
 
181
 
 
182
    for example, with pos=20 and max=500, the range would be:
 
183
    [ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
 
184
 
 
185
    i admit this is a very strange way of jumping through revisions.  i didn't
 
186
    invent it. :)
 
187
    """
 
188
    out = []
 
189
    for n in triple_factors(pagesize + 1):
 
190
        if n > max:
 
191
            return out
 
192
        if pos + n < max:
 
193
            out.append(n)
 
194
        if pos - n >= 0:
 
195
            out.insert(0, -n)
 
196
 
 
197
 
176
198
# only do this if unicode turns out to be a problem
177
199
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
178
200
 
187
209
    s = s.replace(' ', '&nbsp;')
188
210
    return s
189
211
 
 
212
 
 
213
 
190
214
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
191
215
 
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
216
def fixed_width(s):
216
217
    """
217
218
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
239
240
    return '-rw-r--r--'
240
241
 
241
242
 
 
243
def if_present(format, value):
 
244
    """
 
245
    format a value using a format string, if the value exists and is not None.
 
246
    """
 
247
    if value is None:
 
248
        return ''
 
249
    return format % value
 
250
 
 
251
 
242
252
def b64(s):
243
253
    s = base64.encodestring(s).replace('\n', '')
244
254
    while (len(s) > 0) and (s[-1] == '='):
318
328
            return None
319
329
        return navigation.revid_list[navigation.position + offset]
320
330
 
321
 
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
322
331
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
323
332
    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
333
 
330
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
334
    params = { 'file_id': navigation.file_id }
331
335
    if getattr(navigation, 'query', None) is not None:
332
336
        params['q'] = navigation.query
333
 
 
334
 
    if getattr(navigation, 'start_revid', None) is not None:
335
 
        params['start_revid'] = start_revno
 
337
    else:
 
338
        params['start_revid'] = navigation.start_revid
336
339
 
337
340
    if navigation.prev_page_revid:
338
 
        navigation.prev_page_url = navigation.branch.context_url(
339
 
            [navigation.scan_url, prev_page_revno], **params)
 
341
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
340
342
    if navigation.next_page_revid:
341
 
        navigation.next_page_url = navigation.branch.context_url(
342
 
            [navigation.scan_url, next_page_revno], **params)
 
343
        navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
 
344
 
 
345
 
 
346
def log_exception(log):
 
347
    for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
 
348
        log.debug(line)
343
349
 
344
350
 
345
351
def decorator(unbound):
372
378
 
373
379
 
374
380
@decorator
 
381
def strip_whitespace(f):
 
382
    def _f(*a, **kw):
 
383
        out = f(*a, **kw)
 
384
        orig_len = len(out)
 
385
        out = re.sub(r'\n\s+', '\n', out)
 
386
        out = re.sub(r'[ \t]+', ' ', out)
 
387
        out = re.sub(r'\s+\n', '\n', out)
 
388
        new_len = len(out)
 
389
        log.debug('Saved %sB (%d%%) by stripping whitespace.',
 
390
                  human_size(orig_len - new_len),
 
391
                  round(100.0 - float(new_len) * 100.0 / float(orig_len)))
 
392
        return out
 
393
    return _f
 
394
 
 
395
 
 
396
@decorator
375
397
def lsprof(f):
376
398
    def _f(*a, **kw):
377
399
        from loggerhead.lsprof import profile
397
419
#         current location along the navigation path (while browsing)
398
420
#     - starting revid (start_revid)
399
421
#         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.
 
422
#         the original revision) -- this may not be along the primary revision
 
423
#         path since the user may have navigated into a branch
402
424
#     - file_id
403
 
#         the file being looked at
404
 
#     - filter_file_id
405
425
#         if navigating the revisions that touched a file
406
426
#     - q (query)
407
427
#         if navigating the revisions that matched a search query
418
438
#         for re-ordering an existing page by different sort
419
439
 
420
440
t_context = threading.local()
421
 
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
422
 
          'compare_revid', 'sort')
 
441
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
423
442
 
424
443
 
425
444
def set_context(map):
428
447
 
429
448
def get_context(**overrides):
430
449
    """
431
 
    Soon to be deprecated.
432
 
 
433
 
 
434
450
    return a context map that may be overriden by specific values passed in,
435
451
    but only contains keys from the list of valid context keys.
436
452