Olemis Lang avatar Olemis Lang committed 3925a70

GViz providers: Bloodhound web UI enhancements

Comments (0)

Files changed (6)

trac-dev/gviz/setup.py

                             # Package data
                             ['data/**'],
                           ), 
+            'tracgviz.util' : ('tracgviz/util',     # Package dir
+                            # Package data
+                            [],
+                          ), 
 #            'tracgviz.ig' : ('./ig',                     # Package dir
 #                            # Package data
 #                            ['templates/*', 'htdocs/*'],

trac-dev/gviz/tracgviz/templates/gviz_provdoc.html

   idx = count()
   ?>
   <py:def function="attr_desc(name, value)">
-    <tr class="${idx.next() % 2 and 'odd' or None}">
-      <th scope="row"><var>$name</var></th>
+    <tr class="${idx.next() % 2 and 'odd' or None}" py:choose="">
+      <td py:when="is_bhtheme_active">
+        <span class="label"><var>$name</var></span>
+      </td>
+      <th scope="row" py:otherwise="">
+        <var>$name</var>
+      </th>
       <td colspan="4"><small>$value</small></td>
     </tr>
   </py:def>
   <br/>
-  <strong> GViz data provider: ${p.__class__.__name__}</strong>
+  <h4> GViz data provider: ${p.__class__.__name__}</h4>
   <br/>
-  <table class="listing">
+  <table class="listing${' table table-striped' if is_bhtheme_active else None}">
     <thead>
-      <tr>
-        <th>Property</th>
-        <th colspan="4">Value</th>
-      </tr>
     </thead>
     <tbody>
       ${attr_desc('Name', p.__class__.__name__)}
     </tbody>
     <thead py:if="cols">
       <tr>
-        <th colspan="5">Columns</th>
+        <th colspan="5">
+          <h5>
+            <span class="label label-info" py:strip="not is_bhtheme_active">Columns</span>
+          </h5>
+        </th>
       </tr>
       <tr>
         <th>Index</th>
     </tbody>
     <thead>
       <tr>
-        <th colspan="5">Parameters</th>
+        <th colspan="5">
+          <h5>
+            <span class="label label-info" py:strip="not is_bhtheme_active">Parameters</span>
+          </h5>
+        </th>
       </tr>
       <tr>
         <th>Name</th>

trac-dev/gviz/tracgviz/util.py

-#!/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 api import IGVizProtocolHandler, IGVizTableEncoder, \
-                IGVizDataProvider, IHashLibrary, GVizBadRequestError, \
-                GVizInvalidConfigError
-from 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)
-            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))
-        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)

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

+#!/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)
+            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))
+        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)

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

+#!/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 for Apache(TM) Bloodhound integration.
+
+Copyright 2009-2011 Olemis Lang <olemis at gmail.com>
+Licensed under the Apache License, Version 2.0 
+"""
+__author__ = 'Olemis Lang'
+
+__all__ = 'is_bhtheme_active',
+
+def is_bhtheme_active(env):
+  r"""Determine whether Bloodhound theme is active in target environment
+  """
+  try:
+    from themeengine.api import ThemeEngineSystem
+    from bhtheme.theme import BloodhoundTheme
+  except ImportError:
+    return False
+  else:
+    tes = env[ThemeEngineSystem]
+    bht = env[BloodhoundTheme]
+    return tes is not None and bht is not None and \
+        tes.theme['name'] == bht.get_theme_names().next()
+

trac-dev/gviz/tracgviz/wiki.py

 import types
 from xmlrpclib import DateTime
 
-from api import TracGVizSystem, gviz_api, ITracLinksHandler, \
+from tracgviz.api import TracGVizSystem, gviz_api, ITracLinksHandler, \
                 GVizBadRequestError, gviz_col, gviz_param
-from util import GVizXMLRPCAdapter, convert_req_date, \
+from tracgviz.util import GVizXMLRPCAdapter, convert_req_date, \
                 REQFIELDS_DESC, REQFIELDS_DEFAULTS, is_dyn_schema, \
                 dummy_request, compile_pattern
+from tracgviz.util.bloodhound import is_bhtheme_active
 
 __metaclass__ = type
 
             else:
               wikifier = lambda text : text
             stream = tmpl.generate(cols=cols, p=provider, docs=docs, \
-                                    pdocs=pdocs, dyn=dyn, fmt=wikifier)
+                                    pdocs=pdocs, dyn=dyn, fmt=wikifier, \
+                                    is_bhtheme_active=is_bhtheme_active(self.env))
             return stream
         except Exception, exc:
             self.log.exception("IG: Generating provider docs ... failed")
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.