~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/util.py

  • Committer: Michael Hudson
  • Date: 2009-07-21 02:13:45 UTC
  • Revision ID: michael.hudson@canonical.com-20090721021345-dwgvfu4uneo51ui8
fix bug 382765 by deleting the ThreadSafeUIFactory.  a purely cosmetic problem remains.  also clean up some imports

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
 
 
27
from simpletal.simpleTALUtils import HTMLStructureCleaner
 
28
 
22
29
import base64
 
30
import cgi
23
31
import datetime
24
32
import logging
25
33
import re
30
38
import os
31
39
import subprocess
32
40
 
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
41
log = logging.getLogger("loggerhead.controllers")
43
42
 
44
43
 
52
51
# Display of times.
53
52
 
54
53
# date_day -- just the day
55
 
# date_time -- full date with time (UTC)
 
54
# date_time -- full date with time
56
55
#
 
56
# displaydate -- for use in sentences
57
57
# approximatedate -- for use in tables
58
58
#
59
 
# approximatedate return an elementtree <span> Element
60
 
# with the full date (UTC) in a tooltip.
 
59
# displaydate and approximatedate return an elementtree <span> Element
 
60
# with the full date in a tooltip.
61
61
 
62
62
 
63
63
def date_day(value):
66
66
 
67
67
def date_time(value):
68
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')
 
69
        return value.strftime('%Y-%m-%d %T')
71
70
    else:
72
71
        return 'N/A'
73
72
 
74
73
 
 
74
def _displaydate(date):
 
75
    delta = abs(datetime.datetime.now() - date)
 
76
    if delta > datetime.timedelta(1, 0, 0):
 
77
        # far in the past or future, display the date
 
78
        return 'on ' + date_day(date)
 
79
    return _approximatedate(date)
 
80
 
 
81
 
75
82
def _approximatedate(date):
76
83
    delta = datetime.datetime.now() - date
77
84
    if abs(delta) > datetime.timedelta(1, 0, 0):
118
125
    return _wrap_with_date_time_title(date, _approximatedate(date))
119
126
 
120
127
 
121
 
class Container(object):
 
128
def displaydate(date):
 
129
    return _wrap_with_date_time_title(date, _displaydate(date))
 
130
 
 
131
 
 
132
class Container (object):
122
133
    """
123
134
    Convert a dict into an object with attributes.
124
135
    """
125
136
 
126
137
    def __init__(self, _dict=None, **kw):
127
 
        self._properties = {}
128
138
        if _dict is not None:
129
139
            for key, value in _dict.iteritems():
130
140
                setattr(self, key, value)
141
151
        out += '}'
142
152
        return out
143
153
 
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
154
 
163
155
def trunc(text, limit=10):
164
156
    if len(text) <= limit:
201
193
# only do this if unicode turns out to be a problem
202
194
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
203
195
 
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
196
# FIXME: get rid of this method; use fixed_width() and avoid XML().
237
197
 
 
198
 
238
199
def html_clean(s):
239
200
    """
240
201
    clean up a string for html display.  expand any tabs, encode any html
241
202
    entities, and replace spaces with '&nbsp;'.  this is primarily for use
242
203
    in displaying monospace text.
243
204
    """
244
 
    s = html_escape(s.expandtabs())
 
205
    s = cgi.escape(s.expandtabs())
245
206
    s = s.replace(' ', '&nbsp;')
246
207
    return s
247
208
 
256
217
 
257
218
    return: the same value recieved if not empty, and a '&nbsp;' if it is.
258
219
    """
 
220
 
 
221
 
259
222
    if s is None:
260
223
        return '&nbsp;'
261
224
    elif isinstance(s, int):
287
250
        except UnicodeDecodeError:
288
251
            s = s.decode('iso-8859-15')
289
252
 
290
 
    s = html_escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
 
253
    s = cgi.escape(s).expandtabs().replace(' ', NONBREAKING_SPACE)
291
254
 
292
255
    return HSC.clean(s).replace('\n', '<br/>')
293
256
 
353
316
 
354
317
    out = str(base)
355
318
    if (base < 100) and (dot != 0):
356
 
        out += '.%d' % (dot,)
 
319
        out += '.%d' % (dot)
357
320
    if divisor == KILO:
358
321
        out += 'K'
359
322
    elif divisor == MEG:
363
326
    return out
364
327
 
365
328
 
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
329
def fill_in_navigation(navigation):
375
330
    """
376
331
    given a navigation block (used by the template for the page header), fill
472
427
    for index, dir_name in enumerate(dir_parts):
473
428
        inner_breadcrumbs.append({
474
429
            'dir_name': dir_name,
475
 
            'path': '/'.join(dir_parts[:index + 1]),
 
430
            'file_id': inv.path2id('/'.join(dir_parts[:index + 1])),
476
431
            'suffix': '/' + view,
477
432
        })
478
433
    return inner_breadcrumbs
508
463
        now = time.time()
509
464
        msec = int(now * 1000) % 1000
510
465
        timestr = time.strftime('%Y%m%d%H%M%S',
511
 
                                time.localtime(now)) + ('%03d' % (msec,))
 
466
                                time.localtime(now)) + ('%03d' % msec)
512
467
        filename = f.__name__ + '-' + timestr + '.lsprof'
513
468
        cPickle.dump(stats, open(filename, 'w'), 2)
514
469
        return ret
543
498
#         for re-ordering an existing page by different sort
544
499
 
545
500
t_context = threading.local()
546
 
_valid = (
547
 
    'start_revid', 'filter_file_id', 'q', 'remember', 'compare_revid', 'sort')
 
501
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
 
502
          'compare_revid', 'sort')
548
503
 
549
504
 
550
505
def set_context(map):
580
535
    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
581
536
 
582
537
    @classmethod
583
 
    def _turn_sigterm_into_systemexit(cls):
 
538
    def _turn_sigterm_into_systemexit(self):
584
539
        """
585
540
        Attempts to turn a SIGTERM exception into a SystemExit exception.
586
541
        """
594
549
        signal.signal(signal.SIGTERM, handle_term)
595
550
 
596
551
    @classmethod
597
 
    def is_installed(cls):
598
 
        return os.environ.get(cls._reloader_environ_key)
 
552
    def is_installed(self):
 
553
        return os.environ.get(self._reloader_environ_key)
599
554
 
600
555
    @classmethod
601
 
    def install(cls):
 
556
    def install(self):
602
557
        from paste import reloader
603
558
        reloader.install(int(1))
604
559
 
605
560
    @classmethod
606
 
    def restart_with_reloader(cls):
 
561
    def restart_with_reloader(self):
607
562
        """Based on restart_with_monitor from paste.script.serve."""
608
563
        print 'Starting subprocess with file monitor'
609
 
        while True:
 
564
        while 1:
610
565
            args = [sys.executable] + sys.argv
611
566
            new_environ = os.environ.copy()
612
 
            new_environ[cls._reloader_environ_key] = 'true'
 
567
            new_environ[self._reloader_environ_key] = 'true'
613
568
            proc = None
614
569
            try:
615
570
                try:
616
 
                    cls._turn_sigterm_into_systemexit()
 
571
                    self._turn_sigterm_into_systemexit()
617
572
                    proc = subprocess.Popen(args, env=new_environ)
618
573
                    exit_code = proc.wait()
619
574
                    proc = None
622
577
                    return 1
623
578
            finally:
624
579
                if (proc is not None
625
 
                    and getattr(os, 'kill', None) is not None):
 
580
                    and hasattr(os, 'kill')):
626
581
                    import signal
627
582
                    try:
628
583
                        os.kill(proc.pid, signal.SIGTERM)
634
589
            if exit_code != 3:
635
590
                return exit_code
636
591
            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")