~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Jelmer Vernooij
  • Date: 2008-08-06 18:27:51 UTC
  • mto: (197.1.9 pathargs)
  • mto: This revision was merged to the branch mainline in revision 202.
  • Revision ID: jelmer@samba.org-20080806182751-s68wtdpxaryvylhp
Add --configfile argument.

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)
2
4
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
3
5
# Copyright (C) 2006  Goffredo Baroncelli <kreijack@inwind.it>
4
6
#
17
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
20
#
19
21
 
 
22
try:
 
23
    from xml.etree import ElementTree as ET
 
24
except ImportError:
 
25
    from elementtree import ElementTree as ET
 
26
 
20
27
import base64
21
28
import cgi
22
29
import datetime
23
30
import logging
24
31
import re
25
 
import sha
26
32
import struct
27
 
import sys
28
33
import threading
29
34
import time
30
 
import traceback
31
 
 
 
35
import types
32
36
 
33
37
log = logging.getLogger("loggerhead.controllers")
34
38
 
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
 
 
71
39
def fix_year(year):
72
40
    if year < 70:
73
41
        year += 2000
75
43
        year += 1900
76
44
    return year
77
45
 
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):
 
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):
87
74
    delta = datetime.datetime.now() - date
88
 
    if delta.days > 300:
89
 
        return date.strftime('%d %b %Y')
 
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'
90
96
    else:
91
 
        return date.strftime('%d %b %H:%M')
92
 
 
93
 
def set_date_format(format):
94
 
    global _g_format
95
 
    _g_format = format
 
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))
96
121
 
97
122
 
98
123
class Container (object):
116
141
        return out
117
142
 
118
143
 
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
 
 
129
144
def trunc(text, limit=10):
130
145
    if len(text) <= limit:
131
146
        return text
132
147
    return text[:limit] + '...'
133
148
 
134
149
 
135
 
def to_utf8(s):
136
 
    if isinstance(s, unicode):
137
 
        return s.encode('utf-8')
138
 
    return s
139
 
 
140
 
 
141
150
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
142
151
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
143
152
 
161
170
    return '%s at %s' % (username, domains[0])
162
171
 
163
172
 
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
 
 
198
173
# only do this if unicode turns out to be a problem
199
174
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
200
175
 
209
184
    s = s.replace(' ', '&nbsp;')
210
185
    return s
211
186
 
212
 
 
213
 
 
214
187
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
215
188
 
 
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
 
216
212
def fixed_width(s):
217
213
    """
218
214
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
240
236
    return '-rw-r--r--'
241
237
 
242
238
 
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
 
 
252
239
def b64(s):
253
240
    s = base64.encodestring(s).replace('\n', '')
254
241
    while (len(s) > 0) and (s[-1] == '='):
328
315
            return None
329
316
        return navigation.revid_list[navigation.position + offset]
330
317
 
 
318
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
331
319
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
332
320
    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)
333
326
 
334
 
    params = { 'file_id': navigation.file_id }
 
327
    params = { 'filter_file_id': navigation.filter_file_id }
335
328
    if getattr(navigation, 'query', None) is not None:
336
329
        params['q'] = navigation.query
337
 
    else:
338
 
        params['start_revid'] = navigation.start_revid
 
330
 
 
331
    if getattr(navigation, 'start_revid', None) is not None:
 
332
        params['start_revid'] = start_revno
339
333
 
340
334
    if navigation.prev_page_revid:
341
 
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
 
335
        navigation.prev_page_url = navigation.branch.context_url(
 
336
            [navigation.scan_url, prev_page_revno], **params)
342
337
    if navigation.next_page_revid:
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)
 
338
        navigation.next_page_url = navigation.branch.context_url(
 
339
            [navigation.scan_url, next_page_revno], **params)
349
340
 
350
341
 
351
342
def decorator(unbound):
378
369
 
379
370
 
380
371
@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
397
372
def lsprof(f):
398
373
    def _f(*a, **kw):
399
374
        from loggerhead.lsprof import profile
419
394
#         current location along the navigation path (while browsing)
420
395
#     - starting revid (start_revid)
421
396
#         the current beginning of navigation (navigation continues back to
422
 
#         the original revision) -- this may not be along the primary revision
423
 
#         path since the user may have navigated into a branch
 
397
#         the original revision) -- this defines an 'alternate mainline'
 
398
#         when the user navigates into a branch.
424
399
#     - file_id
 
400
#         the file being looked at
 
401
#     - filter_file_id
425
402
#         if navigating the revisions that touched a file
426
403
#     - q (query)
427
404
#         if navigating the revisions that matched a search query
438
415
#         for re-ordering an existing page by different sort
439
416
 
440
417
t_context = threading.local()
441
 
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
 
418
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
 
419
          'compare_revid', 'sort')
442
420
 
443
421
 
444
422
def set_context(map):
447
425
 
448
426
def get_context(**overrides):
449
427
    """
 
428
    Soon to be deprecated.
 
429
 
 
430
 
450
431
    return a context map that may be overriden by specific values passed in,
451
432
    but only contains keys from the list of valid context keys.
452
433