~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Michael Hudson
  • Date: 2008-06-30 09:09:16 UTC
  • mto: This revision was merged to the branch mainline in revision 180.
  • Revision ID: michael.hudson@canonical.com-20080630090916-4u22x6xyvs5poxxc
now with more working

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
#
24
22
except ImportError:
25
23
    from elementtree import ElementTree as ET
26
24
 
27
 
from simpletal.simpleTALUtils import HTMLStructureCleaner
28
 
 
29
25
import base64
30
26
import cgi
31
27
import datetime
32
28
import logging
33
29
import re
 
30
import sha
34
31
import struct
 
32
import sys
35
33
import threading
36
34
import time
37
 
import sys
38
 
import os
39
 
import subprocess
 
35
import traceback
 
36
 
40
37
 
41
38
log = logging.getLogger("loggerhead.controllers")
42
39
 
43
 
 
44
40
def fix_year(year):
45
41
    if year < 70:
46
42
        year += 2000
59
55
# displaydate and approximatedate return an elementtree <span> Element
60
56
# with the full date in a tooltip.
61
57
 
62
 
 
63
58
def date_day(value):
64
59
    return value.strftime('%Y-%m-%d')
65
60
 
66
61
 
67
62
def date_time(value):
68
 
    if value is not None:
69
 
        return value.strftime('%Y-%m-%d %T')
70
 
    else:
71
 
        return 'N/A'
 
63
    return value.strftime('%Y-%m-%d %T')
72
64
 
73
65
 
74
66
def _displaydate(date):
133
125
    """
134
126
    Convert a dict into an object with attributes.
135
127
    """
136
 
 
137
128
    def __init__(self, _dict=None, **kw):
138
129
        if _dict is not None:
139
130
            for key, value in _dict.iteritems():
144
135
    def __repr__(self):
145
136
        out = '{ '
146
137
        for key, value in self.__dict__.iteritems():
147
 
            if key.startswith('_') or (getattr(self.__dict__[key],
148
 
                                       '__call__', None) is not None):
 
138
            if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
149
139
                continue
150
140
            out += '%r => %r, ' % (key, value)
151
141
        out += '}'
152
142
        return out
153
143
 
154
144
 
 
145
def clean_revid(revid):
 
146
    if revid == 'missing':
 
147
        return revid
 
148
    return sha.new(revid).hexdigest()
 
149
 
 
150
 
 
151
def obfuscate(text):
 
152
    return ''.join([ '&#%d;' % ord(c) for c in text ])
 
153
 
 
154
 
155
155
def trunc(text, limit=10):
156
156
    if len(text) <= limit:
157
157
        return text
158
158
    return text[:limit] + '...'
159
159
 
160
160
 
 
161
def to_utf8(s):
 
162
    if isinstance(s, unicode):
 
163
        return s.encode('utf-8')
 
164
    return s
 
165
 
 
166
 
161
167
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
162
168
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
163
169
 
164
 
 
165
170
def hide_email(email):
166
171
    """
167
172
    try to obsure any email address in a bazaar committer's name.
181
186
        return '%s at %s' % (username, domains[-2])
182
187
    return '%s at %s' % (username, domains[0])
183
188
 
184
 
def hide_emails(emails):
185
 
    """
186
 
    try to obscure any email address in a list of bazaar committers' names.
187
 
    """
188
 
    result = []
189
 
    for email in emails:
190
 
        result.append(hide_email(email))
191
 
    return result
 
189
 
 
190
def triple_factors(min_value=1):
 
191
    factors = (1, 3)
 
192
    index = 0
 
193
    n = 1
 
194
    while True:
 
195
        if n >= min_value:
 
196
            yield n * factors[index]
 
197
        index += 1
 
198
        if index >= len(factors):
 
199
            index = 0
 
200
            n *= 10
 
201
 
 
202
 
 
203
def scan_range(pos, max, pagesize=1):
 
204
    """
 
205
    given a position in a maximum range, return a list of negative and positive
 
206
    jump factors for an hgweb-style triple-factor geometric scan.
 
207
 
 
208
    for example, with pos=20 and max=500, the range would be:
 
209
    [ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
 
210
 
 
211
    i admit this is a very strange way of jumping through revisions.  i didn't
 
212
    invent it. :)
 
213
    """
 
214
    out = []
 
215
    for n in triple_factors(pagesize + 1):
 
216
        if n > max:
 
217
            return out
 
218
        if pos + n < max:
 
219
            out.append(n)
 
220
        if pos - n >= 0:
 
221
            out.insert(0, -n)
 
222
 
192
223
 
193
224
# only do this if unicode turns out to be a problem
194
225
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
195
226
 
196
227
# FIXME: get rid of this method; use fixed_width() and avoid XML().
197
 
 
198
 
 
199
228
def html_clean(s):
200
229
    """
201
230
    clean up a string for html display.  expand any tabs, encode any html
207
236
    return s
208
237
 
209
238
 
 
239
 
210
240
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
211
241
 
212
 
 
213
 
def fill_div(s):
214
 
    """
215
 
    CSS is stupid. In some cases we need to replace an empty value with
216
 
    a non breaking space (&nbsp;). There has to be a better way of doing this.
217
 
 
218
 
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
219
 
    """
220
 
 
221
 
 
222
 
    if s is None:
223
 
        return '&nbsp;'
224
 
    elif isinstance(s, int):
225
 
        return s
226
 
    elif not s.strip():
227
 
        return '&nbsp;'
228
 
    else:
229
 
        try:
230
 
            s = s.decode('utf-8')
231
 
        except UnicodeDecodeError:
232
 
            s = s.decode('iso-8859-15')
233
 
        return s
234
 
 
235
 
HSC = HTMLStructureCleaner()
236
 
 
237
242
def fixed_width(s):
238
243
    """
239
244
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
249
254
            s = s.decode('utf-8')
250
255
        except UnicodeDecodeError:
251
256
            s = s.decode('iso-8859-15')
252
 
 
253
 
    s = cgi.escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
254
 
 
255
 
    return HSC.clean(s).replace('\n', '<br/>')
 
257
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
256
258
 
257
259
 
258
260
def fake_permissions(kind, executable):
264
266
    return '-rw-r--r--'
265
267
 
266
268
 
 
269
def if_present(format, value):
 
270
    """
 
271
    format a value using a format string, if the value exists and is not None.
 
272
    """
 
273
    if value is None:
 
274
        return ''
 
275
    return format % value
 
276
 
 
277
 
267
278
def b64(s):
268
279
    s = base64.encodestring(s).replace('\n', '')
269
280
    while (len(s) > 0) and (s[-1] == '='):
291
302
P95_MEG = int(0.9 * MEG)
292
303
P95_GIG = int(0.9 * GIG)
293
304
 
294
 
 
295
305
def human_size(size, min_divisor=0):
296
306
    size = int(size)
297
307
    if (size == 0) and (min_divisor == 0):
316
326
 
317
327
    out = str(base)
318
328
    if (base < 100) and (dot != 0):
319
 
        out += '.%d' % (dot)
 
329
        out += '.%d' % (dot,)
320
330
    if divisor == KILO:
321
331
        out += 'K'
322
332
    elif divisor == MEG:
337
347
        navigation.position = 0
338
348
    navigation.count = len(navigation.revid_list)
339
349
    navigation.page_position = navigation.position // navigation.pagesize + 1
340
 
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
341
 
 - 1)) // navigation.pagesize
 
350
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
342
351
 
343
352
    def get_offset(offset):
344
 
        if (navigation.position + offset < 0) or (
345
 
           navigation.position + offset > navigation.count - 1):
 
353
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
346
354
            return None
347
355
        return navigation.revid_list[navigation.position + offset]
348
356
 
349
 
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
350
357
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
351
358
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
352
359
    prev_page_revno = navigation.history.get_revno(
355
362
            navigation.next_page_revid)
356
363
    start_revno = navigation.history.get_revno(navigation.start_revid)
357
364
 
358
 
    params = {'filter_file_id': navigation.filter_file_id}
 
365
    params = { 'filter_file_id': navigation.filter_file_id }
359
366
    if getattr(navigation, 'query', None) is not None:
360
367
        params['q'] = navigation.query
361
368
 
370
377
            [navigation.scan_url, next_page_revno], **params)
371
378
 
372
379
 
373
 
def directory_breadcrumbs(path, is_root, view):
374
 
    """
375
 
    Generate breadcrumb information from the directory path given
376
 
 
377
 
    The path given should be a path up to any branch that is currently being
378
 
    served
379
 
 
380
 
    Arguments:
381
 
    path -- The path to convert into breadcrumbs
382
 
    is_root -- Whether or not loggerhead is serving a branch at its root
383
 
    view -- The type of view we are showing (files, changes etc)
384
 
    """
385
 
    # Is our root directory itself a branch?
386
 
    if is_root:
387
 
        if view == 'directory':
388
 
            directory = 'files'
389
 
        breadcrumbs = [{
390
 
            'dir_name': path,
391
 
            'path': '',
392
 
            'suffix': view,
393
 
        }]
394
 
    else:
395
 
        # Create breadcrumb trail for the path leading up to the branch
396
 
        breadcrumbs = [{
397
 
            'dir_name': "(root)",
398
 
            'path': '',
399
 
            'suffix': '',
400
 
        }]
401
 
        if path != '/':
402
 
            dir_parts = path.strip('/').split('/')
403
 
            for index, dir_name in enumerate(dir_parts):
404
 
                breadcrumbs.append({
405
 
                    'dir_name': dir_name,
406
 
                    'path': '/'.join(dir_parts[:index + 1]),
407
 
                    'suffix': '',
408
 
                })
409
 
            # If we are not in the directory view, the last crumb is a branch,
410
 
            # so we need to specify a view
411
 
            if view != 'directory':
412
 
                breadcrumbs[-1]['suffix'] = '/' + view
413
 
    return breadcrumbs
414
 
 
415
 
 
416
 
def branch_breadcrumbs(path, inv, view):
417
 
    """
418
 
    Generate breadcrumb information from the branch path given
419
 
 
420
 
    The path given should be a path that exists within a branch
421
 
 
422
 
    Arguments:
423
 
    path -- The path to convert into breadcrumbs
424
 
    inv -- Inventory to get file information from
425
 
    view -- The type of view we are showing (files, changes etc)
426
 
    """
427
 
    dir_parts = path.strip('/').split('/')
428
 
    inner_breadcrumbs = []
429
 
    for index, dir_name in enumerate(dir_parts):
430
 
        inner_breadcrumbs.append({
431
 
            'dir_name': dir_name,
432
 
            'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
433
 
            'suffix': '/' + view,
434
 
        })
435
 
    return inner_breadcrumbs
 
380
def log_exception(log):
 
381
    for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
 
382
        log.debug(line)
436
383
 
437
384
 
438
385
def decorator(unbound):
439
 
 
440
386
    def new_decorator(f):
441
387
        g = unbound(f)
442
388
        g.__name__ = f.__name__
449
395
    return new_decorator
450
396
 
451
397
 
 
398
# common threading-lock decorator
 
399
def with_lock(lockname, debug_name=None):
 
400
    if debug_name is None:
 
401
        debug_name = lockname
 
402
    @decorator
 
403
    def _decorator(unbound):
 
404
        def locked(self, *args, **kw):
 
405
            getattr(self, lockname).acquire()
 
406
            try:
 
407
                return unbound(self, *args, **kw)
 
408
            finally:
 
409
                getattr(self, lockname).release()
 
410
        return locked
 
411
    return _decorator
 
412
 
 
413
 
452
414
 
453
415
@decorator
454
416
def lsprof(f):
455
 
 
456
417
    def _f(*a, **kw):
457
418
        from loggerhead.lsprof import profile
458
419
        import cPickle
459
420
        z = time.time()
460
421
        ret, stats = profile(f, *a, **kw)
461
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
462
 
            int((time.time() - z) * 1000)))
 
422
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
463
423
        stats.sort()
464
424
        stats.freeze()
465
425
        now = time.time()
466
426
        msec = int(now * 1000) % 1000
467
 
        timestr = time.strftime('%Y%m%d%H%M%S',
468
 
                                time.localtime(now)) + ('%03d' % msec)
 
427
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
469
428
        filename = f.__name__ + '-' + timestr + '.lsprof'
470
429
        cPickle.dump(stats, open(filename, 'w'), 2)
471
430
        return ret
527
486
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
528
487
    map.update(overrides)
529
488
    return map
530
 
 
531
 
 
532
 
class Reloader(object):
533
 
    """
534
 
    This class wraps all paste.reloader logic. All methods are @classmethod.
535
 
    """
536
 
 
537
 
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
538
 
 
539
 
    @classmethod
540
 
    def _turn_sigterm_into_systemexit(self):
541
 
        """
542
 
        Attempts to turn a SIGTERM exception into a SystemExit exception.
543
 
        """
544
 
        try:
545
 
            import signal
546
 
        except ImportError:
547
 
            return
548
 
 
549
 
        def handle_term(signo, frame):
550
 
            raise SystemExit
551
 
        signal.signal(signal.SIGTERM, handle_term)
552
 
 
553
 
    @classmethod
554
 
    def is_installed(self):
555
 
        return os.environ.get(self._reloader_environ_key)
556
 
 
557
 
    @classmethod
558
 
    def install(self):
559
 
        from paste import reloader
560
 
        reloader.install(int(1))
561
 
 
562
 
    @classmethod
563
 
    def restart_with_reloader(self):
564
 
        """Based on restart_with_monitor from paste.script.serve."""
565
 
        print 'Starting subprocess with file monitor'
566
 
        while 1:
567
 
            args = [sys.executable] + sys.argv
568
 
            new_environ = os.environ.copy()
569
 
            new_environ[self._reloader_environ_key] = 'true'
570
 
            proc = None
571
 
            try:
572
 
                try:
573
 
                    self._turn_sigterm_into_systemexit()
574
 
                    proc = subprocess.Popen(args, env=new_environ)
575
 
                    exit_code = proc.wait()
576
 
                    proc = None
577
 
                except KeyboardInterrupt:
578
 
                    print '^C caught in monitor process'
579
 
                    return 1
580
 
            finally:
581
 
                if (proc is not None
582
 
                    and hasattr(os, 'kill')):
583
 
                    import signal
584
 
                    try:
585
 
                        os.kill(proc.pid, signal.SIGTERM)
586
 
                    except (OSError, IOError):
587
 
                        pass
588
 
 
589
 
            # Reloader always exits with code 3; but if we are
590
 
            # a monitor, any exit code will restart
591
 
            if exit_code != 3:
592
 
                return exit_code
593
 
            print '-'*20, 'Restarting', '-'*20