19
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from xml.etree import ElementTree as ET
23
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
43
35
log = logging.getLogger("loggerhead.controllers")
46
37
def fix_year(year):
61
52
# displaydate and approximatedate return an elementtree <span> Element
62
53
# with the full date in a tooltip.
65
55
def date_day(value):
66
56
return value.strftime('%Y-%m-%d')
69
59
def date_time(value):
71
return value.strftime('%Y-%m-%d %H:%M:%S')
60
return value.strftime('%Y-%m-%d %T')
76
63
def _displaydate(date):
131
118
return _wrap_with_date_time_title(date, _displaydate(date))
134
class Container(object):
121
class Container (object):
136
123
Convert a dict into an object with attributes.
139
125
def __init__(self, _dict=None, **kw):
140
self._properties = {}
141
126
if _dict is not None:
142
127
for key, value in _dict.iteritems():
143
128
setattr(self, key, value)
147
132
def __repr__(self):
149
134
for key, value in self.__dict__.iteritems():
150
if key.startswith('_') or (getattr(self.__dict__[key],
151
'__call__', None) is not None):
135
if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
153
137
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
176
142
def trunc(text, limit=10):
177
143
if len(text) <= limit:
202
167
return '%s at %s' % (username, domains[-2])
203
168
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))
214
171
# only do this if unicode turns out to be a problem
215
172
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
217
174
# FIXME: get rid of this method; use fixed_width() and avoid XML().
220
175
def html_clean(s):
222
177
clean up a string for html display. expand any tabs, encode any html
227
182
s = s.replace(' ', ' ')
231
185
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
236
189
CSS is stupid. In some cases we need to replace an empty value with
237
190
a non breaking space ( ). There has to be a better way of doing this.
239
return: the same value recieved if not empty, and a ' ' if it is.
192
return: the same value recieved if not empty, and a NONBREAKING_SPACE
195
if type(s) is int and s is None:
243
elif isinstance(s, int):
197
elif type(s) is int and s is not None:
199
elif type(s) is types.NoneType:
249
s = s.decode('utf-8')
250
except UnicodeDecodeError:
251
s = s.decode('iso-8859-15')
254
HSC = HTMLStructureCleaner()
256
208
def fixed_width(s):
268
220
s = s.decode('utf-8')
269
221
except UnicodeDecodeError:
270
222
s = s.decode('iso-8859-15')
272
s = cgi.escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
274
return HSC.clean(s).replace('\n', '<br/>')
223
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
277
226
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)
356
296
def fill_in_navigation(navigation):
358
298
given a navigation block (used by the template for the page header), fill
364
304
navigation.position = 0
365
305
navigation.count = len(navigation.revid_list)
366
306
navigation.page_position = navigation.position // navigation.pagesize + 1
367
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
368
- 1)) // navigation.pagesize
307
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
370
309
def get_offset(offset):
371
if (navigation.position + offset < 0) or (
372
navigation.position + offset > navigation.count - 1):
310
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
374
312
return navigation.revid_list[navigation.position + offset]
382
320
navigation.next_page_revid)
383
321
start_revno = navigation.history.get_revno(navigation.start_revid)
385
params = {'filter_file_id': navigation.filter_file_id}
323
params = { 'filter_file_id': navigation.filter_file_id }
386
324
if getattr(navigation, 'query', None) is not None:
387
325
params['q'] = navigation.query
397
335
[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
463
338
def decorator(unbound):
465
339
def new_decorator(f):
467
341
g.__name__ = f.__name__
474
348
return new_decorator
351
# common threading-lock decorator
352
def with_lock(lockname, debug_name=None):
353
if debug_name is None:
354
debug_name = lockname
356
def _decorator(unbound):
357
def locked(self, *args, **kw):
358
getattr(self, lockname).acquire()
360
return unbound(self, *args, **kw)
362
getattr(self, lockname).release()
481
369
def _f(*a, **kw):
482
370
from loggerhead.lsprof import profile
485
373
ret, stats = profile(f, *a, **kw)
486
log.debug('Finished profiled %s in %d msec.' % (f.__name__,
487
int((time.time() - z) * 1000)))
374
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
490
377
now = time.time()
491
378
msec = int(now * 1000) % 1000
492
timestr = time.strftime('%Y%m%d%H%M%S',
493
time.localtime(now)) + ('%03d' % (msec,))
379
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
494
380
filename = f.__name__ + '-' + timestr + '.lsprof'
495
381
cPickle.dump(stats, open(filename, 'w'), 2)
552
438
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
553
439
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