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
39
35
log = logging.getLogger("loggerhead.controllers")
41
return '%d years' % (int(delta.days // 365.25),)
43
return '%d days' % delta.days
49
seg.append('%d days' % delta.days)
50
hrs = delta.seconds // 3600
51
mins = (delta.seconds % 3600) // 60
56
seg.append('%d hours' % hrs)
60
seg.append('1 minute')
62
seg.append('%d minutes' % mins)
64
seg.append('less than a minute')
69
now = datetime.datetime.now()
70
return timespan(now - timestamp) + ' ago'
42
73
def fix_year(year):
51
# date_day -- just the day
52
# date_time -- full date with time
54
# displaydate -- for use in sentences
55
# approximatedate -- for use in tables
57
# displaydate and approximatedate return an elementtree <span> Element
58
# with the full date in a tooltip.
62
return value.strftime('%Y-%m-%d')
67
return value.strftime('%Y-%m-%d %T')
72
def _displaydate(date):
73
delta = abs(datetime.datetime.now() - date)
74
if delta > datetime.timedelta(1, 0, 0):
75
# far in the past or future, display the date
76
return 'on ' + date_day(date)
77
return _approximatedate(date)
80
def _approximatedate(date):
81
delta = datetime.datetime.now() - date
82
if abs(delta) > datetime.timedelta(1, 0, 0):
83
# far in the past or future, display the date
85
future = delta < datetime.timedelta(0, 0, 0)
88
hours = delta.seconds / 3600
89
minutes = (delta.seconds - (3600*hours)) / 60
90
seconds = delta.seconds % 60
108
result += '%s %s' % (amount, unit)
114
def _wrap_with_date_time_title(date, formatted_date):
115
elem = ET.Element("span")
116
elem.text = formatted_date
117
elem.set("title", date_time(date))
121
def approximatedate(date):
122
#FIXME: Returns an object instead of a string
123
return _wrap_with_date_time_title(date, _approximatedate(date))
126
def displaydate(date):
127
return _wrap_with_date_time_title(date, _displaydate(date))
130
81
class Container (object):
132
83
Convert a dict into an object with attributes.
135
85
def __init__(self, _dict=None, **kw):
136
86
if _dict is not None:
137
87
for key, value in _dict.iteritems():
138
88
setattr(self, key, value)
139
89
for key, value in kw.iteritems():
140
90
setattr(self, key, value)
142
92
def __repr__(self):
144
94
for key, value in self.__dict__.iteritems():
145
if key.startswith('_') or (getattr(self.__dict__[key],
146
'__call__', None) is not None):
95
if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
148
97
out += '%r => %r, ' % (key, value)
102
def clean_revid(revid):
103
if revid == 'missing':
105
return sha.new(revid).hexdigest()
109
return ''.join([ '&#%d;' % ord(c) for c in text ])
153
112
def trunc(text, limit=10):
154
113
if len(text) <= limit:
156
115
return text[:limit] + '...'
119
if isinstance(s, unicode):
120
return s.encode('utf-8')
159
124
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
160
125
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
163
127
def hide_email(email):
165
129
try to obsure any email address in a bazaar committer's name.
179
143
return '%s at %s' % (username, domains[-2])
180
144
return '%s at %s' % (username, domains[0])
147
def triple_factors(min_value=1):
153
yield n * factors[index]
155
if index >= len(factors):
160
def scan_range(pos, max, pagesize=1):
162
given a position in a maximum range, return a list of negative and positive
163
jump factors for an hgweb-style triple-factor geometric scan.
165
for example, with pos=20 and max=500, the range would be:
166
[ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
168
i admit this is a very strange way of jumping through revisions. i didn't
172
for n in triple_factors(pagesize + 1):
183
181
# only do this if unicode turns out to be a problem
184
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
186
184
# FIXME: get rid of this method; use fixed_width() and avoid XML().
189
185
def html_clean(s):
191
187
clean up a string for html display. expand any tabs, encode any html
323
304
navigation.position = 0
324
305
navigation.count = len(navigation.revid_list)
325
306
navigation.page_position = navigation.position // navigation.pagesize + 1
326
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
327
- 1)) // navigation.pagesize
307
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
329
309
def get_offset(offset):
330
if (navigation.position + offset < 0) or (
331
navigation.position + offset > navigation.count - 1):
310
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
333
312
return navigation.revid_list[navigation.position + offset]
335
navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
336
314
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
337
315
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
338
prev_page_revno = navigation.history.get_revno(
339
navigation.prev_page_revid)
340
next_page_revno = navigation.history.get_revno(
341
navigation.next_page_revid)
342
start_revno = navigation.history.get_revno(navigation.start_revid)
344
params = {'filter_file_id': navigation.filter_file_id}
317
params = { 'file_id': navigation.file_id }
345
318
if getattr(navigation, 'query', None) is not None:
346
319
params['q'] = navigation.query
348
if getattr(navigation, 'start_revid', None) is not None:
349
params['start_revid'] = start_revno
321
params['start_revid'] = navigation.start_revid
351
323
if navigation.prev_page_revid:
352
navigation.prev_page_url = navigation.branch.context_url(
353
[navigation.scan_url, prev_page_revno], **params)
324
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
354
325
if navigation.next_page_revid:
355
navigation.next_page_url = navigation.branch.context_url(
356
[navigation.scan_url, next_page_revno], **params)
359
def directory_breadcrumbs(path, is_root, view):
361
Generate breadcrumb information from the directory path given
363
The path given should be a path up to any branch that is currently being
367
path -- The path to convert into breadcrumbs
368
is_root -- Whether or not loggerhead is serving a branch at its root
369
view -- The type of view we are showing (files, changes etc)
371
# Is our root directory itself a branch?
373
if view == 'directory':
381
# Create breadcrumb trail for the path leading up to the branch
383
'dir_name': "(root)",
388
dir_parts = path.strip('/').split('/')
389
for index, dir_name in enumerate(dir_parts):
391
'dir_name': dir_name,
392
'path': '/'.join(dir_parts[:index + 1]),
395
# If we are not in the directory view, the last crumb is a branch,
396
# so we need to specify a view
397
if view != 'directory':
398
breadcrumbs[-1]['suffix'] = '/' + view
402
def branch_breadcrumbs(path, inv, view):
404
Generate breadcrumb information from the branch path given
406
The path given should be a path that exists within a branch
409
path -- The path to convert into breadcrumbs
410
inv -- Inventory to get file information from
411
view -- The type of view we are showing (files, changes etc)
413
dir_parts = path.strip('/').split('/')
414
inner_breadcrumbs = []
415
for index, dir_name in enumerate(dir_parts):
416
inner_breadcrumbs.append({
417
'dir_name': dir_name,
418
'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
419
'suffix': '/' + view,
421
return inner_breadcrumbs
326
navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
329
def log_exception(log):
330
for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
424
334
def decorator(unbound):
426
335
def new_decorator(f):
428
337
g.__name__ = f.__name__
364
def strip_whitespace(f):
368
out = re.sub(r'\n\s+', '\n', out)
369
out = re.sub(r'[ \t]+', ' ', out)
370
out = re.sub(r'\s+\n', '\n', out)
372
log.debug('Saved %sB (%d%%) by stripping whitespace.',
373
human_size(orig_len - new_len),
374
round(100.0 - float(new_len) * 100.0 / float(orig_len)))
461
381
def _f(*a, **kw):
462
382
from loggerhead.lsprof import profile
465
385
ret, stats = profile(f, *a, **kw)
466
log.debug('Finished profiled %s in %d msec.' % (f.__name__,
467
int((time.time() - z) * 1000)))
386
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
470
389
now = time.time()
471
390
msec = int(now * 1000) % 1000
472
timestr = time.strftime('%Y%m%d%H%M%S',
473
time.localtime(now)) + ('%03d' % msec)
391
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
474
392
filename = f.__name__ + '-' + timestr + '.lsprof'
475
393
cPickle.dump(stats, open(filename, 'w'), 2)
532
444
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
533
445
map.update(overrides)
537
class Reloader(object):
539
This class wraps all paste.reloader logic. All methods are @classmethod.
542
_reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
545
def _turn_sigterm_into_systemexit(self):
547
Attempts to turn a SIGTERM exception into a SystemExit exception.
554
def handle_term(signo, frame):
556
signal.signal(signal.SIGTERM, handle_term)
559
def is_installed(self):
560
return os.environ.get(self._reloader_environ_key)
564
from paste import reloader
565
reloader.install(int(1))
568
def restart_with_reloader(self):
569
"""Based on restart_with_monitor from paste.script.serve."""
570
print 'Starting subprocess with file monitor'
572
args = [sys.executable] + sys.argv
573
new_environ = os.environ.copy()
574
new_environ[self._reloader_environ_key] = 'true'
578
self._turn_sigterm_into_systemexit()
579
proc = subprocess.Popen(args, env=new_environ)
580
exit_code = proc.wait()
582
except KeyboardInterrupt:
583
print '^C caught in monitor process'
587
and hasattr(os, 'kill')):
590
os.kill(proc.pid, signal.SIGTERM)
591
except (OSError, IOError):
594
# Reloader always exits with code 3; but if we are
595
# a monitor, any exit code will restart
598
print '-'*20, 'Restarting', '-'*20