Source

trac-gviz / trac-dev / gviz / tracgviz / util / __init__.py

Full commit
Olemis Lang f4e31f7 



























Olemis Lang fc0334a 


Olemis Lang f4e31f7 
Olemis Lang fc0334a 
Olemis Lang f4e31f7 
Olemis Lang fc0334a 

Olemis Lang f4e31f7 






Olemis Lang fc0334a 
Olemis Lang f4e31f7 

Olemis Lang fc0334a 



Olemis Lang f4e31f7 


Olemis Lang fc0334a 

Olemis Lang f4e31f7 



Olemis Lang fc0334a 


Olemis Lang f4e31f7 


































Olemis Lang fc0334a 


Olemis Lang f4e31f7 




Olemis Lang fc0334a 








Olemis Lang f4e31f7 






































































































































































Olemis Lang fc0334a 






















Olemis Lang f4e31f7 
Olemis Lang fc0334a 
















































































Olemis Lang 41ab8e7 



Olemis Lang fc0334a 













Olemis Lang f4e31f7 















Olemis Lang fc0334a 



Olemis Lang f4e31f7 

































Olemis Lang fc0334a 


























Olemis Lang f4e31f7 





































































Olemis Lang fc0334a 


















































































































































  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# Copyright 2009-2011 Olemis Lang <olemis at gmail.com>
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

r"""Helper (abstract) classes used to implement custom data sources,
formatters, and protocol handlers.

Copyright 2009-2011 Olemis Lang <olemis at gmail.com>
Licensed under the Apache License, Version 2.0 
"""
__author__ = 'Olemis Lang'

__all__ = 'BaseGVizHandler', 'GVizXMLRPCAdapter', 'dummy_request', \
          'convert_req_date', 'rpc_to_datetime', 'render_gviz_value', \
          'get_column_desc', 'TYPES_2_GVIZ', 'get_column_desc', \
          'rpc_opt_sigs', 'REQFIELDS_DESC', 'REQFIELDS_DEFAULTS', \
          'is_dyn_schema', 'compile_pattern', 'ObjectIntercept', \
          'send_response', 'iter_table_data', 'StringIO', 'UTF8Recoder'

from trac.core import Component, ExtensionPoint, implements, TracError
from trac.config import Option
from trac.mimeview.api import Mimeview
from trac.util.text import to_utf8
from trac.web.api import RequestDone, Request
from trac.web.chrome import Chrome
from trac.web.main import RequestDispatcher

from tracrpc.api import XMLRPCSystem, Method

from BaseHTTPServer import DEFAULT_ERROR_MESSAGE, BaseHTTPRequestHandler
from cgi import parse_qs
from datetime import datetime, date, time
from itertools import takewhile, chain, imap, repeat, izip
from fnmatch import translate
from re import compile
from urlparse import urlparse
import types
from xmlrpclib import DateTime

from tracgviz.api import IGVizProtocolHandler, IGVizTableEncoder, \
                IGVizDataProvider, IHashLibrary, GVizBadRequestError, \
                GVizInvalidConfigError
from tracgviz.testing.util import dummy_request

__metaclass__ = type

#--------------------------------------------------
#   Abstract classes used to implement GViz components
#--------------------------------------------------

class BaseGVizHandler(Component):
    r"""This class encloses the functionality which might be present
    in most versions of Google Visualization API. It can be reused by
    specific subclasses implementing a specific protocol version
    as defined by the Google Visualization API.
    """
    abstract = True
    implements(IGVizProtocolHandler)
    encoders = ExtensionPoint(IGVizTableEncoder)
    hashlibs = ExtensionPoint(IHashLibrary)
    
    hash_name = Option('gviz', 'hash', default=None, 
                        doc="""The algorithm used to generate a hash """
                            """of the data sent back to the client. This """
                            """feature is defined by Google """
                            """Visualization API since version 0.5 so as """
                            """to optimize the request / response """
                            """mechanism to make rational use of the """
                            """available bandwith.""")
    
    def _init_hash(self):
        r"""Setup the secure hash algorithm.
        """
        hash_name = self.hash_name
        self._hlib = self.hash_obj = None
        self.log.debug("IG: Config hash method : '%s'", hash_name)
        if hash_name:
          pr = -1
          for hlib in self.hashlibs:
            self.log.debug("IG: Processing : %s", hlib)
            try:
              cur_pr, _ = hlib.get_hash_properties(hash_name)
            except TypeError:
              self.log.debug("IG: %s doesnt support '%s'", hlib, hash_name)
            except :
              self.log.exception('IG: Error processing %s', hlib)
              raise
            else:
              if cur_pr > pr:
                self._hlib = hlib
                pr = cur_pr
        if self._hlib is not None:
          try:
            self.hash_obj = self._hlib.new_hash_obj(hash_name)
            self.log.info("IG: Hash method '%s' lib '%s'", hash_name, self._hlib)
          except GVizInvalidConfigError, exc:
            self.log.warning("IG: Hash method '%s' lib '%s' failed: %s", \
                              hash_name, self._hlib, str(exc))
          except :
            self.log.exception('IG: Error creating hash using %s', hlib)
            raise
        else:
          self.log.info("IG: Hash method 'None'")
    
    @staticmethod
    def fmt_supports_version(encoder, version):
        r"""Return whether a data table encoder supports a specific
        version of Google Visualization API."""
        
        rels = {
                '>' : tuple.__gt__,
                '<' : tuple.__lt__,
                '>=' : tuple.__ge__,
                '<=' : tuple.__le__,
                '==' : tuple.__eq__,
                '!=' : lambda x, y: x != y,
               }
        
        versions = encoder.supported_versions()
        return all(rels[r](v, version) for r, v in versions)
    
    def find_encoder(self, fmt_id, version, mime_type=None):
        r"""Find an encoder able to convert a data table contents into
        a specific format, maybe having a well-known content-type.
        
        @param fmt_id the output format id
        @param version the particular protocol `version` of Google
                    Visualization API in use.
        @param mime_type if specified then a best match is made to 
                    return an encoder returning the specific content
                    type requested by the caller.
                    
        @return the best match made according to the available 
                    encoders or `None` if no such encoder could be
                    found. This encoder *must* support the requested
                    format and protocol version, and *should*
                    use the requested content-type, but the later
                    assertion *is not compulsory*.
        """
        encoders = self._fmt[fmt_id]
        encoders = takewhile(
                lambda e: self.fmt_supports_version(e, version), 
                encoders)
        try:
            first = encoders.next()
        except StopIteration:
            return None
        else:
            if mime_type is None or first.get_content_type() == mime_type:
                return first
            else:
                try:
                    return takewhile(
                            lambda e: e.get_content_type() == mime_type, 
                            encoders).next()
                except StopIteration:
                    return first
    
    def _init_fmt(self):
        """Arrange the available format encoders.
        """
        self._fmt = dict()
        for e in self.encoders:
            self._fmt.setdefault(e.get_format_id(), []).append(e)
    
    def __init__(self):
        self._init_fmt()
        self._init_hash()
        
    # TODO : Implement common features.

class VoidRpcHandler:
    def __getattr__(self, attrnm):
        raise AttributeError("The requested XML-RPC handler cannot "
                                "be found. Either it doesn't exist "
                                "or the component is disabled. "
                                "Contact your Trac administrator.")

#class Method(Method):
#    r"""A faster XML-RPC method implementation since it returns 
#    iterators instead of lists.
#    """
#    def __call__(self, req, args):
#        req.perm.assert_permission(self.permission)
#        result = self.callable(req, *args)
#        # If result is null, return a zero
#        if result is None:
#            result = 0
#        elif isinstance(result, dict):
#            for key,val in result.iteritems():
#                if isinstance(val, datetime.datetime):
#                    result[key] = to_datetime(val)
#            #pass
#        elif not isinstance(result, basestring):
#            # Try and convert result to a list
#            try:
#                result = (i for i in result)
#            except TypeError:
#                pass
#        return (result,)

class RPCHelperObject:
    r"""A proxy class needed to assert the permissions handled by 
    XMLRPCSystem, instead of using directly to the RPC method.
    """
    def __init__(self, rpc_obj):
        methods = (Method(rpc_obj, *mi) for mi in rpc_obj.xmlrpc_methods())
        prefix_len = len(rpc_obj.xmlrpc_namespace()) + 1
        
        def method_wrapper(m):
            wrapper = lambda req, *args: m(req, args)[0]
            wrapper.__module__ = m.callable.__module__
            wrapper.func_name = m.callable.__name__
            return wrapper
        self.__methods = dict([m.name[prefix_len:], method_wrapper(m)] \
                                for m in methods)
        rpc_obj.log.debug('IG: RPC methods %s', self.__methods)
        self.__rpc_obj = rpc_obj
    
    def __getattr__(self, attrnm):
        r"""Try to retrieve the XML-RPC method first. Otherwise return 
        the attribute of the underlying XML-RPC object.
        """
        try:
            return self.__methods[attrnm]
        except KeyError:
            return getattr(self.__rpc_obj, attrnm)

class GVizXMLRPCAdapter(Component):
    r"""Base class for components whose main purpose is to provide 
    some data relying on an existing XML-RPC handler (i.e. a 
    component implementing tracrpc.api.IXMLRPCHandler interface). 
    The data source is meant to reuse the RPC provider namespace and
    logic.
    """
    implements(IGVizDataProvider)
    abstract = True
    
    def __init__(self):
        r"""Assign the corresponding XML RPC handler to this data
        source provider. 
        
        Note: Since Trac core system components hack the initializer,
        further initialiation steps needed by sub-classes should be
        coded by overriding `do_init` method.
        """
        try:
            rpcns = '.'.join(self.xmlrpc_namespace())
        except AttributeError:
            rpcns = '.'.join(self.gviz_namespace())
        self.log.debug('IG: RPC Namespace %s Ok', rpcns)
        for rpc_provider in XMLRPCSystem(self.env).method_handlers:
            # TODO : Implement a proper match for regex in gviz ns
            if rpc_provider.xmlrpc_namespace() == rpcns:
                # Substituted in order to reuse permissions asserted 
                # by XMLRPCSystem.
                # self._rpc_obj = rpc_provider
                self._rpc_obj = RPCHelperObject(rpc_provider)
                break
        else:
            self._rpc_obj = VoidRpcHandler()
            self.log.info('IG: Missing XML-RPC handler %s' % (rpcns,))
        try:
            __init__ = self.do_init
        except AttributeError:
            pass
        else:
            __init__()
        
        if self._is_rpc_datetime :    # Assume datetime as default
          self._rpc_date_impl = lambda dt, req : dt
        else :
          self._rpc_date_impl = lambda dt, req : DateTime(dt)
    
    @property
    def _is_rpc_datetime(self):
        r"""Determine whether the underlying Rpc implementation 
        manipulates instances of `xmlrpclib.DateTime` or 
        `datetime.datetime` (>=1.0.6). 
        
        Immediately after this change was committed, `RPC` entry was 
        added to the system information. So we check for the later 
        condition as a workaround.
        """
        for entrynm, _ in self.env.systeminfo:
          if entrynm == 'RPC':
            self.__dict__['_is_rpc_datetime'] = True
            return True
        else :
          self.__dict__['_is_rpc_datetime'] = False
          return False

class GVizContentProvider(Component):
    r"""Base class useful to implement data sources that convert 
    contents stored in a source file or buffer in order to feed GViz 
    data tables.
    """
    abstract = True
    implements(IGVizDataProvider)
    
    # IGVizDataProvider methods
    def get_data_schema(self, req):
        r"""Provide the schema used to populate GViz data tables out 
        of the file object returned by `get_input_contents` method 
        and used to feed the data table.
        """
        content, mimetype = self.get_input_contents(req)
        self.log.debug("IG: Detected %s, processing %s", mimetype, content.__class__)
        try :
          mimesys = Mimeview(self.env)
          (cols, data), mimetype, ext = mimesys.convert_content(
                                                    req, mimetype, \
                                                    content, 'trac.gviz')
        except TracError :
          self.log.exception("IG: Error converting mimetype %s to 'trac.gviz'", \
                                  mimetype)
          raise NotImplementedError("Impossible to load data from `%s`" \
                                        % (mimetype,))
        req.args['_rawdata'] = data
        return cols
    
    def get_data(self, req, tq, **tqx):
        return req.args.get('_rawdata') or []

    # API methods
    def get_input_contents(self):
        r"""Retrieve the source used to feed the data table.
        
        @return     a binary tuple of the form (`content`, `mimetype`) 
                    where `contents` is a file-like object or a 
                    buffer (string, unicode, ...) and `mimetype` is 
                    the content type to consider when performing the 
                    conversion.
        """
        raise NotImplementedError("Unknown source file or buffer")
    
    def guess_mimetype(self, fd, fnm):
        r"""Try to guess the MIME type of the input file.
        
        @param fd       input file object
        @param fnm      input file name
        @return         a ternary tuple of the form 
                        (`content`, `mimetype`, `rewinded`) where 
                        
                        `content`   the input file object (i.e. `fd`) 
                                    if the file pointer could be 
                                    posiotioned at the beginning
                                    (i.e. if `rewinded` is true)
                        `mimetype`  best match found
                        `rewinded`  whether the file pointer could be 
                                    posiotioned at the beginning or not
        """
        mimesys = Mimeview(self.env)
        content = fd.read(mimesys.max_preview_size)
        mimetype = mimesys.get_mimetype(fnm, content)
        try :
          fd.seek(0)                          # Back to the beginning
        except :
          # Read in buffer
          content+= fd.read()
          return content, mimetype, False
        else:
          return fd, mimetype, True

#--------------------------------------------------
#   Helper functions related to Trac Request(s)
#--------------------------------------------------

def send_response(req, status, response, mimetype='text/plain', \
                    extra_headers=dict()):
    r"""Send an HTTP response back to the caller.
    """
    req.send_response(status)
    req.send_header('Content-Type', mimetype + ';charset=utf-8')
    if isinstance(response, unicode):
      response = response.encode('utf-8')
    req.send_header('Content-Length', len(response))
    for k, v in dict(extra_headers).iteritems():
        req.send_header(k, v)
    req.end_headers()
    req.write(response)
    raise RequestDone()

def send_std_error_response(req, status):
    r"""Send an HTTP error response back to the caller using a 
    standard template.
    """
    message, explain = BaseHTTPRequestHandler.responses[status]
    errctx = dict(code=status, message=message, explain=explain)
    send_response(req, status, DEFAULT_ERROR_MESSAGE % errctx, \
                    mimetype='text/html')
def convert_req_date(when, fmt, req, xmlfmt=True):
    r"""Convert a string to the corresponding datetime value using 
    the specified format string.
    """
    try:
      if when is not None:
          when = datetime.strptime(when, fmt)
          when = when.replace(tzinfo=req.tz)
      else:
          when = datetime.now(tz=req.tz)
      if xmlfmt:
          when = DateTime(when)
      return when
    except:
      raise GVizBadRequestError("Invalid datetime value or wrong date format.")

#--------------------------------------------------
#   Helper functions used by RPC handlers
#--------------------------------------------------

def rpc_to_datetime(DT, req):
    r"""Return the datetime object representing the xmlrpclib.DateTime 
    value in `DT`. The return value is at the timezone of the 
    environment processing the request `req`.
    """
    dt = datetime.strptime(DT.value, '%Y%m%dT%H:%M:%S')
    return dt.replace(tzinfo=req.tz)

def __insert_many_id(id, _tuple): 
    return (id,) + _tuple

def __insert_value_id(id, value): 
    return (id, value)

def map_with_id(req, ids, func, ins, *iterables):
    if iterables:
        iterables = izip(*iterables)
    else:
        iterables = repeat(tuple())
    return chain(*(imap(ins, repeat(x), func(req, x, *args)) \
            for x, args in izip(ids, iterables)))

def map_many_with_id(req, ids, func, *iterables):
    return map_with_id(req, ids, func, __insert_many_id, *iterables)

def map_value_with_id(req, ids, func, *iterables):
    return map_with_id(req, ids, func, __insert_value_id, *iterables)

DEFAULT_DATE_FORMATS = {
    'date' : "%Y-%m-%d",
    'datetime' : "%Y-%m-%d %H:%M:%S",
    'timeofday' : "%H:%M:%S",
  }

def rpc_opt_sigs(ret_type, fixed_types=None, *opt_types):
  r"""Generate tuples describing the signatures of an XML-RPC method 
  whose arguments can take values in a set of optional types or 
  be missing in the method call.
  """
  if fixed_types is None:
    fixed_types = ()
  else:
    fixed_types = tuple(fixed_types)
  
  new_sig = (ret_type,) + fixed_types
  yield new_sig
  old_gen = [new_sig]
  
  for arg_types in opt_types:
    new_gen = []
    for sig in old_gen:
      for arg_type in arg_types:
        new_sig = sig + (arg_type,)
        yield new_sig
        new_gen.append(new_sig)
    old_gen = new_gen

#--------------------------------------------------
#   Helper GViz functions
#--------------------------------------------------

def render_gviz_value(value, gviz_type, table, req_or_env):
  r"""Return a string used to display the values inside GViz data 
  sources.
  """
  if isinstance(req_or_env, Request):
    req = req_or_env
  else:
    # Assume it's an instance of Environment
    req = dummy_request(req_or_env)
  try:
    date_fmt_str = DEFAULT_DATE_FORMATS[gviz_type]
  except KeyError:
    return table.SingleValueToJS(value, gviz_type)
  else:
    try:
      if isinstance(value, DateTime):
        value = rpc_to_datetime(value, req)
      elif isinstance(value, int):
        value = datetime.fromtimestamp(int(value or 0), req.tz)
      return value.strftime(date_fmt_str)
    except Exception, exc:
      return '(Unknown: %s)' % (exc,)

TYPES_2_GVIZ = {
            type(None): 'string',
            str : 'string',
            unicode : 'string',
            long : 'number',
            int : 'number',
            datetime : 'datetime',
            date : 'date', 
            time : 'timeofday',
            DateTime : 'datetime',
            bool : 'boolean',
          }

def get_column_desc(cursor, infer=False):
  r"""Retrieve a sequence of tuples (name, type) describing 
  the columns present in the results provider by a cursor object 
  after executing a database query.
  """
  row = None
  if cursor.description:
    for i, d in enumerate(cursor.description):
      name, type_code = d[:2]
      if isinstance(name, str):
        name = unicode(name, 'utf-8')
      if type_code is None and infer:
        if row is None:
          try:
            row, = cursor.fetchmany(1)
          except:
            row = ('',) * len(list(cursor.description))
        type_code = TYPES_2_GVIZ.get(row[i].__class__)
      yield name, type_code

REQFIELDS_DESC = {
      'datefmt'  : "The syntax of %(args)s field%(plural)s. Here you "
                            "can embed the directives supported by "
                            "`time.strftime` function. The default "
                            "behavior is to accept the well known "
                            "format `yyyy-mm-dd HH:MM:SS` which is "
                            "actually written like this "
                            "`%%Y-%%m-%%d %%H:%%M:%%S`.",
    }

REQFIELDS_DEFAULTS = {
      'datefmt'  : "%Y-%m-%d %H:%M:%S"
    }

def is_dyn_schema(provider):
  r"""Determine whether the schema defined by `provider` is static 
  or dynamic (i.e may change at run-time).
  
  @param provider     an instance of `IGVizDataProvider` interface.
  @return             `True` if the schema may change at run-time
                      `False` otherwise (i.e. schema is static).
  """
  sch = provider.get_data_schema.im_func.func_code
  return sch.co_argcount > 1

def iter_table_data(table, sort_keys=()):
  r"""Iterate over the different rows present in a data table.
  Data in each row will be retrieved in the order determined by 
  the table's columns.
  
  @param sort_keys    list of keys to sort by. For further details 
                      please read docstrings for 
                      `gviz_api.DataTable._PreparedData`.
  """
  colnms = [col["id"] for col in table.columns]
  for row in table._PreparedData(sort_keys):
      yield tuple(row.get(col) for col in colnms)

#--------------------------------------------------
#   Intercepting attribute access
#--------------------------------------------------

class ObjectIntercept:
  r"""Objects used to override the semantics of attribute access.
  """
  def __init__(self, obj, basecls):
    r"""Initialize.
      
    @param obj        the real subject.
    @param basecls    replace reference to `self` before returning 
                      methods of this class.
    """
    self._target = obj
    self._basecls = basecls
  def __getattr__(self, attrnm):
    val = getattr(self._target, attrnm)
    if isinstance(val, types.MethodType) and val.im_class is self._basecls:
      val = val.im_func.__get__(self, self.__class__)
      setattr(self, attrnm, val)
    return val

class IoIntercept(ObjectIntercept):
  r"""Write the contents of the HTTP response to a stream.
  
  The following methods may be overriden :
    - raise_status_failed    :  Raise an exception if `HTTP OK` 
                                (i.e. `200`) is not the status code 
                                returned.
  """
  def __init__(self, reqi, strm):
    r"""Initialize.
    
    @param reqi         a request or intercept object.
    @param strm         the stream used to gather the HTTP response.
    """
    super(IoIntercept, self).__init__(reqi, Request)
    self.__strm = strm
  def _start_response(self, status, headers):
    r"""Ensure that the response code is `HTTP OK` (i.e. `200`).
    Start writing to the stream object.
    
    Parameters are ignored.
    """
    if status.lower() != '200 ok':
      self.raise_status_failed(status)
    return self.__strm.write
  def send_header(self, name, value):
    r"""Don't send any header, but intercept to prevent writes to 
    `request._outheaders`. Otherwise if there's an outer request 
    being handled then the headers of the inner response will be 
    written together with the ones of the outer response. This may 
    lead to chaos (e.g. truncated responses because the content length 
    returned by the inner response interferes with the one reported by 
    the outer response).
    """
  def raise_status_failed(self, code):
    raise TracError('Request failed with status %s' % (code,))

class RedirectIntercept(ObjectIntercept):
  r"""Redirect the request so that it be processed by another 
  request handler.
  
  The following methods may be overriden :
    - raise_status_failed    :  Raise an exception if `HTTP OK` 
                                (i.e. `200`) is not the status code 
                                returned.
  """
  def __getattr__(self, attrnm):
    self.__env.log.debug("IG: Intercepting attribute %s", attrnm)
    return ObjectIntercept.__getattr__(self, attrnm)
  def __init__(self, reqi, env, **params):
    r"""Initialize.
    
    @param reqi         a request or intercept object.
    @param uri_suffix   the suffix of the destination URI (i.e. the 
                        part of it that's inside the web 
                        application's URI space).
    """
    super(RedirectIntercept, self).__init__(reqi, Request)
    self.__params = params
    self.__env = env
  def redirect(self, url, permanent=False):
    r"""Delegate processing upon the corresponding request handler.
    """
    uobj = urlparse(url)
    self.path_info = uobj.path
    self.args = parse_qs(uobj.query)
    self.args.update(self.__params)
    
    # Needed because Trac built-in protection against CSRF attacks 
    # is based on validating form tokens. If not set then form 
    # submissions (e.g. previews) will fail.
    self.args['__FORM_TOKEN'] = self._target.args.get('__FORM_TOKEN')
    
    try:
      RequestDispatcher(self.__env).dispatch(self)
    except (RequestDone, TracError):
      raise
    except Exception, exc:
      raise TracError("Error %s : %s" % (exc.__class__.__name__, \
                                          exc.message))

#--------------------------------------------------
#   Miscellaneous
#--------------------------------------------------

def compile_pattern(fnp):
  r"""Return a compiled UNIX file name pattern. Used for efficiency.
  """
  return compile(translate(fnp))

try :
  from cStringIO import StringIO
except ImportError:
  from StringIO import StringIO

def UTF8Recoder(f, encoding='utf-8'):
  r"""Iterator that reads an encoded stream and reencodes the input 
  to UTF-8.
  """
  from codecs import EncodedFile
  return EncodedFile(f, 'utf-8', encoding)