17
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33
log = logging.getLogger("loggerhead.controllers")
36
return value.strftime('%Y-%m-%d')
38
def displaydate(date):
39
delta = abs(datetime.datetime.now() - date)
40
if delta > datetime.timedelta(1, 0, 0):
41
# far in the past or future, display the date
42
return 'on ' + day_date(date)
43
return approximatedate(date)
45
def approximatedate(date):
46
delta = datetime.datetime.now() - date
47
if abs(delta) > datetime.timedelta(1, 0, 0):
48
# far in the past or future, display the date
50
future = delta < datetime.timedelta(0, 0, 0)
53
hours = delta.seconds / 3600
54
minutes = (delta.seconds - (3600*hours)) / 60
55
seconds = delta.seconds % 60
73
result += '%s %s' % (amount, unit)
78
def lp_format_date(date):
79
from elementtree import ElementTree as ET
80
elem = ET.Element("span")
81
elem.text = approximatedate(date)
82
elem.set("title", date.strftime("%Y-%m-%d %T"))
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')
86
50
class Container (object):
183
125
out.insert(0, -n)
186
# only do this if unicode turns out to be a problem
187
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
189
# FIXME: get rid of this method; use fixed_width() and avoid XML().
192
clean up a string for html display. expand any tabs, encode any html
193
entities, and replace spaces with ' '. this is primarily for use
194
in displaying monospace text.
196
s = cgi.escape(s.expandtabs())
197
s = s.replace(' ', ' ')
202
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
206
expand tabs and turn spaces into "non-breaking spaces", so browsers won't
209
if not isinstance(s, unicode):
210
# this kinda sucks. file contents are just binary data, and no
211
# encoding metadata is stored, so we need to guess. this is probably
212
# okay for most code, but for people using things like KOI-8, this
213
# will display gibberish. we have no way of detecting the correct
216
s = s.decode('utf-8')
217
except UnicodeDecodeError:
218
s = s.decode('iso-8859-15')
219
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
222
def fake_permissions(kind, executable):
223
# fake up unix-style permissions given only a "kind" and executable bit
224
if kind == 'directory':
231
def if_present(format, value):
233
format a value using a format string, if the value exists and is not None.
237
return format % value
241
s = base64.encodestring(s).replace('\n', '')
242
while (len(s) > 0) and (s[-1] == '='):
249
turn a potentially long string into a unique smaller string.
253
uniqs[type(None)] = next = uniqs.get(type(None), 0) + 1
254
x = struct.pack('>I', next)
255
while (len(x) > 1) and (x[0] == '\x00'):
264
P95_MEG = int(0.9 * MEG)
265
P95_GIG = int(0.9 * GIG)
267
def human_size(size, min_divisor=0):
269
if (size == 0) and (min_divisor == 0):
271
if (size < 512) and (min_divisor == 0):
274
if (size >= P95_GIG) or (min_divisor >= GIG):
276
elif (size >= P95_MEG) or (min_divisor >= MEG):
283
dot = dot * 10 // divisor
290
if (base < 100) and (dot != 0):
291
out += '.%d' % (dot,)
301
def fill_in_navigation(navigation):
303
given a navigation block (used by the template for the page header), fill
304
in useful calculated values.
306
if navigation.revid in navigation.revid_list: # XXX is this always true?
307
navigation.position = navigation.revid_list.index(navigation.revid)
309
navigation.position = 0
310
navigation.count = len(navigation.revid_list)
311
navigation.page_position = navigation.position // navigation.pagesize + 1
312
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
314
def get_offset(offset):
315
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
317
return navigation.revid_list[navigation.position + offset]
319
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
320
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
322
params = { 'file_id': navigation.file_id }
323
if getattr(navigation, 'query', None) is not None:
324
params['q'] = navigation.query
326
params['start_revid'] = navigation.start_revid
328
if navigation.prev_page_revid:
329
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
330
if navigation.next_page_revid:
331
navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
334
def log_exception(log):
335
for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
339
def decorator(unbound):
340
def new_decorator(f):
342
g.__name__ = f.__name__
343
g.__doc__ = f.__doc__
344
g.__dict__.update(f.__dict__)
346
new_decorator.__name__ = unbound.__name__
347
new_decorator.__doc__ = unbound.__doc__
348
new_decorator.__dict__.update(unbound.__dict__)
352
# common threading-lock decorator
353
def with_lock(lockname, debug_name=None):
354
if debug_name is None:
355
debug_name = lockname
357
def _decorator(unbound):
358
def locked(self, *args, **kw):
359
getattr(self, lockname).acquire()
361
return unbound(self, *args, **kw)
363
getattr(self, lockname).release()
369
def strip_whitespace(f):
373
out = re.sub(r'\n\s+', '\n', out)
374
out = re.sub(r'[ \t]+', ' ', out)
375
out = re.sub(r'\s+\n', '\n', out)
377
log.debug('Saved %sB (%d%%) by stripping whitespace.',
378
human_size(orig_len - new_len),
379
round(100.0 - float(new_len) * 100.0 / float(orig_len)))
387
from loggerhead.lsprof import profile
390
ret, stats = profile(f, *a, **kw)
391
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
395
msec = int(now * 1000) % 1000
396
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
397
filename = f.__name__ + '-' + timestr + '.lsprof'
398
cPickle.dump(stats, open(filename, 'w'), 2)
403
# just thinking out loud here...
405
# so, when browsing around, there are 5 pieces of context, most optional:
407
# current location along the navigation path (while browsing)
408
# - starting revid (start_revid)
409
# the current beginning of navigation (navigation continues back to
410
# the original revision) -- this may not be along the primary revision
411
# path since the user may have navigated into a branch
413
# if navigating the revisions that touched a file
415
# if navigating the revisions that matched a search query
417
# a previous revision to remember for future comparisons
419
# current revid is given on the url path. the rest are optional components
422
# other transient things can be set:
424
# to compare one revision to another, on /revision only
426
# for re-ordering an existing page by different sort
428
t_context = threading.local()
429
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
432
def set_context(map):
433
t_context.map = dict((k, v) for (k, v) in map.iteritems() if k in _valid)
436
def get_context(**overrides):
438
return a context map that may be overriden by specific values passed in,
439
but only contains keys from the list of valid context keys.
441
if 'clear' is set, only the 'remember' context value will be added, and
442
all other context will be omitted.
445
if overrides.get('clear', False):
446
map['remember'] = t_context.map.get('remember', None)
448
map.update(t_context.map)
449
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
450
map.update(overrides)