~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Michael Hudson
  • Date: 2007-10-29 16:19:30 UTC
  • mto: This revision was merged to the branch mainline in revision 141.
  • Revision ID: michael.hudson@canonical.com-20071029161930-oxqrd4rd8j1oz3hx
add do nothing check target

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
import datetime
25
23
import logging
26
24
import re
 
25
import sha
27
26
import struct
 
27
import sys
28
28
import threading
29
29
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
 
30
import traceback
 
31
 
 
32
import turbogears
 
33
 
42
34
 
43
35
log = logging.getLogger("loggerhead.controllers")
44
36
 
45
37
 
 
38
def timespan(delta):
 
39
    if delta.days > 730:
 
40
        # good grief!
 
41
        return '%d years' % (int(delta.days // 365.25),)
 
42
    if delta.days >= 3:
 
43
        return '%d days' % delta.days
 
44
    seg = []
 
45
    if delta.days > 0:
 
46
        if delta.days == 1:
 
47
            seg.append('1 day')
 
48
        else:
 
49
            seg.append('%d days' % delta.days)
 
50
    hrs = delta.seconds // 3600
 
51
    mins = (delta.seconds % 3600) // 60
 
52
    if hrs > 0:
 
53
        if hrs == 1:
 
54
            seg.append('1 hour')
 
55
        else:
 
56
            seg.append('%d hours' % hrs)
 
57
    if delta.days == 0:
 
58
        if mins > 0:
 
59
            if mins == 1:
 
60
                seg.append('1 minute')
 
61
            else:
 
62
                seg.append('%d minutes' % mins)
 
63
        elif hrs == 0:
 
64
            seg.append('less than a minute')
 
65
    return ', '.join(seg)
 
66
 
 
67
 
 
68
def ago(timestamp):
 
69
    now = datetime.datetime.now()
 
70
    return timespan(now - timestamp) + ' ago'
 
71
 
 
72
 
46
73
def fix_year(year):
47
74
    if year < 70:
48
75
        year += 2000
50
77
        year += 1900
51
78
    return year
52
79
 
53
 
# Display of times.
54
 
 
55
 
# date_day -- just the day
56
 
# date_time -- full date with time
57
 
#
58
 
# displaydate -- for use in sentences
59
 
# approximatedate -- for use in tables
60
 
#
61
 
# displaydate and approximatedate return an elementtree <span> Element
62
 
# with the full date in a tooltip.
63
 
 
64
 
 
65
 
def date_day(value):
66
 
    return value.strftime('%Y-%m-%d')
67
 
 
68
 
 
69
 
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'
74
 
 
75
 
 
76
 
def _displaydate(date):
77
 
    delta = abs(datetime.datetime.now() - date)
78
 
    if delta > datetime.timedelta(1, 0, 0):
79
 
        # far in the past or future, display the date
80
 
        return 'on ' + date_day(date)
81
 
    return _approximatedate(date)
82
 
 
83
 
 
84
 
def _approximatedate(date):
85
 
    delta = datetime.datetime.now() - date
86
 
    if abs(delta) > datetime.timedelta(1, 0, 0):
87
 
        # far in the past or future, display the date
88
 
        return date_day(date)
89
 
    future = delta < datetime.timedelta(0, 0, 0)
90
 
    delta = abs(delta)
91
 
    days = delta.days
92
 
    hours = delta.seconds / 3600
93
 
    minutes = (delta.seconds - (3600*hours)) / 60
94
 
    seconds = delta.seconds % 60
95
 
    result = ''
96
 
    if future:
97
 
        result += 'in '
98
 
    if days != 0:
99
 
        amount = days
100
 
        unit = 'day'
101
 
    elif hours != 0:
102
 
        amount = hours
103
 
        unit = 'hour'
104
 
    elif minutes != 0:
105
 
        amount = minutes
106
 
        unit = 'minute'
107
 
    else:
108
 
        amount = seconds
109
 
        unit = 'second'
110
 
    if amount != 1:
111
 
        unit += 's'
112
 
    result += '%s %s' % (amount, unit)
113
 
    if not future:
114
 
        result += ' ago'
115
 
        return result
116
 
 
117
 
 
118
 
def _wrap_with_date_time_title(date, formatted_date):
119
 
    elem = ET.Element("span")
120
 
    elem.text = formatted_date
121
 
    elem.set("title", date_time(date))
122
 
    return elem
123
 
 
124
 
 
125
 
def approximatedate(date):
126
 
    #FIXME: Returns an object instead of a string
127
 
    return _wrap_with_date_time_title(date, _approximatedate(date))
128
 
 
129
 
 
130
 
def displaydate(date):
131
 
    return _wrap_with_date_time_title(date, _displaydate(date))
132
 
 
133
 
 
134
 
class Container(object):
 
80
 
 
81
class Container (object):
135
82
    """
136
83
    Convert a dict into an object with attributes.
137
84
    """
138
 
 
139
85
    def __init__(self, _dict=None, **kw):
140
 
        self._properties = {}
141
86
        if _dict is not None:
142
87
            for key, value in _dict.iteritems():
143
88
                setattr(self, key, value)
144
89
        for key, value in kw.iteritems():
145
90
            setattr(self, key, value)
146
 
 
 
91
    
147
92
    def __repr__(self):
148
93
        out = '{ '
149
94
        for key, value in self.__dict__.iteritems():
150
 
            if key.startswith('_') or (getattr(self.__dict__[key],
151
 
                                       '__call__', None) is not None):
 
95
            if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
152
96
                continue
153
97
            out += '%r => %r, ' % (key, value)
154
98
        out += '}'
155
99
        return out
156
100
 
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
 
101
 
 
102
def clean_revid(revid):
 
103
    if revid == 'missing':
 
104
        return revid
 
105
    return sha.new(revid).hexdigest()
 
106
 
 
107
 
 
108
def obfuscate(text):
 
109
    return ''.join([ '&#%d;' % ord(c) for c in text ])
174
110
 
175
111
 
176
112
def trunc(text, limit=10):
179
115
    return text[:limit] + '...'
180
116
 
181
117
 
 
118
def to_utf8(s):
 
119
    if isinstance(s, unicode):
 
120
        return s.encode('utf-8')
 
121
    return s
 
122
 
 
123
 
182
124
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
183
125
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
184
126
 
185
 
 
186
127
def hide_email(email):
187
128
    """
188
129
    try to obsure any email address in a bazaar committer's name.
202
143
        return '%s at %s' % (username, domains[-2])
203
144
    return '%s at %s' % (username, domains[0])
204
145
 
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
 
146
    
 
147
def triple_factors(min_value=1):
 
148
    factors = (1, 3)
 
149
    index = 0
 
150
    n = 1
 
151
    while True:
 
152
        if n >= min_value:
 
153
            yield n * factors[index]
 
154
        index += 1
 
155
        if index >= len(factors):
 
156
            index = 0
 
157
            n *= 10
 
158
 
 
159
 
 
160
def scan_range(pos, max, pagesize=1):
 
161
    """
 
162
    given a position in a maximum range, return a list of negative and positive
 
163
    jump factors for an hgweb-style triple-factor geometric scan.
 
164
    
 
165
    for example, with pos=20 and max=500, the range would be:
 
166
    [ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
 
167
    
 
168
    i admit this is a very strange way of jumping through revisions.  i didn't
 
169
    invent it. :)
 
170
    """
 
171
    out = []
 
172
    for n in triple_factors(pagesize + 1):
 
173
        if n > max:
 
174
            return out
 
175
        if pos + n < max:
 
176
            out.append(n)
 
177
        if pos - n >= 0:
 
178
            out.insert(0, -n)
 
179
 
213
180
 
214
181
# only do this if unicode turns out to be a problem
215
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
216
183
 
217
184
# FIXME: get rid of this method; use fixed_width() and avoid XML().
218
 
 
219
 
 
220
185
def html_clean(s):
221
186
    """
222
187
    clean up a string for html display.  expand any tabs, encode any html
228
193
    return s
229
194
 
230
195
 
 
196
 
231
197
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
232
198
 
233
 
 
234
 
def fill_div(s):
235
 
    """
236
 
    CSS is stupid. In some cases we need to replace an empty value with
237
 
    a non breaking space (&nbsp;). There has to be a better way of doing this.
238
 
 
239
 
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
240
 
    """
241
 
    if s is None:
242
 
        return '&nbsp;'
243
 
    elif isinstance(s, int):
244
 
        return s
245
 
    elif not s.strip():
246
 
        return '&nbsp;'
247
 
    else:
248
 
        try:
249
 
            s = s.decode('utf-8')
250
 
        except UnicodeDecodeError:
251
 
            s = s.decode('iso-8859-15')
252
 
        return s
253
 
 
254
 
HSC = HTMLStructureCleaner()
255
 
 
256
199
def fixed_width(s):
257
200
    """
258
201
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
268
211
            s = s.decode('utf-8')
269
212
        except UnicodeDecodeError:
270
213
            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/>')
 
214
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
275
215
 
276
216
 
277
217
def fake_permissions(kind, executable):
283
223
    return '-rw-r--r--'
284
224
 
285
225
 
 
226
def if_present(format, value):
 
227
    """
 
228
    format a value using a format string, if the value exists and is not None.
 
229
    """
 
230
    if value is None:
 
231
        return ''
 
232
    return format % value
 
233
 
 
234
 
286
235
def b64(s):
287
236
    s = base64.encodestring(s).replace('\n', '')
288
237
    while (len(s) > 0) and (s[-1] == '='):
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):
324
272
        divisor = MEG
325
273
    else:
326
274
        divisor = KILO
327
 
 
 
275
    
328
276
    dot = size % divisor
329
277
    base = size - dot
330
278
    dot = dot * 10 // divisor
332
280
    if dot >= 10:
333
281
        base += 1
334
282
        dot -= 10
335
 
 
 
283
    
336
284
    out = str(base)
337
285
    if (base < 100) and (dot != 0):
338
286
        out += '.%d' % (dot,)
343
291
    elif divisor == GIG:
344
292
        out += 'G'
345
293
    return out
346
 
 
347
 
 
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
 
 
 
294
    
355
295
 
356
296
def fill_in_navigation(navigation):
357
297
    """
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
369
 
 
 
307
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
 
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
 
 
376
 
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
 
313
    
377
314
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
378
315
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
379
 
    prev_page_revno = navigation.history.get_revno(
380
 
            navigation.prev_page_revid)
381
 
    next_page_revno = navigation.history.get_revno(
382
 
            navigation.next_page_revid)
383
 
    start_revno = navigation.history.get_revno(navigation.start_revid)
384
 
 
385
 
    params = {'filter_file_id': navigation.filter_file_id}
 
316
    
 
317
    params = { 'file_id': navigation.file_id }
386
318
    if getattr(navigation, 'query', None) is not None:
387
319
        params['q'] = navigation.query
388
 
 
389
 
    if getattr(navigation, 'start_revid', None) is not None:
390
 
        params['start_revid'] = start_revno
391
 
 
 
320
    else:
 
321
        params['start_revid'] = navigation.start_revid
 
322
        
392
323
    if navigation.prev_page_revid:
393
 
        navigation.prev_page_url = navigation.branch.context_url(
394
 
            [navigation.scan_url, prev_page_revno], **params)
 
324
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
395
325
    if navigation.next_page_revid:
396
 
        navigation.next_page_url = navigation.branch.context_url(
397
 
            [navigation.scan_url, next_page_revno], **params)
398
 
 
399
 
 
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
 
326
        navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
 
327
 
 
328
 
 
329
def log_exception(log):
 
330
    for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
 
331
        log.debug(line)
461
332
 
462
333
 
463
334
def decorator(unbound):
464
 
 
465
335
    def new_decorator(f):
466
336
        g = unbound(f)
467
337
        g.__name__ = f.__name__
474
344
    return new_decorator
475
345
 
476
346
 
 
347
# common threading-lock decorator
 
348
def with_lock(lockname, debug_name=None):
 
349
    if debug_name is None:
 
350
        debug_name = lockname
 
351
    @decorator
 
352
    def _decorator(unbound):
 
353
        def locked(self, *args, **kw):
 
354
            getattr(self, lockname).acquire()
 
355
            try:
 
356
                return unbound(self, *args, **kw)
 
357
            finally:
 
358
                getattr(self, lockname).release()
 
359
        return locked
 
360
    return _decorator
 
361
 
 
362
 
 
363
@decorator
 
364
def strip_whitespace(f):
 
365
    def _f(*a, **kw):
 
366
        out = f(*a, **kw)
 
367
        orig_len = len(out)
 
368
        out = re.sub(r'\n\s+', '\n', out)
 
369
        out = re.sub(r'[ \t]+', ' ', out)
 
370
        out = re.sub(r'\s+\n', '\n', out)
 
371
        new_len = len(out)
 
372
        log.debug('Saved %sB (%d%%) by stripping whitespace.',
 
373
                  human_size(orig_len - new_len),
 
374
                  round(100.0 - float(new_len) * 100.0 / float(orig_len)))
 
375
        return out
 
376
    return _f
 
377
 
477
378
 
478
379
@decorator
479
380
def lsprof(f):
480
 
 
481
381
    def _f(*a, **kw):
482
382
        from loggerhead.lsprof import profile
483
383
        import cPickle
484
384
        z = time.time()
485
385
        ret, stats = profile(f, *a, **kw)
486
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
487
 
            int((time.time() - z) * 1000)))
 
386
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
488
387
        stats.sort()
489
388
        stats.freeze()
490
389
        now = time.time()
491
390
        msec = int(now * 1000) % 1000
492
 
        timestr = time.strftime('%Y%m%d%H%M%S',
493
 
                                time.localtime(now)) + ('%03d' % (msec,))
 
391
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
494
392
        filename = f.__name__ + '-' + timestr + '.lsprof'
495
393
        cPickle.dump(stats, open(filename, 'w'), 2)
496
394
        return ret
504
402
#         current location along the navigation path (while browsing)
505
403
#     - starting revid (start_revid)
506
404
#         the current beginning of navigation (navigation continues back to
507
 
#         the original revision) -- this defines an 'alternate mainline'
508
 
#         when the user navigates into a branch.
 
405
#         the original revision) -- this may not be along the primary revision
 
406
#         path since the user may have navigated into a branch
509
407
#     - file_id
510
 
#         the file being looked at
511
 
#     - filter_file_id
512
408
#         if navigating the revisions that touched a file
513
409
#     - q (query)
514
410
#         if navigating the revisions that matched a search query
525
421
#         for re-ordering an existing page by different sort
526
422
 
527
423
t_context = threading.local()
528
 
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
529
 
          'compare_revid', 'sort')
 
424
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
530
425
 
531
426
 
532
427
def set_context(map):
535
430
 
536
431
def get_context(**overrides):
537
432
    """
538
 
    Soon to be deprecated.
539
 
 
540
 
 
541
433
    return a context map that may be overriden by specific values passed in,
542
434
    but only contains keys from the list of valid context keys.
543
 
 
 
435
    
544
436
    if 'clear' is set, only the 'remember' context value will be added, and
545
437
    all other context will be omitted.
546
438
    """
552
444
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
553
445
    map.update(overrides)
554
446
    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