17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
from elementtree import ElementTree as ET
30
35
log = logging.getLogger("loggerhead.controllers")
36
return '%d years' % (int(delta.days // 365.25),)
38
return '%d days' % delta.days
44
seg.append('%d days' % delta.days)
45
hrs = delta.seconds // 3600
46
mins = (delta.seconds % 3600) // 60
51
seg.append('%d hours' % hrs)
55
seg.append('1 minute')
57
seg.append('%d minutes' % mins)
59
seg.append('less than a minute')
64
now = datetime.datetime.now()
65
return timespan(now - timestamp) + ' ago'
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))
68
113
class Container (object):
159
210
out.insert(0, -n)
213
# only do this if unicode turns out to be a problem
214
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
216
# FIXME: get rid of this method; use fixed_width() and avoid XML().
162
217
def html_clean(s):
164
219
clean up a string for html display. expand any tabs, encode any html
165
220
entities, and replace spaces with ' '. this is primarily for use
166
221
in displaying monospace text.
168
s = cgi.escape(s.expandtabs()).replace(' ', ' ')
223
s = cgi.escape(s.expandtabs())
224
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)
172
249
def fake_permissions(kind, executable):
173
250
# fake up unix-style permissions given only a "kind" and executable bit
174
251
if kind == 'directory':
187
264
return format % value
190
def fill_in_navigation(history, navigation):
268
s = base64.encodestring(s).replace('\n', '')
269
while (len(s) > 0) and (s[-1] == '='):
276
turn a potentially long string into a unique smaller string.
280
uniqs[type(None)] = next = uniqs.get(type(None), 0) + 1
281
x = struct.pack('>I', next)
282
while (len(x) > 1) and (x[0] == '\x00'):
291
P95_MEG = int(0.9 * MEG)
292
P95_GIG = int(0.9 * GIG)
294
def human_size(size, min_divisor=0):
296
if (size == 0) and (min_divisor == 0):
298
if (size < 512) and (min_divisor == 0):
301
if (size >= P95_GIG) or (min_divisor >= GIG):
303
elif (size >= P95_MEG) or (min_divisor >= MEG):
310
dot = dot * 10 // divisor
317
if (base < 100) and (dot != 0):
318
out += '.%d' % (dot,)
328
def fill_in_navigation(navigation):
192
330
given a navigation block (used by the template for the page header), fill
193
331
in useful calculated values.
195
navigation.position = history.get_revid_sequence(navigation.revlist, navigation.revid)
196
navigation.count = len(navigation.revlist)
333
if navigation.revid in navigation.revid_list: # XXX is this always true?
334
navigation.position = navigation.revid_list.index(navigation.revid)
336
navigation.position = 0
337
navigation.count = len(navigation.revid_list)
197
338
navigation.page_position = navigation.position // navigation.pagesize + 1
198
navigation.page_count = (len(navigation.revlist) + (navigation.pagesize - 1)) // navigation.pagesize
339
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
200
341
def get_offset(offset):
201
342
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
203
return navigation.revlist[navigation.position + offset]
344
return navigation.revid_list[navigation.position + offset]
205
346
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
206
347
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
349
params = { 'filter_file_id': navigation.filter_file_id }
350
if getattr(navigation, 'query', None) is not None:
351
params['q'] = navigation.query
353
params['start_revid'] = navigation.start_revid
207
355
if navigation.prev_page_revid:
208
navigation.prev_page_url = turbogears.url([ navigation.scan_url, navigation.prev_page_revid ], path=navigation.path, start_revid=navigation.start_revid)
356
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
209
357
if navigation.next_page_revid:
210
navigation.next_page_url = turbogears.url([ navigation.scan_url, navigation.next_page_revid ], path=navigation.path, start_revid=navigation.start_revid)
213
# global branch history & cache
216
_history_lock = threading.Lock()
220
from loggerhead.history import History
222
config = get_config()
224
_history_lock.acquire()
226
if (_history is None) or _history.out_of_date():
227
log.debug('Reload branch history...')
228
if _history is not None:
229
_history.dont_use_cache()
230
_history = History.from_folder(config.get('folder'))
231
_history.use_cache(config.get('cachepath'))
234
_history_lock.release()
239
def set_config(config):
358
navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
361
def log_exception(log):
362
for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
366
def decorator(unbound):
367
def new_decorator(f):
369
g.__name__ = f.__name__
370
g.__doc__ = f.__doc__
371
g.__dict__.update(f.__dict__)
373
new_decorator.__name__ = unbound.__name__
374
new_decorator.__doc__ = unbound.__doc__
375
new_decorator.__dict__.update(unbound.__dict__)
379
# common threading-lock decorator
380
def with_lock(lockname, debug_name=None):
381
if debug_name is None:
382
debug_name = lockname
384
def _decorator(unbound):
385
def locked(self, *args, **kw):
386
getattr(self, lockname).acquire()
388
return unbound(self, *args, **kw)
390
getattr(self, lockname).release()
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)