~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Robey Pointer
  • Date: 2007-03-26 07:05:10 UTC
  • mfrom: (114.2.2 handle-binary-files)
  • Revision ID: robey@lag.net-20070326070510-z9etamcrda8zphod
merge in fix for bug 91686: catch binary files and don't diff them.

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
31
29
import time
32
30
import traceback
33
31
 
 
32
import turbogears
 
33
 
34
34
 
35
35
log = logging.getLogger("loggerhead.controllers")
36
36
 
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))
 
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
 
 
73
def fix_year(year):
 
74
    if year < 70:
 
75
        year += 2000
 
76
    if year < 100:
 
77
        year += 1900
 
78
    return year
111
79
 
112
80
 
113
81
class Container (object):
120
88
                setattr(self, key, value)
121
89
        for key, value in kw.iteritems():
122
90
            setattr(self, key, value)
123
 
 
 
91
    
124
92
    def __repr__(self):
125
93
        out = '{ '
126
94
        for key, value in self.__dict__.iteritems():
175
143
        return '%s at %s' % (username, domains[-2])
176
144
    return '%s at %s' % (username, domains[0])
177
145
 
178
 
 
 
146
    
179
147
def triple_factors(min_value=1):
180
148
    factors = (1, 3)
181
149
    index = 0
193
161
    """
194
162
    given a position in a maximum range, return a list of negative and positive
195
163
    jump factors for an hgweb-style triple-factor geometric scan.
196
 
 
 
164
    
197
165
    for example, with pos=20 and max=500, the range would be:
198
166
    [ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
199
 
 
 
167
    
200
168
    i admit this is a very strange way of jumping through revisions.  i didn't
201
169
    invent it. :)
202
170
    """
213
181
# only do this if unicode turns out to be a problem
214
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
215
183
 
216
 
# FIXME: get rid of this method; use fixed_width() and avoid XML().
217
184
def html_clean(s):
218
185
    """
219
186
    clean up a string for html display.  expand any tabs, encode any html
221
188
    in displaying monospace text.
222
189
    """
223
190
    s = cgi.escape(s.expandtabs())
 
191
#    s = _BADCHARS_RE.sub(lambda x: '&#%d;' % (ord(x.group(0)),), s)
224
192
    s = s.replace(' ', '&nbsp;')
225
193
    return s
226
194
 
227
195
 
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
196
def fake_permissions(kind, executable):
250
197
    # fake up unix-style permissions given only a "kind" and executable bit
251
198
    if kind == 'directory':
304
251
        divisor = MEG
305
252
    else:
306
253
        divisor = KILO
307
 
 
 
254
    
308
255
    dot = size % divisor
309
256
    base = size - dot
310
257
    dot = dot * 10 // divisor
312
259
    if dot >= 10:
313
260
        base += 1
314
261
        dot -= 10
315
 
 
 
262
    
316
263
    out = str(base)
317
264
    if (base < 100) and (dot != 0):
318
265
        out += '.%d' % (dot,)
323
270
    elif divisor == GIG:
324
271
        out += 'G'
325
272
    return out
326
 
 
327
 
 
328
 
def fill_in_navigation(navigation):
 
273
    
 
274
 
 
275
def fill_in_navigation(history, navigation):
329
276
    """
330
277
    given a navigation block (used by the template for the page header), fill
331
278
    in useful calculated values.
332
279
    """
333
 
    if navigation.revid in navigation.revid_list: # XXX is this always true?
334
 
        navigation.position = navigation.revid_list.index(navigation.revid)
335
 
    else:
 
280
    navigation.position = history.get_revid_sequence(navigation.revid_list, navigation.revid)
 
281
    if navigation.position is None:
336
282
        navigation.position = 0
337
283
    navigation.count = len(navigation.revid_list)
338
284
    navigation.page_position = navigation.position // navigation.pagesize + 1
339
285
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
340
 
 
 
286
    
341
287
    def get_offset(offset):
342
288
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
343
289
            return None
344
290
        return navigation.revid_list[navigation.position + offset]
345
 
 
 
291
    
346
292
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
347
293
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
348
 
 
349
 
    params = { 'filter_file_id': navigation.filter_file_id }
 
294
    
 
295
    params = { 'file_id': navigation.file_id }
350
296
    if getattr(navigation, 'query', None) is not None:
351
297
        params['q'] = navigation.query
352
298
    else:
353
299
        params['start_revid'] = navigation.start_revid
354
 
 
 
300
        
355
301
    if navigation.prev_page_revid:
356
302
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
357
303
    if navigation.next_page_revid:
434
380
#         current location along the navigation path (while browsing)
435
381
#     - starting revid (start_revid)
436
382
#         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.
 
383
#         the original revision) -- this may not be along the primary revision
 
384
#         path since the user may have navigated into a branch
439
385
#     - file_id
440
 
#         the file being looked at
441
 
#     - filter_file_id
442
386
#         if navigating the revisions that touched a file
443
387
#     - q (query)
444
388
#         if navigating the revisions that matched a search query
455
399
#         for re-ordering an existing page by different sort
456
400
 
457
401
t_context = threading.local()
458
 
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
459
 
          'compare_revid', 'sort')
 
402
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
460
403
 
461
404
 
462
405
def set_context(map):
467
410
    """
468
411
    return a context map that may be overriden by specific values passed in,
469
412
    but only contains keys from the list of valid context keys.
470
 
 
 
413
    
471
414
    if 'clear' is set, only the 'remember' context value will be added, and
472
415
    all other context will be omitted.
473
416
    """