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
42
39
log = logging.getLogger("loggerhead.controllers")
45
41
def fix_year(year):
60
56
# displaydate and approximatedate return an elementtree <span> Element
61
57
# with the full date in a tooltip.
64
59
def date_day(value):
65
60
return value.strftime('%Y-%m-%d')
68
63
def date_time(value):
69
64
if value is not None:
70
return value.strftime('%Y-%m-%d %H:%M:%S')
65
return value.strftime('%Y-%m-%d %T')
130
125
return _wrap_with_date_time_title(date, _displaydate(date))
133
class Container(object):
128
class Container (object):
135
130
Convert a dict into an object with attributes.
138
132
def __init__(self, _dict=None, **kw):
139
self._properties = {}
140
133
if _dict is not None:
141
134
for key, value in _dict.iteritems():
142
135
setattr(self, key, value)
146
139
def __repr__(self):
148
141
for key, value in self.__dict__.iteritems():
149
if key.startswith('_') or (getattr(self.__dict__[key],
150
'__call__', None) is not None):
142
if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
152
144
out += '%r => %r, ' % (key, value)
156
def __getattr__(self, attr):
157
"""Used for handling things that aren't already available."""
158
if attr.startswith('_') or attr not in self._properties:
159
raise AttributeError('No attribute: %s' % (attr,))
160
val = self._properties[attr](self, attr)
161
setattr(self, attr, val)
164
def _set_property(self, attr, prop_func):
165
"""Set a function that will be called when an attribute is desired.
167
We will cache the return value, so the function call should be
168
idempotent. We will pass 'self' and the 'attr' name when triggered.
170
if attr.startswith('_'):
171
raise ValueError("Cannot create properties that start with _")
172
self._properties[attr] = prop_func
175
149
def trunc(text, limit=10):
176
150
if len(text) <= limit:
201
174
return '%s at %s' % (username, domains[-2])
202
175
return '%s at %s' % (username, domains[0])
204
def hide_emails(emails):
206
try to obscure any email address in a list of bazaar committers' names.
210
result.append(hide_email(email))
213
178
# only do this if unicode turns out to be a problem
214
179
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
216
# Can't be a dict; & needs to be done first.
220
("'", "'"), # ' is defined in XML, but not HTML.
227
"""Transform dangerous (X)HTML characters into entities.
229
Like cgi.escape, except also escaping " and '. This makes it safe to use
230
in both attribute and element content.
232
If you want to safely fill a format string with escaped values, use
235
for char, repl in html_entity_subs:
236
s = s.replace(char, repl)
240
def html_format(template, *args):
241
"""Safely format an HTML template string, escaping the arguments.
243
The template string must not be user-controlled; it will not be escaped.
245
return template % tuple(html_escape(arg) for arg in args)
248
181
# FIXME: get rid of this method; use fixed_width() and avoid XML().
250
182
def html_clean(s):
252
184
clean up a string for html display. expand any tabs, encode any html
253
185
entities, and replace spaces with ' '. this is primarily for use
254
186
in displaying monospace text.
256
s = html_escape(s.expandtabs())
188
s = cgi.escape(s.expandtabs())
257
189
s = s.replace(' ', ' ')
261
192
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
266
196
CSS is stupid. In some cases we need to replace an empty value with
298
229
s = s.decode('utf-8')
299
230
except UnicodeDecodeError:
300
231
s = s.decode('iso-8859-15')
302
s = html_escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
304
return HSC.clean(s).replace('\n', '<br/>')
232
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
307
235
def fake_permissions(kind, executable):
378
def local_path_from_url(url):
379
"""Convert Bazaar URL to local path, ignoring readonly+ prefix"""
380
readonly_prefix = 'readonly+'
381
if url.startswith(readonly_prefix):
382
url = url[len(readonly_prefix):]
383
return urlutils.local_path_from_url(url)
386
305
def fill_in_navigation(navigation):
388
307
given a navigation block (used by the template for the page header), fill
394
313
navigation.position = 0
395
314
navigation.count = len(navigation.revid_list)
396
315
navigation.page_position = navigation.position // navigation.pagesize + 1
397
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
398
- 1)) // navigation.pagesize
316
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
400
318
def get_offset(offset):
401
if (navigation.position + offset < 0) or (
402
navigation.position + offset > navigation.count - 1):
319
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
404
321
return navigation.revid_list[navigation.position + offset]
412
329
navigation.next_page_revid)
413
330
start_revno = navigation.history.get_revno(navigation.start_revid)
415
params = {'filter_file_id': navigation.filter_file_id}
332
params = { 'filter_file_id': navigation.filter_file_id }
416
333
if getattr(navigation, 'query', None) is not None:
417
334
params['q'] = navigation.query
504
422
return new_decorator
425
# common threading-lock decorator
426
def with_lock(lockname, debug_name=None):
427
if debug_name is None:
428
debug_name = lockname
430
def _decorator(unbound):
431
def locked(self, *args, **kw):
432
getattr(self, lockname).acquire()
434
return unbound(self, *args, **kw)
436
getattr(self, lockname).release()
511
443
def _f(*a, **kw):
512
444
from loggerhead.lsprof import profile
515
447
ret, stats = profile(f, *a, **kw)
516
log.debug('Finished profiled %s in %d msec.' % (f.__name__,
517
int((time.time() - z) * 1000)))
448
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
520
451
now = time.time()
521
452
msec = int(now * 1000) % 1000
522
timestr = time.strftime('%Y%m%d%H%M%S',
523
time.localtime(now)) + ('%03d' % (msec,))
453
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
524
454
filename = f.__name__ + '-' + timestr + '.lsprof'
525
455
cPickle.dump(stats, open(filename, 'w'), 2)
601
531
except ImportError:
604
533
def handle_term(signo, frame):
606
535
signal.signal(signal.SIGTERM, handle_term)
609
def is_installed(cls):
610
return os.environ.get(cls._reloader_environ_key)
538
def is_installed(self):
539
return os.environ.get(self._reloader_environ_key)
614
543
from paste import reloader
615
544
reloader.install(int(1))
618
def restart_with_reloader(cls):
547
def restart_with_reloader(self):
619
548
"""Based on restart_with_monitor from paste.script.serve."""
620
549
print 'Starting subprocess with file monitor'
622
551
args = [sys.executable] + sys.argv
623
552
new_environ = os.environ.copy()
624
new_environ[cls._reloader_environ_key] = 'true'
553
new_environ[self._reloader_environ_key] = 'true'
628
cls._turn_sigterm_into_systemexit()
557
self._turn_sigterm_into_systemexit()
629
558
proc = subprocess.Popen(args, env=new_environ)
630
559
exit_code = proc.wait()
636
565
if (proc is not None
637
and getattr(os, 'kill', None) is not None):
566
and hasattr(os, 'kill')):
640
569
os.kill(proc.pid, signal.SIGTERM)
641
570
except (OSError, IOError):
644
573
# Reloader always exits with code 3; but if we are
645
574
# a monitor, any exit code will restart
646
575
if exit_code != 3:
648
577
print '-'*20, 'Restarting', '-'*20
651
def convert_file_errors(application):
652
"""WSGI wrapper to convert some file errors to Paste exceptions"""
653
def new_application(environ, start_response):
655
return application(environ, start_response)
656
except (IOError, OSError), e:
658
from paste import httpexceptions
659
if e.errno == errno.ENOENT:
660
raise httpexceptions.HTTPNotFound()
661
elif e.errno == errno.EACCES:
662
raise httpexceptions.HTTPForbidden()
665
return new_application