35
34
log = logging.getLogger("loggerhead.controllers")
39
# date_day -- just the day
40
# date_time -- full date with time
42
# displaydate -- for use in sentences
43
# approximatedate -- for use in tables
45
# displaydate and approximatedate return an elementtree <span> Element
46
# with the full date in a tooltip.
49
return value.strftime('%Y-%m-%d')
53
return value.strftime('%Y-%m-%d %T')
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)
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
69
future = delta < datetime.timedelta(0, 0, 0)
72
hours = delta.seconds / 3600
73
minutes = (delta.seconds - (3600*hours)) / 60
74
seconds = delta.seconds % 60
92
result += '%s %s' % (amount, unit)
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))
105
def approximatedate(date):
106
return _wrap_with_date_time_title(date, _approximatedate(date))
109
def displaydate(date):
110
return _wrap_with_date_time_title(date, _displaydate(date))
40
return '%d years' % (int(delta.days // 365.25),)
42
return '%d days' % delta.days
48
seg.append('%d days' % delta.days)
49
hrs = delta.seconds // 3600
50
mins = (delta.seconds % 3600) // 60
55
seg.append('%d hours' % hrs)
59
seg.append('1 minute')
61
seg.append('%d minutes' % mins)
63
seg.append('less than a minute')
68
now = datetime.datetime.now()
69
return timespan(now - timestamp) + ' ago'
113
80
class Container (object):
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.
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 ]
200
167
i admit this is a very strange way of jumping through revisions. i didn't
221
187
in displaying monospace text.
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(' ', ' ')
229
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
233
expand tabs and turn spaces into "non-breaking spaces", so browsers won't
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
243
s = s.decode('utf-8')
244
except UnicodeDecodeError:
245
s = s.decode('iso-8859-15')
246
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
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':
323
269
elif divisor == GIG:
328
def fill_in_navigation(navigation):
274
def fill_in_navigation(history, navigation):
330
276
given a navigation block (used by the template for the page header), fill
331
277
in useful calculated values.
333
if navigation.revid in navigation.revid_list: # XXX is this always true?
334
navigation.position = navigation.revid_list.index(navigation.revid)
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
341
286
def get_offset(offset):
342
287
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
344
289
return navigation.revid_list[navigation.position + offset]
346
291
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
347
292
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
349
params = { 'filter_file_id': navigation.filter_file_id }
294
params = { 'file_id': navigation.file_id }
350
295
if getattr(navigation, 'query', None) is not None:
351
296
params['q'] = navigation.query
353
298
params['start_revid'] = navigation.start_revid
355
300
if navigation.prev_page_revid:
356
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
301
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **params)
357
302
if navigation.next_page_revid:
358
navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
303
navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **params)
361
306
def log_exception(log):
384
329
def _decorator(unbound):
385
330
def locked(self, *args, **kw):
331
#self.log.debug('-> %r lock %r', id(threading.currentThread()), debug_name)
386
332
getattr(self, lockname).acquire()
388
334
return unbound(self, *args, **kw)
390
336
getattr(self, lockname).release()
337
#self.log.debug('<- %r unlock %r', id(threading.currentThread()), debug_name)
392
339
return _decorator
396
def strip_whitespace(f):
400
out = re.sub(r'\n\s+', '\n', out)
401
out = re.sub(r'[ \t]+', ' ', out)
402
out = re.sub(r'\s+\n', '\n', out)
404
log.debug('Saved %sB (%d%%) by stripping whitespace.',
405
human_size(orig_len - new_len),
406
round(100.0 - float(new_len) * 100.0 / float(orig_len)))
414
from loggerhead.lsprof import profile
417
ret, stats = profile(f, *a, **kw)
418
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
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)
430
# just thinking out loud here...
432
# so, when browsing around, there are 5 pieces of context, most optional:
434
# current location along the navigation path (while browsing)
435
# - starting revid (start_revid)
436
# 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.
440
# the file being looked at
442
# if navigating the revisions that touched a file
444
# if navigating the revisions that matched a search query
446
# a previous revision to remember for future comparisons
448
# current revid is given on the url path. the rest are optional components
451
# other transient things can be set:
453
# to compare one revision to another, on /revision only
455
# for re-ordering an existing page by different sort
457
t_context = threading.local()
458
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
459
'compare_revid', 'sort')
462
def set_context(map):
463
t_context.map = dict((k, v) for (k, v) in map.iteritems() if k in _valid)
466
def get_context(**overrides):
468
return a context map that may be overriden by specific values passed in,
469
but only contains keys from the list of valid context keys.
471
if 'clear' is set, only the 'remember' context value will be added, and
472
all other context will be omitted.
475
if overrides.get('clear', False):
476
map['remember'] = t_context.map.get('remember', None)
478
map.update(t_context.map)
479
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
480
map.update(overrides)