19
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
from xml.etree import ElementTree as ET
25
from elementtree import ElementTree as ET
27
from simpletal.simpleTALUtils import HTMLStructureCleaner
41
30
log = logging.getLogger("loggerhead.controllers")
53
# date_day -- just the day
54
# date_time -- full date with time
56
# displaydate -- for use in sentences
57
# approximatedate -- for use in tables
59
# displaydate and approximatedate return an elementtree <span> Element
60
# with the full date in a tooltip.
64
return value.strftime('%Y-%m-%d')
69
return value.strftime('%Y-%m-%d %T')
74
def _displaydate(date):
75
delta = abs(datetime.datetime.now() - date)
76
if delta > datetime.timedelta(1, 0, 0):
77
# far in the past or future, display the date
78
return 'on ' + date_day(date)
79
return _approximatedate(date)
82
def _approximatedate(date):
83
delta = datetime.datetime.now() - date
84
if abs(delta) > datetime.timedelta(1, 0, 0):
85
# far in the past or future, display the date
87
future = delta < datetime.timedelta(0, 0, 0)
90
hours = delta.seconds / 3600
91
minutes = (delta.seconds - (3600*hours)) / 60
92
seconds = delta.seconds % 60
110
result += '%s %s' % (amount, unit)
116
def _wrap_with_date_time_title(date, formatted_date):
117
elem = ET.Element("span")
118
elem.text = formatted_date
119
elem.set("title", date_time(date))
123
def approximatedate(date):
124
#FIXME: Returns an object instead of a string
125
return _wrap_with_date_time_title(date, _approximatedate(date))
128
def displaydate(date):
129
return _wrap_with_date_time_title(date, _displaydate(date))
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'
132
68
class Container (object):
134
70
Convert a dict into an object with attributes.
137
72
def __init__(self, _dict=None, **kw):
138
73
if _dict is not None:
139
74
for key, value in _dict.iteritems():
140
75
setattr(self, key, value)
141
76
for key, value in kw.iteritems():
142
77
setattr(self, key, value)
144
79
def __repr__(self):
146
81
for key, value in self.__dict__.iteritems():
147
if key.startswith('_') or (getattr(self.__dict__[key],
148
'__call__', None) is not None):
82
if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
150
84
out += '%r => %r, ' % (key, value)
89
def clean_revid(revid):
90
if revid == 'missing':
92
return sha.new(revid).hexdigest()
96
return ''.join([ '&#%d;' % ord(c) for c in text ])
155
99
def trunc(text, limit=10):
156
100
if len(text) <= limit:
264
178
return '-rw-r--r--'
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)
295
def human_size(size, min_divisor=0):
297
if (size == 0) and (min_divisor == 0):
299
if (size < 512) and (min_divisor == 0):
302
if (size >= P95_GIG) or (min_divisor >= GIG):
304
elif (size >= P95_MEG) or (min_divisor >= MEG):
311
dot = dot * 10 // divisor
318
if (base < 100) and (dot != 0):
329
def fill_in_navigation(navigation):
181
def if_present(format, value):
183
format a value using a format string, if the value exists and is not None.
187
return format % value
190
def fill_in_navigation(history, navigation):
331
192
given a navigation block (used by the template for the page header), fill
332
193
in useful calculated values.
334
if navigation.revid in navigation.revid_list: # XXX is this always true?
335
navigation.position = navigation.revid_list.index(navigation.revid)
337
navigation.position = 0
338
navigation.count = len(navigation.revid_list)
195
navigation.position = history.get_revid_sequence(navigation.revlist, navigation.revid)
196
navigation.count = len(navigation.revlist)
339
197
navigation.page_position = navigation.position // navigation.pagesize + 1
340
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
341
- 1)) // navigation.pagesize
198
navigation.page_count = (len(navigation.revlist) + (navigation.pagesize - 1)) // navigation.pagesize
343
200
def get_offset(offset):
344
if (navigation.position + offset < 0) or (
345
navigation.position + offset > navigation.count - 1):
201
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
347
return navigation.revid_list[navigation.position + offset]
349
navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
203
return navigation.revlist[navigation.position + offset]
350
205
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
351
206
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
352
prev_page_revno = navigation.history.get_revno(
353
navigation.prev_page_revid)
354
next_page_revno = navigation.history.get_revno(
355
navigation.next_page_revid)
356
start_revno = navigation.history.get_revno(navigation.start_revid)
358
params = {'filter_file_id': navigation.filter_file_id}
359
if getattr(navigation, 'query', None) is not None:
360
params['q'] = navigation.query
362
if getattr(navigation, 'start_revid', None) is not None:
363
params['start_revid'] = start_revno
365
207
if navigation.prev_page_revid:
366
navigation.prev_page_url = navigation.branch.context_url(
367
[navigation.scan_url, prev_page_revno], **params)
208
navigation.prev_page_url = turbogears.url([ navigation.scan_url, navigation.prev_page_revid ], path=navigation.path, start_revid=navigation.start_revid)
368
209
if navigation.next_page_revid:
369
navigation.next_page_url = navigation.branch.context_url(
370
[navigation.scan_url, next_page_revno], **params)
373
def directory_breadcrumbs(path, is_root, view):
375
Generate breadcrumb information from the directory path given
377
The path given should be a path up to any branch that is currently being
381
path -- The path to convert into breadcrumbs
382
is_root -- Whether or not loggerhead is serving a branch at its root
383
view -- The type of view we are showing (files, changes etc)
385
# Is our root directory itself a branch?
393
# Create breadcrumb trail for the path leading up to the branch
395
'dir_name': "(root)",
400
dir_parts = path.strip('/').split('/')
401
for index, dir_name in enumerate(dir_parts):
403
'dir_name': dir_name,
404
'path': '/'.join(dir_parts[:index + 1]),
407
# If we are not in the directory view, the last crumb is a branch,
408
# so we need to specify a view
409
if view != 'directory':
410
breadcrumbs[-1]['suffix'] = '/' + view
414
def branch_breadcrumbs(path, inv, view):
416
Generate breadcrumb information from the branch path given
418
The path given should be a path that exists within a branch
421
path -- The path to convert into breadcrumbs
422
inv -- Inventory to get file information from
423
view -- The type of view we are showing (files, changes etc)
425
dir_parts = path.strip('/').split('/')
426
inner_breadcrumbs = []
427
for index, dir_name in enumerate(dir_parts):
428
inner_breadcrumbs.append({
429
'dir_name': dir_name,
430
'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
431
'suffix': '/' + view,
433
return inner_breadcrumbs
436
def decorator(unbound):
438
def new_decorator(f):
440
g.__name__ = f.__name__
441
g.__doc__ = f.__doc__
442
g.__dict__.update(f.__dict__)
444
new_decorator.__name__ = unbound.__name__
445
new_decorator.__doc__ = unbound.__doc__
446
new_decorator.__dict__.update(unbound.__dict__)
455
from loggerhead.lsprof import profile
458
ret, stats = profile(f, *a, **kw)
459
log.debug('Finished profiled %s in %d msec.' % (f.__name__,
460
int((time.time() - z) * 1000)))
464
msec = int(now * 1000) % 1000
465
timestr = time.strftime('%Y%m%d%H%M%S',
466
time.localtime(now)) + ('%03d' % msec)
467
filename = f.__name__ + '-' + timestr + '.lsprof'
468
cPickle.dump(stats, open(filename, 'w'), 2)
473
# just thinking out loud here...
475
# so, when browsing around, there are 5 pieces of context, most optional:
477
# current location along the navigation path (while browsing)
478
# - starting revid (start_revid)
479
# the current beginning of navigation (navigation continues back to
480
# the original revision) -- this defines an 'alternate mainline'
481
# when the user navigates into a branch.
483
# the file being looked at
485
# if navigating the revisions that touched a file
487
# if navigating the revisions that matched a search query
489
# a previous revision to remember for future comparisons
491
# current revid is given on the url path. the rest are optional components
494
# other transient things can be set:
496
# to compare one revision to another, on /revision only
498
# for re-ordering an existing page by different sort
500
t_context = threading.local()
501
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
502
'compare_revid', 'sort')
505
def set_context(map):
506
t_context.map = dict((k, v) for (k, v) in map.iteritems() if k in _valid)
509
def get_context(**overrides):
511
Soon to be deprecated.
514
return a context map that may be overriden by specific values passed in,
515
but only contains keys from the list of valid context keys.
517
if 'clear' is set, only the 'remember' context value will be added, and
518
all other context will be omitted.
521
if overrides.get('clear', False):
522
map['remember'] = t_context.map.get('remember', None)
524
map.update(t_context.map)
525
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
526
map.update(overrides)
530
class Reloader(object):
532
This class wraps all paste.reloader logic. All methods are @classmethod.
535
_reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
538
def _turn_sigterm_into_systemexit(self):
540
Attempts to turn a SIGTERM exception into a SystemExit exception.
547
def handle_term(signo, frame):
549
signal.signal(signal.SIGTERM, handle_term)
552
def is_installed(self):
553
return os.environ.get(self._reloader_environ_key)
557
from paste import reloader
558
reloader.install(int(1))
561
def restart_with_reloader(self):
562
"""Based on restart_with_monitor from paste.script.serve."""
563
print 'Starting subprocess with file monitor'
565
args = [sys.executable] + sys.argv
566
new_environ = os.environ.copy()
567
new_environ[self._reloader_environ_key] = 'true'
571
self._turn_sigterm_into_systemexit()
572
proc = subprocess.Popen(args, env=new_environ)
573
exit_code = proc.wait()
575
except KeyboardInterrupt:
576
print '^C caught in monitor process'
580
and hasattr(os, 'kill')):
583
os.kill(proc.pid, signal.SIGTERM)
584
except (OSError, IOError):
587
# Reloader always exits with code 3; but if we are
588
# a monitor, any exit code will restart
591
print '-'*20, 'Restarting', '-'*20
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):