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
34
from xml.etree import ElementTree as ET
36
from elementtree import ElementTree as ET
38
from bzrlib import urlutils
40
from simpletal.simpleTALUtils import HTMLStructureCleaner
40
42
log = logging.getLogger("loggerhead.controllers")
42
45
def fix_year(year):
49
52
# Display of times.
51
54
# date_day -- just the day
52
# date_time -- full date with time
55
# date_time -- full date with time (UTC)
54
# displaydate -- for use in sentences
55
57
# approximatedate -- for use in tables
57
# displaydate and approximatedate return an elementtree <span> Element
58
# with the full date in a tooltip.
59
# approximatedate return an elementtree <span> Element
60
# with the full date (UTC) in a tooltip.
60
63
def date_day(value):
61
64
return value.strftime('%Y-%m-%d')
64
67
def date_time(value):
65
68
if value is not None:
66
return value.strftime('%Y-%m-%d %T')
69
# Note: this assumes that the value is UTC in some fashion.
70
return value.strftime('%Y-%m-%d %H:%M:%S UTC')
71
def _displaydate(date):
72
delta = abs(datetime.datetime.now() - date)
73
if delta > datetime.timedelta(1, 0, 0):
74
# far in the past or future, display the date
75
return 'on ' + date_day(date)
76
return _approximatedate(date)
79
75
def _approximatedate(date):
80
76
delta = datetime.datetime.now() - date
81
77
if abs(delta) > datetime.timedelta(1, 0, 0):
122
118
return _wrap_with_date_time_title(date, _approximatedate(date))
125
def displaydate(date):
126
return _wrap_with_date_time_title(date, _displaydate(date))
129
class Container (object):
121
class Container(object):
131
123
Convert a dict into an object with attributes.
133
126
def __init__(self, _dict=None, **kw):
127
self._properties = {}
134
128
if _dict is not None:
135
129
for key, value in _dict.iteritems():
136
130
setattr(self, key, value)
140
134
def __repr__(self):
142
136
for key, value in self.__dict__.iteritems():
143
if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
137
if key.startswith('_') or (getattr(self.__dict__[key],
138
'__call__', None) is not None):
145
140
out += '%r => %r, ' % (key, value)
144
def __getattr__(self, attr):
145
"""Used for handling things that aren't already available."""
146
if attr.startswith('_') or attr not in self._properties:
147
raise AttributeError('No attribute: %s' % (attr,))
148
val = self._properties[attr](self, attr)
149
setattr(self, attr, val)
152
def _set_property(self, attr, prop_func):
153
"""Set a function that will be called when an attribute is desired.
155
We will cache the return value, so the function call should be
156
idempotent. We will pass 'self' and the 'attr' name when triggered.
158
if attr.startswith('_'):
159
raise ValueError("Cannot create properties that start with _")
160
self._properties[attr] = prop_func
150
163
def trunc(text, limit=10):
151
164
if len(text) <= limit:
175
189
return '%s at %s' % (username, domains[-2])
176
190
return '%s at %s' % (username, domains[0])
192
def hide_emails(emails):
194
try to obscure any email address in a list of bazaar committers' names.
198
result.append(hide_email(email))
179
201
# only do this if unicode turns out to be a problem
180
202
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
204
# Can't be a dict; & needs to be done first.
208
("'", "'"), # ' is defined in XML, but not HTML.
215
"""Transform dangerous (X)HTML characters into entities.
217
Like cgi.escape, except also escaping \" and '. This makes it safe to use
218
in both attribute and element content.
220
If you want to safely fill a format string with escaped values, use
223
for char, repl in html_entity_subs:
224
s = s.replace(char, repl)
228
def html_format(template, *args):
229
"""Safely format an HTML template string, escaping the arguments.
231
The template string must not be user-controlled; it will not be escaped.
233
return template % tuple(html_escape(arg) for arg in args)
182
236
# FIXME: get rid of this method; use fixed_width() and avoid XML().
183
238
def html_clean(s):
185
240
clean up a string for html display. expand any tabs, encode any html
186
241
entities, and replace spaces with ' '. this is primarily for use
187
242
in displaying monospace text.
189
s = cgi.escape(s.expandtabs())
244
s = html_escape(s.expandtabs())
190
245
s = s.replace(' ', ' ')
193
249
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
197
254
CSS is stupid. In some cases we need to replace an empty value with
230
286
s = s.decode('utf-8')
231
287
except UnicodeDecodeError:
232
288
s = s.decode('iso-8859-15')
233
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
290
s = html_escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
292
return HSC.clean(s).replace('\n', '<br/>')
236
295
def fake_permissions(kind, executable):
366
def local_path_from_url(url):
367
"""Convert Bazaar URL to local path, ignoring readonly+ prefix"""
368
readonly_prefix = 'readonly+'
369
if url.startswith(readonly_prefix):
370
url = url[len(readonly_prefix):]
371
return urlutils.local_path_from_url(url)
306
374
def fill_in_navigation(navigation):
308
376
given a navigation block (used by the template for the page header), fill
314
382
navigation.position = 0
315
383
navigation.count = len(navigation.revid_list)
316
384
navigation.page_position = navigation.position // navigation.pagesize + 1
317
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
385
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
386
- 1)) // navigation.pagesize
319
388
def get_offset(offset):
320
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
389
if (navigation.position + offset < 0) or (
390
navigation.position + offset > navigation.count - 1):
322
392
return navigation.revid_list[navigation.position + offset]
330
400
navigation.next_page_revid)
331
401
start_revno = navigation.history.get_revno(navigation.start_revid)
333
params = { 'filter_file_id': navigation.filter_file_id }
403
params = {'filter_file_id': navigation.filter_file_id}
334
404
if getattr(navigation, 'query', None) is not None:
335
405
params['q'] = navigation.query
345
415
[navigation.scan_url, next_page_revno], **params)
418
def directory_breadcrumbs(path, is_root, view):
420
Generate breadcrumb information from the directory path given
422
The path given should be a path up to any branch that is currently being
426
path -- The path to convert into breadcrumbs
427
is_root -- Whether or not loggerhead is serving a branch at its root
428
view -- The type of view we are showing (files, changes etc)
430
# Is our root directory itself a branch?
438
# Create breadcrumb trail for the path leading up to the branch
440
'dir_name': "(root)",
445
dir_parts = path.strip('/').split('/')
446
for index, dir_name in enumerate(dir_parts):
448
'dir_name': dir_name,
449
'path': '/'.join(dir_parts[:index + 1]),
452
# If we are not in the directory view, the last crumb is a branch,
453
# so we need to specify a view
454
if view != 'directory':
455
breadcrumbs[-1]['suffix'] = '/' + view
459
def branch_breadcrumbs(path, inv, view):
461
Generate breadcrumb information from the branch path given
463
The path given should be a path that exists within a branch
466
path -- The path to convert into breadcrumbs
467
inv -- Inventory to get file information from
468
view -- The type of view we are showing (files, changes etc)
470
dir_parts = path.strip('/').split('/')
471
inner_breadcrumbs = []
472
for index, dir_name in enumerate(dir_parts):
473
inner_breadcrumbs.append({
474
'dir_name': dir_name,
475
'path': '/'.join(dir_parts[:index + 1]),
476
'suffix': '/' + view,
478
return inner_breadcrumbs
348
481
def decorator(unbound):
349
483
def new_decorator(f):
351
485
g.__name__ = f.__name__
358
492
return new_decorator
361
# common threading-lock decorator
362
def with_lock(lockname, debug_name=None):
363
if debug_name is None:
364
debug_name = lockname
366
def _decorator(unbound):
367
def locked(self, *args, **kw):
368
getattr(self, lockname).acquire()
370
return unbound(self, *args, **kw)
372
getattr(self, lockname).release()
379
499
def _f(*a, **kw):
380
500
from loggerhead.lsprof import profile
383
503
ret, stats = profile(f, *a, **kw)
384
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
504
log.debug('Finished profiled %s in %d msec.' % (f.__name__,
505
int((time.time() - z) * 1000)))
387
508
now = time.time()
388
509
msec = int(now * 1000) % 1000
389
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
510
timestr = time.strftime('%Y%m%d%H%M%S',
511
time.localtime(now)) + ('%03d' % (msec,))
390
512
filename = f.__name__ + '-' + timestr + '.lsprof'
391
513
cPickle.dump(stats, open(filename, 'w'), 2)
421
543
# for re-ordering an existing page by different sort
423
545
t_context = threading.local()
424
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
425
'compare_revid', 'sort')
547
'start_revid', 'filter_file_id', 'q', 'remember', 'compare_revid', 'sort')
428
550
def set_context(map):
467
589
except ImportError:
469
592
def handle_term(signo, frame):
471
594
signal.signal(signal.SIGTERM, handle_term)
474
def is_installed(self):
475
return os.environ.get(self._reloader_environ_key)
597
def is_installed(cls):
598
return os.environ.get(cls._reloader_environ_key)
479
602
from paste import reloader
480
603
reloader.install(int(1))
483
def restart_with_reloader(self):
606
def restart_with_reloader(cls):
484
607
"""Based on restart_with_monitor from paste.script.serve."""
485
608
print 'Starting subprocess with file monitor'
487
610
args = [sys.executable] + sys.argv
488
611
new_environ = os.environ.copy()
489
new_environ[self._reloader_environ_key] = 'true'
612
new_environ[cls._reloader_environ_key] = 'true'
493
self._turn_sigterm_into_systemexit()
616
cls._turn_sigterm_into_systemexit()
494
617
proc = subprocess.Popen(args, env=new_environ)
495
618
exit_code = proc.wait()
501
624
if (proc is not None
502
and hasattr(os, 'kill')):
625
and getattr(os, 'kill', None) is not None):
505
628
os.kill(proc.pid, signal.SIGTERM)
506
629
except (OSError, IOError):
509
632
# Reloader always exits with code 3; but if we are
510
633
# a monitor, any exit code will restart
511
634
if exit_code != 3:
513
print '-'*20, 'Restarting', '-'*20
b'\\ No newline at end of file'
636
print '-'*20, 'Restarting', '-'*20
639
def convert_file_errors(application):
640
"""WSGI wrapper to convert some file errors to Paste exceptions"""
641
def new_application(environ, start_response):
643
return application(environ, start_response)
644
except (IOError, OSError), e:
646
from paste import httpexceptions
647
if e.errno == errno.ENOENT:
648
raise httpexceptions.HTTPNotFound()
649
elif e.errno == errno.EACCES:
650
raise httpexceptions.HTTPForbidden()
653
return new_application
656
def convert_to_json_ready(obj):
657
if isinstance(obj, Container):
658
d = obj.__dict__.copy()
661
elif isinstance(obj, datetime.datetime):
662
return tuple(obj.utctimetuple())
663
raise TypeError(repr(obj) + " is not JSON serializable")