~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Martin Albisetti
  • Date: 2008-06-23 22:05:20 UTC
  • mto: (157.1.3 loggerhead)
  • mto: This revision was merged to the branch mainline in revision 187.
  • Revision ID: argentina@gmail.com-20080623220520-3prleeajgc3jf2h1
 * Remove download bundle link. That will die soon.
 * Make internal links to files work

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
import types
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.
182
187
    return '%s at %s' % (username, domains[0])
183
188
 
184
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
 
 
223
 
185
224
# only do this if unicode turns out to be a problem
186
225
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
187
226
 
188
227
# FIXME: get rid of this method; use fixed_width() and avoid XML().
189
 
 
190
 
 
191
228
def html_clean(s):
192
229
    """
193
230
    clean up a string for html display.  expand any tabs, encode any html
198
235
    s = s.replace(' ', '&nbsp;')
199
236
    return s
200
237
 
201
 
 
202
238
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
203
239
 
204
 
 
205
240
def fill_div(s):
206
241
    """
207
242
    CSS is stupid. In some cases we need to replace an empty value with
208
243
    a non breaking space (&nbsp;). There has to be a better way of doing this.
209
244
 
210
 
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
 
245
    return: the same value recieved if not empty, and a NONBREAKING_SPACE 
 
246
            if not 
211
247
    """
212
 
 
213
 
 
214
 
    if s is None:
215
 
        return '&nbsp;'
216
 
    elif isinstance(s, int):
 
248
    if type(s) is int and s is None:
 
249
        return NONBREAKING_SPACE
 
250
    elif type(s) is int and s is not None:
217
251
        return s
218
 
    elif not s.strip():
219
 
        return '&nbsp;'
 
252
    elif type(s) is types.NoneType:
 
253
        return NONBREAKING_SPACE
 
254
    elif len(s) is 0:
 
255
        return NONBREAKING_SPACE
220
256
    else:
221
 
        try:
222
 
            s = s.decode('utf-8')
223
 
        except UnicodeDecodeError:
224
 
            s = s.decode('iso-8859-15')
225
257
        return s
226
258
 
227
 
HSC = HTMLStructureCleaner()
 
259
 
228
260
 
229
261
def fixed_width(s):
230
262
    """
241
273
            s = s.decode('utf-8')
242
274
        except UnicodeDecodeError:
243
275
            s = s.decode('iso-8859-15')
244
 
 
245
 
    s = s.expandtabs().replace(' ', NONBREAKING_SPACE)
246
 
 
247
 
    return HSC.clean(s).replace('\n', '<br/>')
 
276
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
248
277
 
249
278
 
250
279
def fake_permissions(kind, executable):
256
285
    return '-rw-r--r--'
257
286
 
258
287
 
 
288
def if_present(format, value):
 
289
    """
 
290
    format a value using a format string, if the value exists and is not None.
 
291
    """
 
292
    if value is None:
 
293
        return ''
 
294
    return format % value
 
295
 
 
296
 
259
297
def b64(s):
260
298
    s = base64.encodestring(s).replace('\n', '')
261
299
    while (len(s) > 0) and (s[-1] == '='):
283
321
P95_MEG = int(0.9 * MEG)
284
322
P95_GIG = int(0.9 * GIG)
285
323
 
286
 
 
287
324
def human_size(size, min_divisor=0):
288
325
    size = int(size)
289
326
    if (size == 0) and (min_divisor == 0):
308
345
 
309
346
    out = str(base)
310
347
    if (base < 100) and (dot != 0):
311
 
        out += '.%d' % (dot)
 
348
        out += '.%d' % (dot,)
312
349
    if divisor == KILO:
313
350
        out += 'K'
314
351
    elif divisor == MEG:
329
366
        navigation.position = 0
330
367
    navigation.count = len(navigation.revid_list)
331
368
    navigation.page_position = navigation.position // navigation.pagesize + 1
332
 
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
333
 
 - 1)) // navigation.pagesize
 
369
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
334
370
 
335
371
    def get_offset(offset):
336
 
        if (navigation.position + offset < 0) or (
337
 
           navigation.position + offset > navigation.count - 1):
 
372
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
338
373
            return None
339
374
        return navigation.revid_list[navigation.position + offset]
340
375
 
341
376
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
342
377
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
343
378
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
344
 
    prev_page_revno = navigation.history.get_revno(
345
 
            navigation.prev_page_revid)
346
 
    next_page_revno = navigation.history.get_revno(
347
 
            navigation.next_page_revid)
348
 
    start_revno = navigation.history.get_revno(navigation.start_revid)
349
 
 
350
 
    params = {'filter_file_id': navigation.filter_file_id}
 
379
    prev_page_revno = navigation.branch.history.get_revno(
 
380
            navigation.prev_page_revid)
 
381
    next_page_revno = navigation.branch.history.get_revno(
 
382
            navigation.next_page_revid)
 
383
    start_revno = navigation.branch._history.get_revno(navigation.start_revid)
 
384
 
 
385
    prev_page_revno = navigation.branch._history.get_revno(
 
386
            navigation.prev_page_revid)
 
387
    next_page_revno = navigation.branch._history.get_revno(
 
388
            navigation.next_page_revid)
 
389
 
 
390
    params = { 'filter_file_id': navigation.filter_file_id }
351
391
    if getattr(navigation, 'query', None) is not None:
352
392
        params['q'] = navigation.query
353
393
 
362
402
            [navigation.scan_url, next_page_revno], **params)
363
403
 
364
404
 
365
 
def directory_breadcrumbs(path, is_root, view):
366
 
    """
367
 
    Generate breadcrumb information from the directory path given
368
 
 
369
 
    The path given should be a path up to any branch that is currently being
370
 
    served
371
 
 
372
 
    Arguments:
373
 
    path -- The path to convert into breadcrumbs
374
 
    is_root -- Whether or not loggerhead is serving a branch at its root
375
 
    view -- The type of view we are showing (files, changes etc)
376
 
    """
377
 
    # Is our root directory itself a branch?
378
 
    if is_root:
379
 
        if view == 'directory':
380
 
            directory = 'files'
381
 
        breadcrumbs = [{
382
 
            'dir_name': path,
383
 
            'path': '',
384
 
            'suffix': view,
385
 
        }]
386
 
    else:
387
 
        # Create breadcrumb trail for the path leading up to the branch
388
 
        breadcrumbs = [{
389
 
            'dir_name': "(root)",
390
 
            'path': '',
391
 
            'suffix': '',
392
 
        }]
393
 
        if path != '/':
394
 
            dir_parts = path.strip('/').split('/')
395
 
            for index, dir_name in enumerate(dir_parts):
396
 
                breadcrumbs.append({
397
 
                    'dir_name': dir_name,
398
 
                    'path': '/'.join(dir_parts[:index + 1]),
399
 
                    'suffix': '',
400
 
                })
401
 
            # If we are not in the directory view, the last crumb is a branch,
402
 
            # so we need to specify a view
403
 
            if view != 'directory':
404
 
                breadcrumbs[-1]['suffix'] = '/' + view
405
 
    return breadcrumbs
406
 
 
407
 
 
408
 
def branch_breadcrumbs(path, inv, view):
409
 
    """
410
 
    Generate breadcrumb information from the branch path given
411
 
 
412
 
    The path given should be a path that exists within a branch
413
 
 
414
 
    Arguments:
415
 
    path -- The path to convert into breadcrumbs
416
 
    inv -- Inventory to get file information from
417
 
    view -- The type of view we are showing (files, changes etc)
418
 
    """
419
 
    dir_parts = path.strip('/').split('/')
420
 
    inner_breadcrumbs = []
421
 
    for index, dir_name in enumerate(dir_parts):
422
 
        inner_breadcrumbs.append({
423
 
            'dir_name': dir_name,
424
 
            'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
425
 
            'suffix': '/' + view,
426
 
        })
427
 
    return inner_breadcrumbs
 
405
def log_exception(log):
 
406
    for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
 
407
        log.debug(line)
428
408
 
429
409
 
430
410
def decorator(unbound):
431
 
 
432
411
    def new_decorator(f):
433
412
        g = unbound(f)
434
413
        g.__name__ = f.__name__
442
421
 
443
422
 
444
423
# common threading-lock decorator
445
 
 
446
 
 
447
424
def with_lock(lockname, debug_name=None):
448
425
    if debug_name is None:
449
426
        debug_name = lockname
450
 
 
451
427
    @decorator
452
428
    def _decorator(unbound):
453
 
 
454
429
        def locked(self, *args, **kw):
455
430
            getattr(self, lockname).acquire()
456
431
            try:
462
437
 
463
438
 
464
439
@decorator
 
440
def strip_whitespace(f):
 
441
    def _f(*a, **kw):
 
442
        out = f(*a, **kw)
 
443
        orig_len = len(out)
 
444
        out = re.sub(r'\n\s+', '\n', out)
 
445
        out = re.sub(r'[ \t]+', ' ', out)
 
446
        out = re.sub(r'\s+\n', '\n', out)
 
447
        new_len = len(out)
 
448
        log.debug('Saved %sB (%d%%) by stripping whitespace.',
 
449
                  human_size(orig_len - new_len),
 
450
                  round(100.0 - float(new_len) * 100.0 / float(orig_len)))
 
451
        return out
 
452
    return _f
 
453
 
 
454
 
 
455
@decorator
465
456
def lsprof(f):
466
 
 
467
457
    def _f(*a, **kw):
468
458
        from loggerhead.lsprof import profile
469
459
        import cPickle
470
460
        z = time.time()
471
461
        ret, stats = profile(f, *a, **kw)
472
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
473
 
            int((time.time() - z) * 1000)))
 
462
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
474
463
        stats.sort()
475
464
        stats.freeze()
476
465
        now = time.time()
477
466
        msec = int(now * 1000) % 1000
478
 
        timestr = time.strftime('%Y%m%d%H%M%S',
479
 
                                time.localtime(now)) + ('%03d' % msec)
 
467
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
480
468
        filename = f.__name__ + '-' + timestr + '.lsprof'
481
469
        cPickle.dump(stats, open(filename, 'w'), 2)
482
470
        return ret
538
526
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
539
527
    map.update(overrides)
540
528
    return map
541
 
 
542
 
 
543
 
class Reloader(object):
544
 
    """
545
 
    This class wraps all paste.reloader logic. All methods are @classmethod.
546
 
    """
547
 
 
548
 
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
549
 
 
550
 
    @classmethod
551
 
    def _turn_sigterm_into_systemexit(self):
552
 
        """
553
 
        Attempts to turn a SIGTERM exception into a SystemExit exception.
554
 
        """
555
 
        try:
556
 
            import signal
557
 
        except ImportError:
558
 
            return
559
 
 
560
 
        def handle_term(signo, frame):
561
 
            raise SystemExit
562
 
        signal.signal(signal.SIGTERM, handle_term)
563
 
 
564
 
    @classmethod
565
 
    def is_installed(self):
566
 
        return os.environ.get(self._reloader_environ_key)
567
 
 
568
 
    @classmethod
569
 
    def install(self):
570
 
        from paste import reloader
571
 
        reloader.install(int(1))
572
 
 
573
 
    @classmethod
574
 
    def restart_with_reloader(self):
575
 
        """Based on restart_with_monitor from paste.script.serve."""
576
 
        print 'Starting subprocess with file monitor'
577
 
        while 1:
578
 
            args = [sys.executable] + sys.argv
579
 
            new_environ = os.environ.copy()
580
 
            new_environ[self._reloader_environ_key] = 'true'
581
 
            proc = None
582
 
            try:
583
 
                try:
584
 
                    self._turn_sigterm_into_systemexit()
585
 
                    proc = subprocess.Popen(args, env=new_environ)
586
 
                    exit_code = proc.wait()
587
 
                    proc = None
588
 
                except KeyboardInterrupt:
589
 
                    print '^C caught in monitor process'
590
 
                    return 1
591
 
            finally:
592
 
                if (proc is not None
593
 
                    and hasattr(os, 'kill')):
594
 
                    import signal
595
 
                    try:
596
 
                        os.kill(proc.pid, signal.SIGTERM)
597
 
                    except (OSError, IOError):
598
 
                        pass
599
 
 
600
 
            # Reloader always exits with code 3; but if we are
601
 
            # a monitor, any exit code will restart
602
 
            if exit_code != 3:
603
 
                return exit_code
604
 
            print '-'*20, 'Restarting', '-'*20