~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Martin Albisetti
  • Date: 2008-07-22 18:51:42 UTC
  • mto: (157.1.3 loggerhead)
  • mto: This revision was merged to the branch mainline in revision 187.
  • Revision ID: argentina@gmail.com-20080722185142-v8puz36u24tyuhov
 Fixed links in header and footer

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#
2
 
# Copyright (C) 2008  Canonical Ltd.
3
 
#                     (Authored by Martin Albisetti <argentina@gmail.com)
4
2
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
5
3
# Copyright (C) 2006  Goffredo Baroncelli <kreijack@inwind.it>
6
4
#
19
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
18
#
21
19
 
 
20
try:
 
21
    from xml.etree import ElementTree as ET
 
22
except ImportError:
 
23
    from elementtree import ElementTree as ET
 
24
 
22
25
import base64
23
26
import cgi
24
27
import datetime
27
30
import struct
28
31
import threading
29
32
import time
30
 
import sys
31
 
import os
32
 
import subprocess
33
 
 
34
 
try:
35
 
    from xml.etree import ElementTree as ET
36
 
except ImportError:
37
 
    from elementtree import ElementTree as ET
38
 
 
39
 
from bzrlib import urlutils
40
 
 
41
 
from simpletal.simpleTALUtils import HTMLStructureCleaner
 
33
import types
42
34
 
43
35
log = logging.getLogger("loggerhead.controllers")
44
36
 
45
 
 
46
37
def fix_year(year):
47
38
    if year < 70:
48
39
        year += 2000
61
52
# displaydate and approximatedate return an elementtree <span> Element
62
53
# with the full date in a tooltip.
63
54
 
64
 
 
65
55
def date_day(value):
66
56
    return value.strftime('%Y-%m-%d')
67
57
 
68
58
 
69
59
def date_time(value):
70
 
    if value is not None:
71
 
        return value.strftime('%Y-%m-%d %H:%M:%S')
72
 
    else:
73
 
        return 'N/A'
 
60
    return value.strftime('%Y-%m-%d %T')
74
61
 
75
62
 
76
63
def _displaydate(date):
131
118
    return _wrap_with_date_time_title(date, _displaydate(date))
132
119
 
133
120
 
134
 
class Container(object):
 
121
class Container (object):
135
122
    """
136
123
    Convert a dict into an object with attributes.
137
124
    """
138
 
 
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):
148
133
        out = '{ '
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):
152
136
                continue
153
137
            out += '%r => %r, ' % (key, value)
154
138
        out += '}'
155
139
        return out
156
140
 
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)
163
 
        return val
164
 
 
165
 
    def _set_property(self, attr, prop_func):
166
 
        """Set a function that will be called when an attribute is desired.
167
 
 
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.
170
 
        """
171
 
        if attr.startswith('_'):
172
 
            raise ValueError("Cannot create properties that start with _")
173
 
        self._properties[attr] = prop_func
174
 
 
175
141
 
176
142
def trunc(text, limit=10):
177
143
    if len(text) <= limit:
182
148
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
183
149
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
184
150
 
185
 
 
186
151
def hide_email(email):
187
152
    """
188
153
    try to obsure any email address in a bazaar committer's name.
202
167
        return '%s at %s' % (username, domains[-2])
203
168
    return '%s at %s' % (username, domains[0])
204
169
 
205
 
def hide_emails(emails):
206
 
    """
207
 
    try to obscure any email address in a list of bazaar committers' names.
208
 
    """
209
 
    result = []
210
 
    for email in emails:
211
 
        result.append(hide_email(email))
212
 
    return result
213
170
 
214
171
# only do this if unicode turns out to be a problem
215
172
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
216
173
 
217
174
# FIXME: get rid of this method; use fixed_width() and avoid XML().
218
 
 
219
 
 
220
175
def html_clean(s):
221
176
    """
222
177
    clean up a string for html display.  expand any tabs, encode any html
227
182
    s = s.replace(' ', '&nbsp;')
228
183
    return s
229
184
 
230
 
 
231
185
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
232
186
 
233
 
 
234
187
def fill_div(s):
235
188
    """
236
189
    CSS is stupid. In some cases we need to replace an empty value with
237
190
    a non breaking space (&nbsp;). There has to be a better way of doing this.
238
191
 
239
 
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
 
192
    return: the same value recieved if not empty, and a NONBREAKING_SPACE 
 
193
            if not 
240
194
    """
241
 
    if s is None:
 
195
    if type(s) is int and s is None:
242
196
        return '&nbsp;'
243
 
    elif isinstance(s, int):
 
197
    elif type(s) is int and s is not None:
244
198
        return s
245
 
    elif not s.strip():
 
199
    elif type(s) is types.NoneType:
 
200
        return '&nbsp;'
 
201
    elif len(s) is 0:
246
202
        return '&nbsp;'
247
203
    else:
248
 
        try:
249
 
            s = s.decode('utf-8')
250
 
        except UnicodeDecodeError:
251
 
            s = s.decode('iso-8859-15')
252
204
        return s
253
205
 
254
 
HSC = HTMLStructureCleaner()
 
206
 
255
207
 
256
208
def fixed_width(s):
257
209
    """
268
220
            s = s.decode('utf-8')
269
221
        except UnicodeDecodeError:
270
222
            s = s.decode('iso-8859-15')
271
 
 
272
 
    s = cgi.escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
273
 
 
274
 
    return HSC.clean(s).replace('\n', '<br/>')
 
223
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
275
224
 
276
225
 
277
226
def fake_permissions(kind, executable):
310
259
P95_MEG = int(0.9 * MEG)
311
260
P95_GIG = int(0.9 * GIG)
312
261
 
313
 
 
314
262
def human_size(size, min_divisor=0):
315
263
    size = int(size)
316
264
    if (size == 0) and (min_divisor == 0):
345
293
    return out
346
294
 
347
295
 
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)
354
 
 
355
 
 
356
296
def fill_in_navigation(navigation):
357
297
    """
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
369
308
 
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):
373
311
            return None
374
312
        return navigation.revid_list[navigation.position + offset]
375
313
 
382
320
            navigation.next_page_revid)
383
321
    start_revno = navigation.history.get_revno(navigation.start_revid)
384
322
 
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
388
326
 
397
335
            [navigation.scan_url, next_page_revno], **params)
398
336
 
399
337
 
400
 
def directory_breadcrumbs(path, is_root, view):
401
 
    """
402
 
    Generate breadcrumb information from the directory path given
403
 
 
404
 
    The path given should be a path up to any branch that is currently being
405
 
    served
406
 
 
407
 
    Arguments:
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)
411
 
    """
412
 
    # Is our root directory itself a branch?
413
 
    if is_root:
414
 
        breadcrumbs = [{
415
 
            'dir_name': path,
416
 
            'path': '',
417
 
            'suffix': view,
418
 
        }]
419
 
    else:
420
 
        # Create breadcrumb trail for the path leading up to the branch
421
 
        breadcrumbs = [{
422
 
            'dir_name': "(root)",
423
 
            'path': '',
424
 
            'suffix': '',
425
 
        }]
426
 
        if path != '/':
427
 
            dir_parts = path.strip('/').split('/')
428
 
            for index, dir_name in enumerate(dir_parts):
429
 
                breadcrumbs.append({
430
 
                    'dir_name': dir_name,
431
 
                    'path': '/'.join(dir_parts[:index + 1]),
432
 
                    'suffix': '',
433
 
                })
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
438
 
    return breadcrumbs
439
 
 
440
 
 
441
 
def branch_breadcrumbs(path, inv, view):
442
 
    """
443
 
    Generate breadcrumb information from the branch path given
444
 
 
445
 
    The path given should be a path that exists within a branch
446
 
 
447
 
    Arguments:
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)
451
 
    """
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,
459
 
        })
460
 
    return inner_breadcrumbs
461
 
 
462
 
 
463
338
def decorator(unbound):
464
 
 
465
339
    def new_decorator(f):
466
340
        g = unbound(f)
467
341
        g.__name__ = f.__name__
474
348
    return new_decorator
475
349
 
476
350
 
 
351
# common threading-lock decorator
 
352
def with_lock(lockname, debug_name=None):
 
353
    if debug_name is None:
 
354
        debug_name = lockname
 
355
    @decorator
 
356
    def _decorator(unbound):
 
357
        def locked(self, *args, **kw):
 
358
            getattr(self, lockname).acquire()
 
359
            try:
 
360
                return unbound(self, *args, **kw)
 
361
            finally:
 
362
                getattr(self, lockname).release()
 
363
        return locked
 
364
    return _decorator
 
365
 
477
366
 
478
367
@decorator
479
368
def lsprof(f):
480
 
 
481
369
    def _f(*a, **kw):
482
370
        from loggerhead.lsprof import profile
483
371
        import cPickle
484
372
        z = time.time()
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)))
488
375
        stats.sort()
489
376
        stats.freeze()
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)
496
382
        return ret
552
438
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
553
439
    map.update(overrides)
554
440
    return map
555
 
 
556
 
 
557
 
class Reloader(object):
558
 
    """
559
 
    This class wraps all paste.reloader logic. All methods are @classmethod.
560
 
    """
561
 
 
562
 
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
563
 
 
564
 
    @classmethod
565
 
    def _turn_sigterm_into_systemexit(cls):
566
 
        """
567
 
        Attempts to turn a SIGTERM exception into a SystemExit exception.
568
 
        """
569
 
        try:
570
 
            import signal
571
 
        except ImportError:
572
 
            return
573
 
 
574
 
        def handle_term(signo, frame):
575
 
            raise SystemExit
576
 
        signal.signal(signal.SIGTERM, handle_term)
577
 
 
578
 
    @classmethod
579
 
    def is_installed(cls):
580
 
        return os.environ.get(cls._reloader_environ_key)
581
 
 
582
 
    @classmethod
583
 
    def install(cls):
584
 
        from paste import reloader
585
 
        reloader.install(int(1))
586
 
 
587
 
    @classmethod
588
 
    def restart_with_reloader(cls):
589
 
        """Based on restart_with_monitor from paste.script.serve."""
590
 
        print 'Starting subprocess with file monitor'
591
 
        while True:
592
 
            args = [sys.executable] + sys.argv
593
 
            new_environ = os.environ.copy()
594
 
            new_environ[cls._reloader_environ_key] = 'true'
595
 
            proc = None
596
 
            try:
597
 
                try:
598
 
                    cls._turn_sigterm_into_systemexit()
599
 
                    proc = subprocess.Popen(args, env=new_environ)
600
 
                    exit_code = proc.wait()
601
 
                    proc = None
602
 
                except KeyboardInterrupt:
603
 
                    print '^C caught in monitor process'
604
 
                    return 1
605
 
            finally:
606
 
                if (proc is not None
607
 
                    and getattr(os, 'kill', None) is not None):
608
 
                    import signal
609
 
                    try:
610
 
                        os.kill(proc.pid, signal.SIGTERM)
611
 
                    except (OSError, IOError):
612
 
                        pass
613
 
 
614
 
            # Reloader always exits with code 3; but if we are
615
 
            # a monitor, any exit code will restart
616
 
            if exit_code != 3:
617
 
                return exit_code
618
 
            print '-'*20, 'Restarting', '-'*20
619
 
 
620
 
 
621
 
def convert_file_errors(application):
622
 
    """WSGI wrapper to convert some file errors to Paste exceptions"""
623
 
    def new_application(environ, start_response):
624
 
        try:
625
 
            return application(environ, start_response)
626
 
        except (IOError, OSError), e:
627
 
            import errno
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()
633
 
            else:
634
 
                raise
635
 
    return new_application