~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Michael Hudson
  • Date: 2008-09-29 21:35:13 UTC
  • Revision ID: michael.hudson@canonical.com-20080929213513-ools0krfn8l9wwf0
clean up flakes and whitespace in diff_ui.py

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
30
36
import os
31
37
import subprocess
32
38
 
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
41
 
 
42
39
log = logging.getLogger("loggerhead.controllers")
43
40
 
44
 
 
45
41
def fix_year(year):
46
42
    if year < 70:
47
43
        year += 2000
60
56
# displaydate and approximatedate return an elementtree <span> Element
61
57
# with the full date in a tooltip.
62
58
 
63
 
 
64
59
def date_day(value):
65
60
    return value.strftime('%Y-%m-%d')
66
61
 
67
62
 
68
63
def date_time(value):
69
64
    if value is not None:
70
 
        return value.strftime('%Y-%m-%d %H:%M:%S')
 
65
        return value.strftime('%Y-%m-%d %T')
71
66
    else:
72
67
        return 'N/A'
73
68
 
130
125
    return _wrap_with_date_time_title(date, _displaydate(date))
131
126
 
132
127
 
133
 
class Container(object):
 
128
class Container (object):
134
129
    """
135
130
    Convert a dict into an object with attributes.
136
131
    """
137
 
 
138
132
    def __init__(self, _dict=None, **kw):
139
 
        self._properties = {}
140
133
        if _dict is not None:
141
134
            for key, value in _dict.iteritems():
142
135
                setattr(self, key, value)
146
139
    def __repr__(self):
147
140
        out = '{ '
148
141
        for key, value in self.__dict__.iteritems():
149
 
            if key.startswith('_') or (getattr(self.__dict__[key],
150
 
                                       '__call__', None) is not None):
 
142
            if key.startswith('_') or (getattr(self.__dict__[key], '__call__', None) is not None):
151
143
                continue
152
144
            out += '%r => %r, ' % (key, value)
153
145
        out += '}'
154
146
        return out
155
147
 
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
173
 
 
174
148
 
175
149
def trunc(text, limit=10):
176
150
    if len(text) <= limit:
181
155
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
182
156
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
183
157
 
184
 
 
185
158
def hide_email(email):
186
159
    """
187
160
    try to obsure any email address in a bazaar committer's name.
201
174
        return '%s at %s' % (username, domains[-2])
202
175
    return '%s at %s' % (username, domains[0])
203
176
 
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
212
177
 
213
178
# only do this if unicode turns out to be a problem
214
179
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
215
180
 
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
181
# FIXME: get rid of this method; use fixed_width() and avoid XML().
249
 
 
250
182
def html_clean(s):
251
183
    """
252
184
    clean up a string for html display.  expand any tabs, encode any html
253
185
    entities, and replace spaces with '&nbsp;'.  this is primarily for use
254
186
    in displaying monospace text.
255
187
    """
256
 
    s = html_escape(s.expandtabs())
 
188
    s = cgi.escape(s.expandtabs())
257
189
    s = s.replace(' ', '&nbsp;')
258
190
    return s
259
191
 
260
 
 
261
192
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
262
193
 
263
 
 
264
194
def fill_div(s):
265
195
    """
266
196
    CSS is stupid. In some cases we need to replace an empty value with
268
198
 
269
199
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
270
200
    """
 
201
    
 
202
 
271
203
    if s is None:
272
204
        return '&nbsp;'
273
205
    elif isinstance(s, int):
281
213
            s = s.decode('iso-8859-15')
282
214
        return s
283
215
 
284
 
HSC = HTMLStructureCleaner()
285
216
 
286
217
def fixed_width(s):
287
218
    """
298
229
            s = s.decode('utf-8')
299
230
        except UnicodeDecodeError:
300
231
            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/>')
 
232
    return s.expandtabs().replace(' ', NONBREAKING_SPACE)
305
233
 
306
234
 
307
235
def fake_permissions(kind, executable):
340
268
P95_MEG = int(0.9 * MEG)
341
269
P95_GIG = int(0.9 * GIG)
342
270
 
343
 
 
344
271
def human_size(size, min_divisor=0):
345
272
    size = int(size)
346
273
    if (size == 0) and (min_divisor == 0):
375
302
    return out
376
303
 
377
304
 
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
305
def fill_in_navigation(navigation):
387
306
    """
388
307
    given a navigation block (used by the template for the page header), fill
394
313
        navigation.position = 0
395
314
    navigation.count = len(navigation.revid_list)
396
315
    navigation.page_position = navigation.position // navigation.pagesize + 1
397
 
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize\
398
 
 - 1)) // navigation.pagesize
 
316
    navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
399
317
 
400
318
    def get_offset(offset):
401
 
        if (navigation.position + offset < 0) or (
402
 
           navigation.position + offset > navigation.count - 1):
 
319
        if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
403
320
            return None
404
321
        return navigation.revid_list[navigation.position + offset]
405
322
 
412
329
            navigation.next_page_revid)
413
330
    start_revno = navigation.history.get_revno(navigation.start_revid)
414
331
 
415
 
    params = {'filter_file_id': navigation.filter_file_id}
 
332
    params = { 'filter_file_id': navigation.filter_file_id }
416
333
    if getattr(navigation, 'query', None) is not None:
417
334
        params['q'] = navigation.query
418
335
 
441
358
    """
442
359
    # Is our root directory itself a branch?
443
360
    if is_root:
 
361
        if view == 'directory':
 
362
            directory = 'files'
444
363
        breadcrumbs = [{
445
364
            'dir_name': path,
446
365
            'path': '',
485
404
        inner_breadcrumbs.append({
486
405
            'dir_name': dir_name,
487
406
            'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
488
 
            'suffix': '/' + view,
 
407
            'suffix': '/' + view ,
489
408
        })
490
409
    return inner_breadcrumbs
491
410
 
492
411
 
493
412
def decorator(unbound):
494
 
 
495
413
    def new_decorator(f):
496
414
        g = unbound(f)
497
415
        g.__name__ = f.__name__
504
422
    return new_decorator
505
423
 
506
424
 
 
425
# common threading-lock decorator
 
426
def with_lock(lockname, debug_name=None):
 
427
    if debug_name is None:
 
428
        debug_name = lockname
 
429
    @decorator
 
430
    def _decorator(unbound):
 
431
        def locked(self, *args, **kw):
 
432
            getattr(self, lockname).acquire()
 
433
            try:
 
434
                return unbound(self, *args, **kw)
 
435
            finally:
 
436
                getattr(self, lockname).release()
 
437
        return locked
 
438
    return _decorator
 
439
 
507
440
 
508
441
@decorator
509
442
def lsprof(f):
510
 
 
511
443
    def _f(*a, **kw):
512
444
        from loggerhead.lsprof import profile
513
445
        import cPickle
514
446
        z = time.time()
515
447
        ret, stats = profile(f, *a, **kw)
516
 
        log.debug('Finished profiled %s in %d msec.' % (f.__name__,
517
 
            int((time.time() - z) * 1000)))
 
448
        log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
518
449
        stats.sort()
519
450
        stats.freeze()
520
451
        now = time.time()
521
452
        msec = int(now * 1000) % 1000
522
 
        timestr = time.strftime('%Y%m%d%H%M%S',
523
 
                                time.localtime(now)) + ('%03d' % (msec,))
 
453
        timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
524
454
        filename = f.__name__ + '-' + timestr + '.lsprof'
525
455
        cPickle.dump(stats, open(filename, 'w'), 2)
526
456
        return ret
592
522
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
593
523
 
594
524
    @classmethod
595
 
    def _turn_sigterm_into_systemexit(cls):
 
525
    def _turn_sigterm_into_systemexit(self):
596
526
        """
597
527
        Attempts to turn a SIGTERM exception into a SystemExit exception.
598
528
        """
600
530
            import signal
601
531
        except ImportError:
602
532
            return
603
 
 
604
533
        def handle_term(signo, frame):
605
534
            raise SystemExit
606
535
        signal.signal(signal.SIGTERM, handle_term)
607
536
 
608
537
    @classmethod
609
 
    def is_installed(cls):
610
 
        return os.environ.get(cls._reloader_environ_key)
611
 
 
 
538
    def is_installed(self):
 
539
        return os.environ.get(self._reloader_environ_key)
 
540
    
612
541
    @classmethod
613
 
    def install(cls):
 
542
    def install(self):
614
543
        from paste import reloader
615
544
        reloader.install(int(1))
616
 
 
617
 
    @classmethod
618
 
    def restart_with_reloader(cls):
 
545
    
 
546
    @classmethod    
 
547
    def restart_with_reloader(self):
619
548
        """Based on restart_with_monitor from paste.script.serve."""
620
549
        print 'Starting subprocess with file monitor'
621
 
        while True:
 
550
        while 1:
622
551
            args = [sys.executable] + sys.argv
623
552
            new_environ = os.environ.copy()
624
 
            new_environ[cls._reloader_environ_key] = 'true'
 
553
            new_environ[self._reloader_environ_key] = 'true'
625
554
            proc = None
626
555
            try:
627
556
                try:
628
 
                    cls._turn_sigterm_into_systemexit()
 
557
                    self._turn_sigterm_into_systemexit()
629
558
                    proc = subprocess.Popen(args, env=new_environ)
630
559
                    exit_code = proc.wait()
631
560
                    proc = None
634
563
                    return 1
635
564
            finally:
636
565
                if (proc is not None
637
 
                    and getattr(os, 'kill', None) is not None):
 
566
                    and hasattr(os, 'kill')):
638
567
                    import signal
639
568
                    try:
640
569
                        os.kill(proc.pid, signal.SIGTERM)
641
570
                    except (OSError, IOError):
642
571
                        pass
643
 
 
 
572
                
644
573
            # Reloader always exits with code 3; but if we are
645
574
            # a monitor, any exit code will restart
646
575
            if exit_code != 3:
647
576
                return exit_code
648
577
            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