17
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from xml.etree import ElementTree as ET
23
from elementtree import ElementTree as ET
38
log = logging.getLogger("loggerhead.controllers")
49
# date_day -- just the day
50
# date_time -- full date with time
52
# displaydate -- for use in sentences
53
# approximatedate -- for use in tables
55
# displaydate and approximatedate return an elementtree <span> Element
56
# with the full date in a tooltip.
59
return value.strftime('%Y-%m-%d')
63
return value.strftime('%Y-%m-%d %T')
66
def _displaydate(date):
67
delta = abs(datetime.datetime.now() - date)
68
if delta > datetime.timedelta(1, 0, 0):
69
# far in the past or future, display the date
70
return 'on ' + date_day(date)
71
return _approximatedate(date)
74
def _approximatedate(date):
75
delta = datetime.datetime.now() - date
76
if abs(delta) > datetime.timedelta(1, 0, 0):
77
# far in the past or future, display the date
79
future = delta < datetime.timedelta(0, 0, 0)
82
hours = delta.seconds / 3600
83
minutes = (delta.seconds - (3600*hours)) / 60
84
seconds = delta.seconds % 60
102
result += '%s %s' % (amount, unit)
108
def _wrap_with_date_time_title(date, formatted_date):
109
elem = ET.Element("span")
110
elem.text = formatted_date
111
elem.set("title", date_time(date))
115
def approximatedate(date):
116
#FIXME: Returns an object instead of a string
117
return _wrap_with_date_time_title(date, _approximatedate(date))
120
def displaydate(date):
121
return _wrap_with_date_time_title(date, _displaydate(date))
25
return '%d days' % delta.days
31
seg.append('%d days' % delta.days)
32
hrs = delta.seconds // 3600
33
mins = (delta.seconds % 3600) // 60
38
seg.append('%d hours' % hrs)
42
seg.append('1 minute')
44
seg.append('%d minutes' % mins)
46
seg.append('less than a minute')
124
50
class Container (object):
221
125
out.insert(0, -n)
224
# only do this if unicode turns out to be a problem
225
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
227
# FIXME: get rid of this method; use fixed_width() and avoid XML().
230
clean up a string for html display. expand any tabs, encode any html
231
entities, and replace spaces with ' '. this is primarily for use
232
in displaying monospace text.
234
s = cgi.escape(s.expandtabs())
235
s = s.replace(' ', ' ')
240
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
244
expand tabs and turn spaces into "non-breaking spaces", so browsers won't
247
if not isinstance(s, unicode):
248
# this kinda sucks. file contents are just binary data, and no
249
# encoding metadata is stored, so we need to guess. this is probably
250
# okay for most code, but for people using things like KOI-8, this
251
# will display gibberish. we have no way of detecting the correct
254
s = s.decode('utf-8')
255
except UnicodeDecodeError:
256
s = s.decode('iso-8859-15')
257
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
260
def fake_permissions(kind, executable):
261
# fake up unix-style permissions given only a "kind" and executable bit
262
if kind == 'directory':
269
def if_present(format, value):
271
format a value using a format string, if the value exists and is not None.
275
return format % value
279
s = base64.encodestring(s).replace('\n', '')
280
while (len(s) > 0) and (s[-1] == '='):
287
turn a potentially long string into a unique smaller string.
291
uniqs[type(None)] = next = uniqs.get(type(None), 0) + 1
292
x = struct.pack('>I', next)
293
while (len(x) > 1) and (x[0] == '\x00'):
302
P95_MEG = int(0.9 * MEG)
303
P95_GIG = int(0.9 * GIG)
305
def human_size(size, min_divisor=0):
307
if (size == 0) and (min_divisor == 0):
309
if (size < 512) and (min_divisor == 0):
312
if (size >= P95_GIG) or (min_divisor >= GIG):
314
elif (size >= P95_MEG) or (min_divisor >= MEG):
321
dot = dot * 10 // divisor
328
if (base < 100) and (dot != 0):
329
out += '.%d' % (dot,)
339
def fill_in_navigation(navigation):
341
given a navigation block (used by the template for the page header), fill
342
in useful calculated values.
344
if navigation.revid in navigation.revid_list: # XXX is this always true?
345
navigation.position = navigation.revid_list.index(navigation.revid)
347
navigation.position = 0
348
navigation.count = len(navigation.revid_list)
349
navigation.page_position = navigation.position // navigation.pagesize + 1
350
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
352
def get_offset(offset):
353
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
355
return navigation.revid_list[navigation.position + offset]
357
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
358
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
359
prev_page_revno = navigation.branch.history.get_revno(
360
navigation.prev_page_revid)
361
next_page_revno = navigation.branch.history.get_revno(
362
navigation.next_page_revid)
363
start_revno = navigation.branch._history.get_revno(navigation.start_revid)
365
params = { 'filter_file_id': navigation.filter_file_id }
366
if getattr(navigation, 'query', None) is not None:
367
params['q'] = navigation.query
369
if getattr(navigation, 'start_revid', None) is not None:
370
params['start_revid'] = start_revno
372
if navigation.prev_page_revid:
373
navigation.prev_page_url = navigation.branch.context_url(
374
[navigation.scan_url, prev_page_revno], **params)
375
if navigation.next_page_revid:
376
navigation.next_page_url = navigation.branch.context_url(
377
[navigation.scan_url, next_page_revno], **params)
380
def log_exception(log):
381
for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
385
def decorator(unbound):
386
def new_decorator(f):
388
g.__name__ = f.__name__
389
g.__doc__ = f.__doc__
390
g.__dict__.update(f.__dict__)
392
new_decorator.__name__ = unbound.__name__
393
new_decorator.__doc__ = unbound.__doc__
394
new_decorator.__dict__.update(unbound.__dict__)
398
# common threading-lock decorator
399
def with_lock(lockname, debug_name=None):
400
if debug_name is None:
401
debug_name = lockname
403
def _decorator(unbound):
404
def locked(self, *args, **kw):
405
getattr(self, lockname).acquire()
407
return unbound(self, *args, **kw)
409
getattr(self, lockname).release()
415
def strip_whitespace(f):
419
out = re.sub(r'\n\s+', '\n', out)
420
out = re.sub(r'[ \t]+', ' ', out)
421
out = re.sub(r'\s+\n', '\n', out)
423
log.debug('Saved %sB (%d%%) by stripping whitespace.',
424
human_size(orig_len - new_len),
425
round(100.0 - float(new_len) * 100.0 / float(orig_len)))
433
from loggerhead.lsprof import profile
436
ret, stats = profile(f, *a, **kw)
437
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
441
msec = int(now * 1000) % 1000
442
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
443
filename = f.__name__ + '-' + timestr + '.lsprof'
444
cPickle.dump(stats, open(filename, 'w'), 2)
449
# just thinking out loud here...
451
# so, when browsing around, there are 5 pieces of context, most optional:
453
# current location along the navigation path (while browsing)
454
# - starting revid (start_revid)
455
# the current beginning of navigation (navigation continues back to
456
# the original revision) -- this defines an 'alternate mainline'
457
# when the user navigates into a branch.
459
# the file being looked at
461
# if navigating the revisions that touched a file
463
# if navigating the revisions that matched a search query
465
# a previous revision to remember for future comparisons
467
# current revid is given on the url path. the rest are optional components
470
# other transient things can be set:
472
# to compare one revision to another, on /revision only
474
# for re-ordering an existing page by different sort
476
t_context = threading.local()
477
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
478
'compare_revid', 'sort')
481
def set_context(map):
482
t_context.map = dict((k, v) for (k, v) in map.iteritems() if k in _valid)
485
def get_context(**overrides):
487
Soon to be deprecated.
490
return a context map that may be overriden by specific values passed in,
491
but only contains keys from the list of valid context keys.
493
if 'clear' is set, only the 'remember' context value will be added, and
494
all other context will be omitted.
497
if overrides.get('clear', False):
498
map['remember'] = t_context.map.get('remember', None)
500
map.update(t_context.map)
501
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
502
map.update(overrides)