~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Robey Pointer
  • Date: 2007-03-26 07:05:10 UTC
  • mfrom: (114.2.2 handle-binary-files)
  • Revision ID: robey@lag.net-20070326070510-z9etamcrda8zphod
merge in fix for bug 91686: catch binary files and don't diff them.

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
#
20
18
#
21
19
 
22
20
import base64
 
21
import cgi
23
22
import datetime
24
23
import logging
25
24
import re
 
25
import sha
26
26
import struct
 
27
import sys
27
28
import threading
28
29
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
 
30
import traceback
 
31
 
 
32
import turbogears
 
33
 
41
34
 
42
35
log = logging.getLogger("loggerhead.controllers")
43
36
 
44
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
 
45
73
def fix_year(year):
46
74
    if year < 70:
47
75
        year += 2000
49
77
        year += 1900
50
78
    return year
51
79
 
52
 
# Display of times.
53
 
 
54
 
# date_day -- just the day
55
 
# date_time -- full date with time
56
 
#
57
 
# displaydate -- for use in sentences
58
 
# approximatedate -- for use in tables
59
 
#
60
 
# displaydate and approximatedate return an elementtree <span> Element
61
 
# with the full date in a tooltip.
62
 
 
63
 
 
64
 
def date_day(value):
65
 
    return value.strftime('%Y-%m-%d')
66
 
 
67
 
 
68
 
def date_time(value):
69
 
    if value is not None:
70
 
        return value.strftime('%Y-%m-%d %H:%M:%S')
71
 
    else:
72
 
        return 'N/A'
73
 
 
74
 
 
75
 
def _displaydate(date):
76
 
    delta = abs(datetime.datetime.now() - date)
77
 
    if delta > datetime.timedelta(1, 0, 0):
78
 
        # far in the past or future, display the date
79
 
        return 'on ' + date_day(date)
80
 
    return _approximatedate(date)
81
 
 
82
 
 
83
 
def _approximatedate(date):
84
 
    delta = datetime.datetime.now() - date
85
 
    if abs(delta) > datetime.timedelta(1, 0, 0):
86
 
        # far in the past or future, display the date
87
 
        return date_day(date)
88
 
    future = delta < datetime.timedelta(0, 0, 0)
89
 
    delta = abs(delta)
90
 
    days = delta.days
91
 
    hours = delta.seconds / 3600
92
 
    minutes = (delta.seconds - (3600*hours)) / 60
93
 
    seconds = delta.seconds % 60
94
 
    result = ''
95
 
    if future:
96
 
        result += 'in '
97
 
    if days != 0:
98
 
        amount = days
99
 
        unit = 'day'
100
 
    elif hours != 0:
101
 
        amount = hours
102
 
        unit = 'hour'
103
 
    elif minutes != 0:
104
 
        amount = minutes
105
 
        unit = 'minute'
106
 
    else:
107
 
        amount = seconds
108
 
        unit = 'second'
109
 
    if amount != 1:
110
 
        unit += 's'
111
 
    result += '%s %s' % (amount, unit)
112
 
    if not future:
113
 
        result += ' ago'
114
 
        return result
115
 
 
116
 
 
117
 
def _wrap_with_date_time_title(date, formatted_date):
118
 
    elem = ET.Element("span")
119
 
    elem.text = formatted_date
120
 
    elem.set("title", date_time(date))
121
 
    return elem
122
 
 
123
 
 
124
 
def approximatedate(date):
125
 
    #FIXME: Returns an object instead of a string
126
 
    return _wrap_with_date_time_title(date, _approximatedate(date))
127
 
 
128
 
 
129
 
def displaydate(date):
130
 
    return _wrap_with_date_time_title(date, _displaydate(date))
131
 
 
132
 
 
133
 
class Container(object):
 
80
 
 
81
class Container (object):
134
82
    """
135
83
    Convert a dict into an object with attributes.
136
84
    """
137
 
 
138
85
    def __init__(self, _dict=None, **kw):
139
 
        self._properties = {}
140
86
        if _dict is not None:
141
87
            for key, value in _dict.iteritems():
142
88
                setattr(self, key, value)
143
89
        for key, value in kw.iteritems():
144
90
            setattr(self, key, value)
145
 
 
 
91
    
146
92
    def __repr__(self):
147
93
        out = '{ '
148
94
        for key, value in self.__dict__.iteritems():
149
 
            if key.startswith('_') or (getattr(self.__dict__[key],
150
 
                                       '__call__', None) is not None):
 
95
            if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
151
96
                continue
152
97
            out += '%r => %r, ' % (key, value)
153
98
        out += '}'
154
99
        return out
155
100
 
156
 
    def __getattr__(self, attr):
157
 
        """Used for handling things that aren't already available."""
158
 
        if attr.startswith('_') or attr not in self._properties:
159
 
            raise AttributeError('No attribute: %s' % (attr,))
160
 
        val = self._properties[attr](self, attr)
161
 
        setattr(self, attr, val)
162
 
        return val
163
 
 
164
 
    def _set_property(self, attr, prop_func):
165
 
        """Set a function that will be called when an attribute is desired.
166
 
 
167
 
        We will cache the return value, so the function call should be
168
 
        idempotent. We will pass 'self' and the 'attr' name when triggered.
169
 
        """
170
 
        if attr.startswith('_'):
171
 
            raise ValueError("Cannot create properties that start with _")
172
 
        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 ])
173
110
 
174
111
 
175
112
def trunc(text, limit=10):
178
115
    return text[:limit] + '...'
179
116
 
180
117
 
 
118
def to_utf8(s):
 
119
    if isinstance(s, unicode):
 
120
        return s.encode('utf-8')
 
121
    return s
 
122
 
 
123
 
181
124
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
182
125
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
183
126
 
184
 
 
185
127
def hide_email(email):
186
128
    """
187
129
    try to obsure any email address in a bazaar committer's name.
201
143
        return '%s at %s' % (username, domains[-2])
202
144
    return '%s at %s' % (username, domains[0])
203
145
 
204
 
def hide_emails(emails):
205
 
    """
206
 
    try to obscure any email address in a list of bazaar committers' names.
207
 
    """
208
 
    result = []
209
 
    for email in emails:
210
 
        result.append(hide_email(email))
211
 
    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
 
212
180
 
213
181
# only do this if unicode turns out to be a problem
214
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
215
183
 
216
 
# Can't be a dict; &amp; needs to be done first.
217
 
html_entity_subs = [
218
 
    ("&", "&amp;"),
219
 
    ('"', "&quot;"),
220
 
    ("'", "&#39;"), # &apos; is defined in XML, but not HTML.
221
 
    (">", "&gt;"),
222
 
    ("<", "&lt;"),
223
 
    ]
224
 
 
225
 
 
226
 
def html_escape(s):
227
 
    """Transform dangerous (X)HTML characters into entities.
228
 
 
229
 
    Like cgi.escape, except also escaping \" and '. This makes it safe to use
230
 
    in both attribute and element content.
231
 
 
232
 
    If you want to safely fill a format string with escaped values, use
233
 
    html_format instead
234
 
    """
235
 
    for char, repl in html_entity_subs:
236
 
        s = s.replace(char, repl)
237
 
    return s
238
 
 
239
 
 
240
 
def html_format(template, *args):
241
 
    """Safely format an HTML template string, escaping the arguments.
242
 
 
243
 
    The template string must not be user-controlled; it will not be escaped.
244
 
    """
245
 
    return template % tuple(html_escape(arg) for arg in args)
246
 
 
247
 
 
248
 
# FIXME: get rid of this method; use fixed_width() and avoid XML().
249
 
 
250
184
def html_clean(s):
251
185
    """
252
186
    clean up a string for html display.  expand any tabs, encode any html
253
187
    entities, and replace spaces with '&nbsp;'.  this is primarily for use
254
188
    in displaying monospace text.
255
189
    """
256
 
    s = html_escape(s.expandtabs())
 
190
    s = cgi.escape(s.expandtabs())
 
191
#    s = _BADCHARS_RE.sub(lambda x: '&#%d;' % (ord(x.group(0)),), s)
257
192
    s = s.replace(' ', '&nbsp;')
258
193
    return s
259
194
 
260
195
 
261
 
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
262
 
 
263
 
 
264
 
def fill_div(s):
265
 
    """
266
 
    CSS is stupid. In some cases we need to replace an empty value with
267
 
    a non breaking space (&nbsp;). There has to be a better way of doing this.
268
 
 
269
 
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
270
 
    """
271
 
    if s is None:
272
 
        return '&nbsp;'
273
 
    elif isinstance(s, int):
274
 
        return s
275
 
    elif not s.strip():
276
 
        return '&nbsp;'
277
 
    else:
278
 
        try:
279
 
            s = s.decode('utf-8')
280
 
        except UnicodeDecodeError:
281
 
            s = s.decode('iso-8859-15')
282
 
        return s
283
 
 
284
 
HSC = HTMLStructureCleaner()
285
 
 
286
 
def fixed_width(s):
287
 
    """
288
 
    expand tabs and turn spaces into "non-breaking spaces", so browsers won't
289
 
    chop up the string.
290
 
    """
291
 
    if not isinstance(s, unicode):
292
 
        # this kinda sucks.  file contents are just binary data, and no
293
 
        # encoding metadata is stored, so we need to guess.  this is probably
294
 
        # okay for most code, but for people using things like KOI-8, this
295
 
        # will display gibberish.  we have no way of detecting the correct
296
 
        # encoding to use.
297
 
        try:
298
 
            s = s.decode('utf-8')
299
 
        except UnicodeDecodeError:
300
 
            s = s.decode('iso-8859-15')
301
 
 
302
 
    s = html_escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
303
 
 
304
 
    return HSC.clean(s).replace('\n', '<br/>')
305
 
 
306
 
 
307
196
def fake_permissions(kind, executable):
308
197
    # fake up unix-style permissions given only a "kind" and executable bit
309
198
    if kind == 'directory':
313
202
    return '-rw-r--r--'
314
203
 
315
204
 
 
205
def if_present(format, value):
 
206
    """
 
207
    format a value using a format string, if the value exists and is not None.
 
208
    """
 
209
    if value is None:
 
210
        return ''
 
211
    return format % value
 
212
 
 
213
 
316
214
def b64(s):
317
215
    s = base64.encodestring(s).replace('\n', '')
318
216
    while (len(s) > 0) and (s[-1] == '='):
340
238
P95_MEG = int(0.9 * MEG)
341
239
P95_GIG = int(0.9 * GIG)
342
240
 
343
 
 
344
241
def human_size(size, min_divisor=0):
345
242
    size = int(size)
346
243
    if (size == 0) and (min_divisor == 0):
354
251
        divisor = MEG
355
252
    else:
356
253
        divisor = KILO
357
 
 
 
254
    
358
255
    dot = size % divisor
359
256
    base = size - dot
360
257
    dot = dot * 10 // divisor
362
259
    if dot >= 10:
363
260
        base += 1
364
261
        dot -= 10
365
 
 
 
262
    
366
263
    out = str(base)
367
264
    if (base < 100) and (dot != 0):
368
265
        out += '.%d' % (dot,)
373
270
    elif divisor == GIG:
374
271
        out += 'G'
375
272
    return out
376
 
 
377
 
 
378
 
def local_path_from_url(url):
379
 
    """Convert Bazaar URL to local path, ignoring readonly+ prefix"""
380
 
    readonly_prefix = 'readonly+'
381
 
    if url.startswith(readonly_prefix):
382
 
        url = url[len(readonly_prefix):]
383
 
    return urlutils.local_path_from_url(url)
384
 
 
385
 
 
386
 
def fill_in_navigation(navigation):
 
273
    
 
274
 
 
275
def fill_in_navigation(history, navigation):
387
276
    """
388
277
    given a navigation block (used by the template for the page header), fill
389
278
    in useful calculated values.
390
279
    """
391
 
    if navigation.revid in navigation.revid_list: # XXX is this always true?
392
 
        navigation.position = navigation.revid_list.index(navigation.revid)
393
 
    else:
 
280
    navigation.position = history.get_revid_sequence(navigation.revid_list, navigation.revid)
 
281
    if navigation.position is None:
394
282
        navigation.position = 0
395
283
    navigation.count = len(navigation.revid_list)
396
284
    navigation.page_position = navigation.position // navigation.pagesize + 1
397
 
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
398
 
 - 1)) // navigation.pagesize
399
 
 
 
285
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
 
286
    
400
287
    def get_offset(offset):
401
 
        if (navigation.position + offset < 0) or (
402
 
           navigation.position + offset > navigation.count - 1):
 
288
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
403
289
            return None
404
290
        return navigation.revid_list[navigation.position + offset]
405
 
 
406
 
    navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
 
291
    
407
292
    navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
408
293
    navigation.next_page_revid = get_offset(1 * navigation.pagesize)
409
 
    prev_page_revno = navigation.history.get_revno(
410
 
            navigation.prev_page_revid)
411
 
    next_page_revno = navigation.history.get_revno(
412
 
            navigation.next_page_revid)
413
 
    start_revno = navigation.history.get_revno(navigation.start_revid)
414
 
 
415
 
    params = {'filter_file_id': navigation.filter_file_id}
 
294
    
 
295
    params = { 'file_id': navigation.file_id }
416
296
    if getattr(navigation, 'query', None) is not None:
417
297
        params['q'] = navigation.query
418
 
 
419
 
    if getattr(navigation, 'start_revid', None) is not None:
420
 
        params['start_revid'] = start_revno
421
 
 
 
298
    else:
 
299
        params['start_revid'] = navigation.start_revid
 
300
        
422
301
    if navigation.prev_page_revid:
423
 
        navigation.prev_page_url = navigation.branch.context_url(
424
 
            [navigation.scan_url, prev_page_revno], **params)
 
302
        navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
425
303
    if navigation.next_page_revid:
426
 
        navigation.next_page_url = navigation.branch.context_url(
427
 
            [navigation.scan_url, next_page_revno], **params)
428
 
 
429
 
 
430
 
def directory_breadcrumbs(path, is_root, view):
431
 
    """
432
 
    Generate breadcrumb information from the directory path given
433
 
 
434
 
    The path given should be a path up to any branch that is currently being
435
 
    served
436
 
 
437
 
    Arguments:
438
 
    path -- The path to convert into breadcrumbs
439
 
    is_root -- Whether or not loggerhead is serving a branch at its root
440
 
    view -- The type of view we are showing (files, changes etc)
441
 
    """
442
 
    # Is our root directory itself a branch?
443
 
    if is_root:
444
 
        breadcrumbs = [{
445
 
            'dir_name': path,
446
 
            'path': '',
447
 
            'suffix': view,
448
 
        }]
449
 
    else:
450
 
        # Create breadcrumb trail for the path leading up to the branch
451
 
        breadcrumbs = [{
452
 
            'dir_name': "(root)",
453
 
            'path': '',
454
 
            'suffix': '',
455
 
        }]
456
 
        if path != '/':
457
 
            dir_parts = path.strip('/').split('/')
458
 
            for index, dir_name in enumerate(dir_parts):
459
 
                breadcrumbs.append({
460
 
                    'dir_name': dir_name,
461
 
                    'path': '/'.join(dir_parts[:index + 1]),
462
 
                    'suffix': '',
463
 
                })
464
 
            # If we are not in the directory view, the last crumb is a branch,
465
 
            # so we need to specify a view
466
 
            if view != 'directory':
467
 
                breadcrumbs[-1]['suffix'] = '/' + view
468
 
    return breadcrumbs
469
 
 
470
 
 
471
 
def branch_breadcrumbs(path, inv, view):
472
 
    """
473
 
    Generate breadcrumb information from the branch path given
474
 
 
475
 
    The path given should be a path that exists within a branch
476
 
 
477
 
    Arguments:
478
 
    path -- The path to convert into breadcrumbs
479
 
    inv -- Inventory to get file information from
480
 
    view -- The type of view we are showing (files, changes etc)
481
 
    """
482
 
    dir_parts = path.strip('/').split('/')
483
 
    inner_breadcrumbs = []
484
 
    for index, dir_name in enumerate(dir_parts):
485
 
        inner_breadcrumbs.append({
486
 
            'dir_name': dir_name,
487
 
            'path': '/'.join(dir_parts[:index + 1]),
488
 
            'suffix': '/' + view,
489
 
        })
490
 
    return inner_breadcrumbs
 
304
        navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
 
305
 
 
306
 
 
307
def log_exception(log):
 
308
    for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
 
309
        log.debug(line)
491
310
 
492
311
 
493
312
def decorator(unbound):
494
 
 
495
313
    def new_decorator(f):
496
314
        g = unbound(f)
497
315
        g.__name__ = f.__name__
504
322
    return new_decorator
505
323
 
506
324
 
 
325
# common threading-lock decorator
 
326
def with_lock(lockname, debug_name=None):
 
327
    if debug_name is None:
 
328
        debug_name = lockname
 
329
    @decorator
 
330
    def _decorator(unbound):
 
331
        def locked(self, *args, **kw):
 
332
            getattr(self, lockname).acquire()
 
333
            try:
 
334
                return unbound(self, *args, **kw)
 
335
            finally:
 
336
                getattr(self, lockname).release()
 
337
        return locked
 
338
    return _decorator
 
339
 
 
340
 
 
341
@decorator
 
342
def strip_whitespace(f):
 
343
    def _f(*a, **kw):
 
344
        out = f(*a, **kw)
 
345
        orig_len = len(out)
 
346
        out = re.sub(r'\n\s+', '\n', out)
 
347
        out = re.sub(r'[ \t]+', ' ', out)
 
348
        out = re.sub(r'\s+\n', '\n', out)
 
349
        new_len = len(out)
 
350
        log.debug('Saved %sB (%d%%) by stripping whitespace.',
 
351
                  human_size(orig_len - new_len),
 
352
                  round(100.0 - float(new_len) * 100.0 / float(orig_len)))
 
353
        return out
 
354
    return _f
 
355
 
507
356
 
508
357
@decorator
509
358
def lsprof(f):
510
 
 
511
359
    def _f(*a, **kw):
512
360
        from loggerhead.lsprof import profile
513
361
        import cPickle
514
362
        z = time.time()
515
363
        ret, stats = profile(f, *a, **kw)
516
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
517
 
            int((time.time() - z) * 1000)))
 
364
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
518
365
        stats.sort()
519
366
        stats.freeze()
520
367
        now = time.time()
521
368
        msec = int(now * 1000) % 1000
522
 
        timestr = time.strftime('%Y%m%d%H%M%S',
523
 
                                time.localtime(now)) + ('%03d' % (msec,))
 
369
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
524
370
        filename = f.__name__ + '-' + timestr + '.lsprof'
525
371
        cPickle.dump(stats, open(filename, 'w'), 2)
526
372
        return ret
534
380
#         current location along the navigation path (while browsing)
535
381
#     - starting revid (start_revid)
536
382
#         the current beginning of navigation (navigation continues back to
537
 
#         the original revision) -- this defines an 'alternate mainline'
538
 
#         when the user navigates into a branch.
 
383
#         the original revision) -- this may not be along the primary revision
 
384
#         path since the user may have navigated into a branch
539
385
#     - file_id
540
 
#         the file being looked at
541
 
#     - filter_file_id
542
386
#         if navigating the revisions that touched a file
543
387
#     - q (query)
544
388
#         if navigating the revisions that matched a search query
555
399
#         for re-ordering an existing page by different sort
556
400
 
557
401
t_context = threading.local()
558
 
_valid = (
559
 
    'start_revid', 'filter_file_id', 'q', 'remember', 'compare_revid', 'sort')
 
402
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
560
403
 
561
404
 
562
405
def set_context(map):
565
408
 
566
409
def get_context(**overrides):
567
410
    """
568
 
    Soon to be deprecated.
569
 
 
570
 
 
571
411
    return a context map that may be overriden by specific values passed in,
572
412
    but only contains keys from the list of valid context keys.
573
 
 
 
413
    
574
414
    if 'clear' is set, only the 'remember' context value will be added, and
575
415
    all other context will be omitted.
576
416
    """
582
422
    overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
583
423
    map.update(overrides)
584
424
    return map
585
 
 
586
 
 
587
 
class Reloader(object):
588
 
    """
589
 
    This class wraps all paste.reloader logic. All methods are @classmethod.
590
 
    """
591
 
 
592
 
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
593
 
 
594
 
    @classmethod
595
 
    def _turn_sigterm_into_systemexit(cls):
596
 
        """
597
 
        Attempts to turn a SIGTERM exception into a SystemExit exception.
598
 
        """
599
 
        try:
600
 
            import signal
601
 
        except ImportError:
602
 
            return
603
 
 
604
 
        def handle_term(signo, frame):
605
 
            raise SystemExit
606
 
        signal.signal(signal.SIGTERM, handle_term)
607
 
 
608
 
    @classmethod
609
 
    def is_installed(cls):
610
 
        return os.environ.get(cls._reloader_environ_key)
611
 
 
612
 
    @classmethod
613
 
    def install(cls):
614
 
        from paste import reloader
615
 
        reloader.install(int(1))
616
 
 
617
 
    @classmethod
618
 
    def restart_with_reloader(cls):
619
 
        """Based on restart_with_monitor from paste.script.serve."""
620
 
        print 'Starting subprocess with file monitor'
621
 
        while True:
622
 
            args = [sys.executable] + sys.argv
623
 
            new_environ = os.environ.copy()
624
 
            new_environ[cls._reloader_environ_key] = 'true'
625
 
            proc = None
626
 
            try:
627
 
                try:
628
 
                    cls._turn_sigterm_into_systemexit()
629
 
                    proc = subprocess.Popen(args, env=new_environ)
630
 
                    exit_code = proc.wait()
631
 
                    proc = None
632
 
                except KeyboardInterrupt:
633
 
                    print '^C caught in monitor process'
634
 
                    return 1
635
 
            finally:
636
 
                if (proc is not None
637
 
                    and getattr(os, 'kill', None) is not None):
638
 
                    import signal
639
 
                    try:
640
 
                        os.kill(proc.pid, signal.SIGTERM)
641
 
                    except (OSError, IOError):
642
 
                        pass
643
 
 
644
 
            # Reloader always exits with code 3; but if we are
645
 
            # a monitor, any exit code will restart
646
 
            if exit_code != 3:
647
 
                return exit_code
648
 
            print '-'*20, 'Restarting', '-'*20
649
 
 
650
 
 
651
 
def convert_file_errors(application):
652
 
    """WSGI wrapper to convert some file errors to Paste exceptions"""
653
 
    def new_application(environ, start_response):
654
 
        try:
655
 
            return application(environ, start_response)
656
 
        except (IOError, OSError), e:
657
 
            import errno
658
 
            from paste import httpexceptions
659
 
            if e.errno == errno.ENOENT:
660
 
                raise httpexceptions.HTTPNotFound()
661
 
            elif e.errno == errno.EACCES:
662
 
                raise httpexceptions.HTTPForbidden()
663
 
            else:
664
 
                raise
665
 
    return new_application
666
 
 
667
 
 
668
 
def convert_to_json_ready(obj):
669
 
    if isinstance(obj, Container):
670
 
        d = obj.__dict__.copy()
671
 
        del d['_properties']
672
 
        return d
673
 
    elif isinstance(obj, datetime.datetime):
674
 
        return tuple(obj.utctimetuple())
675
 
    raise TypeError(repr(obj) + " is not JSON serializable")