~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Robey Pointer
  • Date: 2007-01-24 18:45:18 UTC
  • Revision ID: robey@lag.net-20070124184518-b7xkufmmmv0igouz
fix stupid bugs in inventory that i didn't even check before.

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
 
from elementtree import ElementTree as ET
21
 
 
22
20
import base64
23
21
import cgi
24
22
import datetime
28
26
import struct
29
27
import sys
30
28
import threading
31
 
import time
32
29
import traceback
33
30
 
 
31
import turbogears
 
32
 
34
33
 
35
34
log = logging.getLogger("loggerhead.controllers")
36
35
 
37
 
# Display of times.
38
 
 
39
 
# date_day -- just the day
40
 
# date_time -- full date with time
41
 
#
42
 
# displaydate -- for use in sentences
43
 
# approximatedate -- for use in tables
44
 
#
45
 
# displaydate and approximatedate return an elementtree <span> Element
46
 
# with the full date in a tooltip.
47
 
 
48
 
def date_day(value):
49
 
    return value.strftime('%Y-%m-%d')
50
 
 
51
 
 
52
 
def date_time(value):
53
 
    return value.strftime('%Y-%m-%d %T')
54
 
 
55
 
 
56
 
def _displaydate(date):
57
 
    delta = abs(datetime.datetime.now() - date)
58
 
    if delta > datetime.timedelta(1, 0, 0):
59
 
        # far in the past or future, display the date
60
 
        return 'on ' + date_day(date)
61
 
    return _approximatedate(date)
62
 
 
63
 
 
64
 
def _approximatedate(date):
65
 
    delta = datetime.datetime.now() - date
66
 
    if abs(delta) > datetime.timedelta(1, 0, 0):
67
 
        # far in the past or future, display the date
68
 
        return date_day(date)
69
 
    future = delta < datetime.timedelta(0, 0, 0)
70
 
    delta = abs(delta)
71
 
    days = delta.days
72
 
    hours = delta.seconds / 3600
73
 
    minutes = (delta.seconds - (3600*hours)) / 60
74
 
    seconds = delta.seconds % 60
75
 
    result = ''
76
 
    if future:
77
 
        result += 'in '
78
 
    if days != 0:
79
 
        amount = days
80
 
        unit = 'day'
81
 
    elif hours != 0:
82
 
        amount = hours
83
 
        unit = 'hour'
84
 
    elif minutes != 0:
85
 
        amount = minutes
86
 
        unit = 'minute'
87
 
    else:
88
 
        amount = seconds
89
 
        unit = 'second'
90
 
    if amount != 1:
91
 
        unit += 's'
92
 
    result += '%s %s' % (amount, unit)
93
 
    if not future:
94
 
        result += ' ago'
95
 
        return result
96
 
 
97
 
 
98
 
def _wrap_with_date_time_title(date, formatted_date):
99
 
    elem = ET.Element("span")
100
 
    elem.text = formatted_date
101
 
    elem.set("title", date_time(date))
102
 
    return elem
103
 
 
104
 
 
105
 
def approximatedate(date):
106
 
    return _wrap_with_date_time_title(date, _approximatedate(date))
107
 
 
108
 
 
109
 
def displaydate(date):
110
 
    return _wrap_with_date_time_title(date, _displaydate(date))
 
36
 
 
37
def timespan(delta):
 
38
    if delta.days > 730:
 
39
        # good grief!
 
40
        return '%d years' % (int(delta.days // 365.25),)
 
41
    if delta.days >= 3:
 
42
        return '%d days' % delta.days
 
43
    seg = []
 
44
    if delta.days > 0:
 
45
        if delta.days == 1:
 
46
            seg.append('1 day')
 
47
        else:
 
48
            seg.append('%d days' % delta.days)
 
49
    hrs = delta.seconds // 3600
 
50
    mins = (delta.seconds % 3600) // 60
 
51
    if hrs > 0:
 
52
        if hrs == 1:
 
53
            seg.append('1 hour')
 
54
        else:
 
55
            seg.append('%d hours' % hrs)
 
56
    if delta.days == 0:
 
57
        if mins > 0:
 
58
            if mins == 1:
 
59
                seg.append('1 minute')
 
60
            else:
 
61
                seg.append('%d minutes' % mins)
 
62
        elif hrs == 0:
 
63
            seg.append('less than a minute')
 
64
    return ', '.join(seg)
 
65
 
 
66
 
 
67
def ago(timestamp):
 
68
    now = datetime.datetime.now()
 
69
    return timespan(now - timestamp) + ' ago'
 
70
 
 
71
 
 
72
def fix_year(year):
 
73
    if year < 70:
 
74
        year += 2000
 
75
    if year < 100:
 
76
        year += 1900
 
77
    return year
111
78
 
112
79
 
113
80
class Container (object):
120
87
                setattr(self, key, value)
121
88
        for key, value in kw.iteritems():
122
89
            setattr(self, key, value)
123
 
 
 
90
    
124
91
    def __repr__(self):
125
92
        out = '{ '
126
93
        for key, value in self.__dict__.iteritems():
175
142
        return '%s at %s' % (username, domains[-2])
176
143
    return '%s at %s' % (username, domains[0])
177
144
 
178
 
 
 
145
    
179
146
def triple_factors(min_value=1):
180
147
    factors = (1, 3)
181
148
    index = 0
193
160
    """
194
161
    given a position in a maximum range, return a list of negative and positive
195
162
    jump factors for an hgweb-style triple-factor geometric scan.
196
 
 
 
163
    
197
164
    for example, with pos=20 and max=500, the range would be:
198
165
    [ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
199
 
 
 
166
    
200
167
    i admit this is a very strange way of jumping through revisions.  i didn't
201
168
    invent it. :)
202
169
    """
213
180
# only do this if unicode turns out to be a problem
214
181
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
215
182
 
216
 
# FIXME: get rid of this method; use fixed_width() and avoid XML().
217
183
def html_clean(s):
218
184
    """
219
185
    clean up a string for html display.  expand any tabs, encode any html
221
187
    in displaying monospace text.
222
188
    """
223
189
    s = cgi.escape(s.expandtabs())
 
190
#    s = _BADCHARS_RE.sub(lambda x: '&#%d;' % (ord(x.group(0)),), s)
224
191
    s = s.replace(' ', '&nbsp;')
225
192
    return s
226
193
 
227
194
 
228
 
 
229
 
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
230
 
 
231
 
def fixed_width(s):
232
 
    """
233
 
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
234
 
    chop up the string.
235
 
    """
236
 
    if not isinstance(s, unicode):
237
 
        # this kinda sucks.  file contents are just binary data, and no
238
 
        # encoding metadata is stored, so we need to guess.  this is probably
239
 
        # okay for most code, but for people using things like KOI-8, this
240
 
        # will display gibberish.  we have no way of detecting the correct
241
 
        # encoding to use.
242
 
        try:
243
 
            s = s.decode('utf-8')
244
 
        except UnicodeDecodeError:
245
 
            s = s.decode('iso-8859-15')
246
 
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
247
 
 
248
 
 
249
195
def fake_permissions(kind, executable):
250
196
    # fake up unix-style permissions given only a "kind" and executable bit
251
197
    if kind == 'directory':
304
250
        divisor = MEG
305
251
    else:
306
252
        divisor = KILO
307
 
 
 
253
    
308
254
    dot = size % divisor
309
255
    base = size - dot
310
256
    dot = dot * 10 // divisor
312
258
    if dot >= 10:
313
259
        base += 1
314
260
        dot -= 10
315
 
 
 
261
    
316
262
    out = str(base)
317
263
    if (base < 100) and (dot != 0):
318
264
        out += '.%d' % (dot,)
323
269
    elif divisor == GIG:
324
270
        out += 'G'
325
271
    return out
326
 
 
327
 
 
328
 
def fill_in_navigation(navigation):
 
272
    
 
273
 
 
274
def fill_in_navigation(history, navigation):
329
275
    """
330
276
    given a navigation block (used by the template for the page header), fill
331
277
    in useful calculated values.
332
278
    """
333
 
    if navigation.revid in navigation.revid_list: # XXX is this always true?
334
 
        navigation.position = navigation.revid_list.index(navigation.revid)
335
 
    else:
 
279
    navigation.position = history.get_revid_sequence(navigation.revid_list, navigation.revid)
 
280
    if navigation.position is None:
336
281
        navigation.position = 0
337
282
    navigation.count = len(navigation.revid_list)
338
283
    navigation.page_position = navigation.position // navigation.pagesize + 1
339
284
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
340
 
 
 
285
    
341
286
    def get_offset(offset):
342
287
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
343
288
            return None
344
289
        return navigation.revid_list[navigation.position + offset]
345
 
 
 
290
    
346
291
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
347
292
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
348
 
 
349
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
293
    
 
294
    params = { 'file_id': navigation.file_id }
350
295
    if getattr(navigation, 'query', None) is not None:
351
296
        params['q'] = navigation.query
352
297
    else:
353
298
        params['start_revid'] = navigation.start_revid
354
 
 
 
299
        
355
300
    if navigation.prev_page_revid:
356
301
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
357
302
    if navigation.next_page_revid:
408
353
    return _f
409
354
 
410
355
 
411
 
@decorator
412
 
def lsprof(f):
413
 
    def _f(*a, **kw):
414
 
        from loggerhead.lsprof import profile
415
 
        import cPickle
416
 
        z = time.time()
417
 
        ret, stats = profile(f, *a, **kw)
418
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
419
 
        stats.sort()
420
 
        stats.freeze()
421
 
        now = time.time()
422
 
        msec = int(now * 1000) % 1000
423
 
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
424
 
        filename = f.__name__ + '-' + timestr + '.lsprof'
425
 
        cPickle.dump(stats, open(filename, 'w'), 2)
426
 
        return ret
427
 
    return _f
428
 
 
429
 
 
430
356
# just thinking out loud here...
431
357
#
432
358
# so, when browsing around, there are 5 pieces of context, most optional:
434
360
#         current location along the navigation path (while browsing)
435
361
#     - starting revid (start_revid)
436
362
#         the current beginning of navigation (navigation continues back to
437
 
#         the original revision) -- this defines an 'alternate mainline'
438
 
#         when the user navigates into a branch.
 
363
#         the original revision) -- this may not be along the primary revision
 
364
#         path since the user may have navigated into a branch
439
365
#     - file_id
440
 
#         the file being looked at
441
 
#     - filter_file_id
442
366
#         if navigating the revisions that touched a file
443
367
#     - q (query)
444
368
#         if navigating the revisions that matched a search query
455
379
#         for re-ordering an existing page by different sort
456
380
 
457
381
t_context = threading.local()
458
 
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
459
 
          'compare_revid', 'sort')
 
382
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
460
383
 
461
384
 
462
385
def set_context(map):
467
390
    """
468
391
    return a context map that may be overriden by specific values passed in,
469
392
    but only contains keys from the list of valid context keys.
470
 
 
 
393
    
471
394
    if 'clear' is set, only the 'remember' context value will be added, and
472
395
    all other context will be omitted.
473
396
    """