19
19
# 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
35
from xml.etree import ElementTree as ET
37
from elementtree import ElementTree as ET
39
from bzrlib import urlutils
41
from simpletal.simpleTALUtils import HTMLStructureCleaner
37
43
log = logging.getLogger("loggerhead.controllers")
39
46
def fix_year(year):
54
61
# displaydate and approximatedate return an elementtree <span> Element
55
62
# with the full date in a tooltip.
57
65
def date_day(value):
58
66
return value.strftime('%Y-%m-%d')
61
69
def date_time(value):
62
return value.strftime('%Y-%m-%d %T')
71
return value.strftime('%Y-%m-%d %H:%M:%S')
65
76
def _displaydate(date):
120
131
return _wrap_with_date_time_title(date, _displaydate(date))
123
class Container (object):
134
class Container(object):
125
136
Convert a dict into an object with attributes.
127
139
def __init__(self, _dict=None, **kw):
140
self._properties = {}
128
141
if _dict is not None:
129
142
for key, value in _dict.iteritems():
130
143
setattr(self, key, value)
134
147
def __repr__(self):
136
149
for key, value in self.__dict__.iteritems():
137
if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
150
if key.startswith('_') or (getattr(self.__dict__[key],
151
'__call__', None) is not None):
139
153
out += '%r => %r, ' % (key, value)
157
def __getattr__(self, attr):
158
"""Used for handling things that aren't already available."""
159
if attr.startswith('_') or attr not in self._properties:
160
raise AttributeError('No attribute: %s' % (attr,))
161
val = self._properties[attr](self, attr)
162
setattr(self, attr, val)
165
def _set_property(self, attr, prop_func):
166
"""Set a function that will be called when an attribute is desired.
168
We will cache the return value, so the function call should be
169
idempotent. We will pass 'self' and the 'attr' name when triggered.
171
if attr.startswith('_'):
172
raise ValueError("Cannot create properties that start with _")
173
self._properties[attr] = prop_func
144
176
def trunc(text, limit=10):
145
177
if len(text) <= limit:
169
202
return '%s at %s' % (username, domains[-2])
170
203
return '%s at %s' % (username, domains[0])
205
def hide_emails(emails):
207
try to obscure any email address in a list of bazaar committers' names.
211
result.append(hide_email(email))
173
214
# only do this if unicode turns out to be a problem
174
215
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
176
217
# FIXME: get rid of this method; use fixed_width() and avoid XML().
177
220
def html_clean(s):
179
222
clean up a string for html display. expand any tabs, encode any html
224
268
s = s.decode('utf-8')
225
269
except UnicodeDecodeError:
226
270
s = s.decode('iso-8859-15')
227
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
272
s = cgi.escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
274
return HSC.clean(s).replace('\n', '<br/>')
230
277
def fake_permissions(kind, executable):
348
def local_path_from_url(url):
349
"""Convert Bazaar URL to local path, ignoring readonly+ prefix"""
350
readonly_prefix = 'readonly+'
351
if url.startswith(readonly_prefix):
352
url = url[len(readonly_prefix):]
353
return urlutils.local_path_from_url(url)
300
356
def fill_in_navigation(navigation):
302
358
given a navigation block (used by the template for the page header), fill
308
364
navigation.position = 0
309
365
navigation.count = len(navigation.revid_list)
310
366
navigation.page_position = navigation.position // navigation.pagesize + 1
311
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
367
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
368
- 1)) // navigation.pagesize
313
370
def get_offset(offset):
314
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
371
if (navigation.position + offset < 0) or (
372
navigation.position + offset > navigation.count - 1):
316
374
return navigation.revid_list[navigation.position + offset]
324
382
navigation.next_page_revid)
325
383
start_revno = navigation.history.get_revno(navigation.start_revid)
327
params = { 'filter_file_id': navigation.filter_file_id }
385
params = {'filter_file_id': navigation.filter_file_id}
328
386
if getattr(navigation, 'query', None) is not None:
329
387
params['q'] = navigation.query
339
397
[navigation.scan_url, next_page_revno], **params)
400
def directory_breadcrumbs(path, is_root, view):
402
Generate breadcrumb information from the directory path given
404
The path given should be a path up to any branch that is currently being
408
path -- The path to convert into breadcrumbs
409
is_root -- Whether or not loggerhead is serving a branch at its root
410
view -- The type of view we are showing (files, changes etc)
412
# Is our root directory itself a branch?
420
# Create breadcrumb trail for the path leading up to the branch
422
'dir_name': "(root)",
427
dir_parts = path.strip('/').split('/')
428
for index, dir_name in enumerate(dir_parts):
430
'dir_name': dir_name,
431
'path': '/'.join(dir_parts[:index + 1]),
434
# If we are not in the directory view, the last crumb is a branch,
435
# so we need to specify a view
436
if view != 'directory':
437
breadcrumbs[-1]['suffix'] = '/' + view
441
def branch_breadcrumbs(path, inv, view):
443
Generate breadcrumb information from the branch path given
445
The path given should be a path that exists within a branch
448
path -- The path to convert into breadcrumbs
449
inv -- Inventory to get file information from
450
view -- The type of view we are showing (files, changes etc)
452
dir_parts = path.strip('/').split('/')
453
inner_breadcrumbs = []
454
for index, dir_name in enumerate(dir_parts):
455
inner_breadcrumbs.append({
456
'dir_name': dir_name,
457
'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
458
'suffix': '/' + view,
460
return inner_breadcrumbs
342
463
def decorator(unbound):
343
465
def new_decorator(f):
345
467
g.__name__ = f.__name__
352
474
return new_decorator
355
# common threading-lock decorator
356
def with_lock(lockname, debug_name=None):
357
if debug_name is None:
358
debug_name = lockname
360
def _decorator(unbound):
361
def locked(self, *args, **kw):
362
getattr(self, lockname).acquire()
364
return unbound(self, *args, **kw)
366
getattr(self, lockname).release()
373
481
def _f(*a, **kw):
374
482
from loggerhead.lsprof import profile
377
485
ret, stats = profile(f, *a, **kw)
378
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
486
log.debug('Finished profiled %s in %d msec.' % (f.__name__,
487
int((time.time() - z) * 1000)))
381
490
now = time.time()
382
491
msec = int(now * 1000) % 1000
383
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
492
timestr = time.strftime('%Y%m%d%H%M%S',
493
time.localtime(now)) + ('%03d' % (msec,))
384
494
filename = f.__name__ + '-' + timestr + '.lsprof'
385
495
cPickle.dump(stats, open(filename, 'w'), 2)
442
552
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
443
553
map.update(overrides)
557
class Reloader(object):
559
This class wraps all paste.reloader logic. All methods are @classmethod.
562
_reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
565
def _turn_sigterm_into_systemexit(cls):
567
Attempts to turn a SIGTERM exception into a SystemExit exception.
574
def handle_term(signo, frame):
576
signal.signal(signal.SIGTERM, handle_term)
579
def is_installed(cls):
580
return os.environ.get(cls._reloader_environ_key)
584
from paste import reloader
585
reloader.install(int(1))
588
def restart_with_reloader(cls):
589
"""Based on restart_with_monitor from paste.script.serve."""
590
print 'Starting subprocess with file monitor'
592
args = [sys.executable] + sys.argv
593
new_environ = os.environ.copy()
594
new_environ[cls._reloader_environ_key] = 'true'
598
cls._turn_sigterm_into_systemexit()
599
proc = subprocess.Popen(args, env=new_environ)
600
exit_code = proc.wait()
602
except KeyboardInterrupt:
603
print '^C caught in monitor process'
607
and getattr(os, 'kill', None) is not None):
610
os.kill(proc.pid, signal.SIGTERM)
611
except (OSError, IOError):
614
# Reloader always exits with code 3; but if we are
615
# a monitor, any exit code will restart
618
print '-'*20, 'Restarting', '-'*20
621
def convert_file_errors(application):
622
"""WSGI wrapper to convert some file errors to Paste exceptions"""
623
def new_application(environ, start_response):
625
return application(environ, start_response)
626
except (IOError, OSError), e:
628
from paste import httpexceptions
629
if e.errno == errno.ENOENT:
630
raise httpexceptions.HTTPNotFound()
631
elif e.errno == errno.EACCES:
632
raise httpexceptions.HTTPForbidden()
635
return new_application