~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Martin Albisetti
  • Date: 2008-08-06 19:27:02 UTC
  • Revision ID: argentina@gmail.com-20080806192702-k5wjw7q3b655ch2n
Clean up unused/inexistant import

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
20
#
21
21
 
 
22
try:
 
23
    from xml.etree import ElementTree as ET
 
24
except ImportError:
 
25
    from elementtree import ElementTree as ET
 
26
 
22
27
import base64
 
28
import cgi
23
29
import datetime
24
30
import logging
25
31
import re
26
32
import struct
27
33
import threading
28
34
import time
29
 
import sys
30
 
import os
31
 
import subprocess
32
 
 
33
 
try:
34
 
    from xml.etree import ElementTree as ET
35
 
except ImportError:
36
 
    from elementtree import ElementTree as ET
37
 
 
38
 
from bzrlib import urlutils
39
 
 
40
 
from simpletal.simpleTALUtils import HTMLStructureCleaner
 
35
import types
41
36
 
42
37
log = logging.getLogger("loggerhead.controllers")
43
38
 
44
 
 
45
39
def fix_year(year):
46
40
    if year < 70:
47
41
        year += 2000
52
46
# Display of times.
53
47
 
54
48
# date_day -- just the day
55
 
# date_time -- full date with time (UTC)
 
49
# date_time -- full date with time
56
50
#
 
51
# displaydate -- for use in sentences
57
52
# approximatedate -- for use in tables
58
53
#
59
 
# approximatedate return an elementtree <span> Element
60
 
# with the full date (UTC) in a tooltip.
61
 
 
 
54
# displaydate and approximatedate return an elementtree <span> Element
 
55
# with the full date in a tooltip.
62
56
 
63
57
def date_day(value):
64
58
    return value.strftime('%Y-%m-%d')
65
59
 
66
60
 
67
61
def date_time(value):
68
 
    if value is not None:
69
 
        # Note: this assumes that the value is UTC in some fashion.
70
 
        return value.strftime('%Y-%m-%d %H:%M:%S UTC')
71
 
    else:
72
 
        return 'N/A'
 
62
    return value.strftime('%Y-%m-%d %T')
 
63
 
 
64
 
 
65
def _displaydate(date):
 
66
    delta = abs(datetime.datetime.now() - date)
 
67
    if delta > datetime.timedelta(1, 0, 0):
 
68
        # far in the past or future, display the date
 
69
        return 'on ' + date_day(date)
 
70
    return _approximatedate(date)
73
71
 
74
72
 
75
73
def _approximatedate(date):
118
116
    return _wrap_with_date_time_title(date, _approximatedate(date))
119
117
 
120
118
 
121
 
class Container(object):
 
119
def displaydate(date):
 
120
    return _wrap_with_date_time_title(date, _displaydate(date))
 
121
 
 
122
 
 
123
class Container (object):
122
124
    """
123
125
    Convert a dict into an object with attributes.
124
126
    """
125
 
 
126
127
    def __init__(self, _dict=None, **kw):
127
 
        self._properties = {}
128
128
        if _dict is not None:
129
129
            for key, value in _dict.iteritems():
130
130
                setattr(self, key, value)
134
134
    def __repr__(self):
135
135
        out = '{ '
136
136
        for key, value in self.__dict__.iteritems():
137
 
            if key.startswith('_') or (getattr(self.__dict__[key],
138
 
                                       '__call__', None) is not None):
 
137
            if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
139
138
                continue
140
139
            out += '%r => %r, ' % (key, value)
141
140
        out += '}'
142
141
        return out
143
142
 
144
 
    def __getattr__(self, attr):
145
 
        """Used for handling things that aren't already available."""
146
 
        if attr.startswith('_') or attr not in self._properties:
147
 
            raise AttributeError('No attribute: %s' % (attr,))
148
 
        val = self._properties[attr](self, attr)
149
 
        setattr(self, attr, val)
150
 
        return val
151
 
 
152
 
    def _set_property(self, attr, prop_func):
153
 
        """Set a function that will be called when an attribute is desired.
154
 
 
155
 
        We will cache the return value, so the function call should be
156
 
        idempotent. We will pass 'self' and the 'attr' name when triggered.
157
 
        """
158
 
        if attr.startswith('_'):
159
 
            raise ValueError("Cannot create properties that start with _")
160
 
        self._properties[attr] = prop_func
161
 
 
162
143
 
163
144
def trunc(text, limit=10):
164
145
    if len(text) <= limit:
169
150
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
170
151
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
171
152
 
172
 
 
173
153
def hide_email(email):
174
154
    """
175
155
    try to obsure any email address in a bazaar committer's name.
189
169
        return '%s at %s' % (username, domains[-2])
190
170
    return '%s at %s' % (username, domains[0])
191
171
 
192
 
def hide_emails(emails):
193
 
    """
194
 
    try to obscure any email address in a list of bazaar committers' names.
195
 
    """
196
 
    result = []
197
 
    for email in emails:
198
 
        result.append(hide_email(email))
199
 
    return result
200
172
 
201
173
# only do this if unicode turns out to be a problem
202
174
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
203
175
 
204
 
# Can't be a dict; &amp; needs to be done first.
205
 
html_entity_subs = [
206
 
    ("&", "&amp;"),
207
 
    ('"', "&quot;"),
208
 
    ("'", "&#39;"), # &apos; is defined in XML, but not HTML.
209
 
    (">", "&gt;"),
210
 
    ("<", "&lt;"),
211
 
    ]
212
 
 
213
 
 
214
 
def html_escape(s):
215
 
    """Transform dangerous (X)HTML characters into entities.
216
 
 
217
 
    Like cgi.escape, except also escaping \" and '. This makes it safe to use
218
 
    in both attribute and element content.
219
 
 
220
 
    If you want to safely fill a format string with escaped values, use
221
 
    html_format instead
222
 
    """
223
 
    for char, repl in html_entity_subs:
224
 
        s = s.replace(char, repl)
225
 
    return s
226
 
 
227
 
 
228
 
def html_format(template, *args):
229
 
    """Safely format an HTML template string, escaping the arguments.
230
 
 
231
 
    The template string must not be user-controlled; it will not be escaped.
232
 
    """
233
 
    return template % tuple(html_escape(arg) for arg in args)
234
 
 
235
 
 
236
176
# FIXME: get rid of this method; use fixed_width() and avoid XML().
237
 
 
238
177
def html_clean(s):
239
178
    """
240
179
    clean up a string for html display.  expand any tabs, encode any html
241
180
    entities, and replace spaces with '&nbsp;'.  this is primarily for use
242
181
    in displaying monospace text.
243
182
    """
244
 
    s = html_escape(s.expandtabs())
 
183
    s = cgi.escape(s.expandtabs())
245
184
    s = s.replace(' ', '&nbsp;')
246
185
    return s
247
186
 
248
 
 
249
187
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
250
188
 
251
 
 
252
189
def fill_div(s):
253
190
    """
254
191
    CSS is stupid. In some cases we need to replace an empty value with
256
193
 
257
194
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
258
195
    """
 
196
    
 
197
 
259
198
    if s is None:
260
199
        return '&nbsp;'
261
200
    elif isinstance(s, int):
269
208
            s = s.decode('iso-8859-15')
270
209
        return s
271
210
 
272
 
HSC = HTMLStructureCleaner()
273
211
 
274
212
def fixed_width(s):
275
213
    """
286
224
            s = s.decode('utf-8')
287
225
        except UnicodeDecodeError:
288
226
            s = s.decode('iso-8859-15')
289
 
 
290
 
    s = html_escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
291
 
 
292
 
    return HSC.clean(s).replace('\n', '<br/>')
 
227
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
293
228
 
294
229
 
295
230
def fake_permissions(kind, executable):
328
263
P95_MEG = int(0.9 * MEG)
329
264
P95_GIG = int(0.9 * GIG)
330
265
 
331
 
 
332
266
def human_size(size, min_divisor=0):
333
267
    size = int(size)
334
268
    if (size == 0) and (min_divisor == 0):
363
297
    return out
364
298
 
365
299
 
366
 
def local_path_from_url(url):
367
 
    """Convert Bazaar URL to local path, ignoring readonly+ prefix"""
368
 
    readonly_prefix = 'readonly+'
369
 
    if url.startswith(readonly_prefix):
370
 
        url = url[len(readonly_prefix):]
371
 
    return urlutils.local_path_from_url(url)
372
 
 
373
 
 
374
300
def fill_in_navigation(navigation):
375
301
    """
376
302
    given a navigation block (used by the template for the page header), fill
382
308
        navigation.position = 0
383
309
    navigation.count = len(navigation.revid_list)
384
310
    navigation.page_position = navigation.position // navigation.pagesize + 1
385
 
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
386
 
 - 1)) // navigation.pagesize
 
311
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
387
312
 
388
313
    def get_offset(offset):
389
 
        if (navigation.position + offset < 0) or (
390
 
           navigation.position + offset > navigation.count - 1):
 
314
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
391
315
            return None
392
316
        return navigation.revid_list[navigation.position + offset]
393
317
 
400
324
            navigation.next_page_revid)
401
325
    start_revno = navigation.history.get_revno(navigation.start_revid)
402
326
 
403
 
    params = {'filter_file_id': navigation.filter_file_id}
 
327
    params = { 'filter_file_id': navigation.filter_file_id }
404
328
    if getattr(navigation, 'query', None) is not None:
405
329
        params['q'] = navigation.query
406
330
 
415
339
            [navigation.scan_url, next_page_revno], **params)
416
340
 
417
341
 
418
 
def directory_breadcrumbs(path, is_root, view):
419
 
    """
420
 
    Generate breadcrumb information from the directory path given
421
 
 
422
 
    The path given should be a path up to any branch that is currently being
423
 
    served
424
 
 
425
 
    Arguments:
426
 
    path -- The path to convert into breadcrumbs
427
 
    is_root -- Whether or not loggerhead is serving a branch at its root
428
 
    view -- The type of view we are showing (files, changes etc)
429
 
    """
430
 
    # Is our root directory itself a branch?
431
 
    if is_root:
432
 
        breadcrumbs = [{
433
 
            'dir_name': path,
434
 
            'path': '',
435
 
            'suffix': view,
436
 
        }]
437
 
    else:
438
 
        # Create breadcrumb trail for the path leading up to the branch
439
 
        breadcrumbs = [{
440
 
            'dir_name': "(root)",
441
 
            'path': '',
442
 
            'suffix': '',
443
 
        }]
444
 
        if path != '/':
445
 
            dir_parts = path.strip('/').split('/')
446
 
            for index, dir_name in enumerate(dir_parts):
447
 
                breadcrumbs.append({
448
 
                    'dir_name': dir_name,
449
 
                    'path': '/'.join(dir_parts[:index + 1]),
450
 
                    'suffix': '',
451
 
                })
452
 
            # If we are not in the directory view, the last crumb is a branch,
453
 
            # so we need to specify a view
454
 
            if view != 'directory':
455
 
                breadcrumbs[-1]['suffix'] = '/' + view
456
 
    return breadcrumbs
457
 
 
458
 
 
459
 
def branch_breadcrumbs(path, inv, view):
460
 
    """
461
 
    Generate breadcrumb information from the branch path given
462
 
 
463
 
    The path given should be a path that exists within a branch
464
 
 
465
 
    Arguments:
466
 
    path -- The path to convert into breadcrumbs
467
 
    inv -- Inventory to get file information from
468
 
    view -- The type of view we are showing (files, changes etc)
469
 
    """
470
 
    dir_parts = path.strip('/').split('/')
471
 
    inner_breadcrumbs = []
472
 
    for index, dir_name in enumerate(dir_parts):
473
 
        inner_breadcrumbs.append({
474
 
            'dir_name': dir_name,
475
 
            'path': '/'.join(dir_parts[:index + 1]),
476
 
            'suffix': '/' + view,
477
 
        })
478
 
    return inner_breadcrumbs
479
 
 
480
 
 
481
342
def decorator(unbound):
482
 
 
483
343
    def new_decorator(f):
484
344
        g = unbound(f)
485
345
        g.__name__ = f.__name__
492
352
    return new_decorator
493
353
 
494
354
 
 
355
# common threading-lock decorator
 
356
def with_lock(lockname, debug_name=None):
 
357
    if debug_name is None:
 
358
        debug_name = lockname
 
359
    @decorator
 
360
    def _decorator(unbound):
 
361
        def locked(self, *args, **kw):
 
362
            getattr(self, lockname).acquire()
 
363
            try:
 
364
                return unbound(self, *args, **kw)
 
365
            finally:
 
366
                getattr(self, lockname).release()
 
367
        return locked
 
368
    return _decorator
 
369
 
495
370
 
496
371
@decorator
497
372
def lsprof(f):
498
 
 
499
373
    def _f(*a, **kw):
500
374
        from loggerhead.lsprof import profile
501
375
        import cPickle
502
376
        z = time.time()
503
377
        ret, stats = profile(f, *a, **kw)
504
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
505
 
            int((time.time() - z) * 1000)))
 
378
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
506
379
        stats.sort()
507
380
        stats.freeze()
508
381
        now = time.time()
509
382
        msec = int(now * 1000) % 1000
510
 
        timestr = time.strftime('%Y%m%d%H%M%S',
511
 
                                time.localtime(now)) + ('%03d' % (msec,))
 
383
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
512
384
        filename = f.__name__ + '-' + timestr + '.lsprof'
513
385
        cPickle.dump(stats, open(filename, 'w'), 2)
514
386
        return ret
543
415
#         for re-ordering an existing page by different sort
544
416
 
545
417
t_context = threading.local()
546
 
_valid = (
547
 
    'start_revid', 'filter_file_id', 'q', 'remember', 'compare_revid', 'sort')
 
418
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
 
419
          'compare_revid', 'sort')
548
420
 
549
421
 
550
422
def set_context(map):
570
442
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
571
443
    map.update(overrides)
572
444
    return map
573
 
 
574
 
 
575
 
class Reloader(object):
576
 
    """
577
 
    This class wraps all paste.reloader logic. All methods are @classmethod.
578
 
    """
579
 
 
580
 
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
581
 
 
582
 
    @classmethod
583
 
    def _turn_sigterm_into_systemexit(cls):
584
 
        """
585
 
        Attempts to turn a SIGTERM exception into a SystemExit exception.
586
 
        """
587
 
        try:
588
 
            import signal
589
 
        except ImportError:
590
 
            return
591
 
 
592
 
        def handle_term(signo, frame):
593
 
            raise SystemExit
594
 
        signal.signal(signal.SIGTERM, handle_term)
595
 
 
596
 
    @classmethod
597
 
    def is_installed(cls):
598
 
        return os.environ.get(cls._reloader_environ_key)
599
 
 
600
 
    @classmethod
601
 
    def install(cls):
602
 
        from paste import reloader
603
 
        reloader.install(int(1))
604
 
 
605
 
    @classmethod
606
 
    def restart_with_reloader(cls):
607
 
        """Based on restart_with_monitor from paste.script.serve."""
608
 
        print 'Starting subprocess with file monitor'
609
 
        while True:
610
 
            args = [sys.executable] + sys.argv
611
 
            new_environ = os.environ.copy()
612
 
            new_environ[cls._reloader_environ_key] = 'true'
613
 
            proc = None
614
 
            try:
615
 
                try:
616
 
                    cls._turn_sigterm_into_systemexit()
617
 
                    proc = subprocess.Popen(args, env=new_environ)
618
 
                    exit_code = proc.wait()
619
 
                    proc = None
620
 
                except KeyboardInterrupt:
621
 
                    print '^C caught in monitor process'
622
 
                    return 1
623
 
            finally:
624
 
                if (proc is not None
625
 
                    and getattr(os, 'kill', None) is not None):
626
 
                    import signal
627
 
                    try:
628
 
                        os.kill(proc.pid, signal.SIGTERM)
629
 
                    except (OSError, IOError):
630
 
                        pass
631
 
 
632
 
            # Reloader always exits with code 3; but if we are
633
 
            # a monitor, any exit code will restart
634
 
            if exit_code != 3:
635
 
                return exit_code
636
 
            print '-'*20, 'Restarting', '-'*20
637
 
 
638
 
 
639
 
def convert_file_errors(application):
640
 
    """WSGI wrapper to convert some file errors to Paste exceptions"""
641
 
    def new_application(environ, start_response):
642
 
        try:
643
 
            return application(environ, start_response)
644
 
        except (IOError, OSError), e:
645
 
            import errno
646
 
            from paste import httpexceptions
647
 
            if e.errno == errno.ENOENT:
648
 
                raise httpexceptions.HTTPNotFound()
649
 
            elif e.errno == errno.EACCES:
650
 
                raise httpexceptions.HTTPForbidden()
651
 
            else:
652
 
                raise
653
 
    return new_application
654
 
 
655
 
 
656
 
def convert_to_json_ready(obj):
657
 
    if isinstance(obj, Container):
658
 
        d = obj.__dict__.copy()
659
 
        del d['_properties']
660
 
        return d
661
 
    elif isinstance(obj, datetime.datetime):
662
 
        return tuple(obj.utctimetuple())
663
 
    raise TypeError(repr(obj) + " is not JSON serializable")