[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/apidoc/ When browsing through the source tree using the "Class Documentation

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun Jul 18 17:59:44 EDT 2004


Log message for revision 26615:
  When browsing through the source tree using the "Class Documentation 
  Module", ZCML configuration files are now listed. When you click on 
  them, their content will be shown. The content is marked up with HTML 
  and provides links to other documentation modules.
  


Changed:
  U   Zope3/trunk/src/zope/app/apidoc/browser/apidoc.css
  U   Zope3/trunk/src/zope/app/apidoc/classmodule/__init__.py
  U   Zope3/trunk/src/zope/app/apidoc/classmodule/browser.py
  U   Zope3/trunk/src/zope/app/apidoc/classmodule/configure.zcml
  U   Zope3/trunk/src/zope/app/apidoc/classmodule/ftests.py
  U   Zope3/trunk/src/zope/app/apidoc/classmodule/module_index.pt
  A   Zope3/trunk/src/zope/app/apidoc/classmodule/zcmlfile_index.pt


-=-
Modified: Zope3/trunk/src/zope/app/apidoc/browser/apidoc.css
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/browser/apidoc.css	2004-07-18 21:56:36 UTC (rev 26614)
+++ Zope3/trunk/src/zope/app/apidoc/browser/apidoc.css	2004-07-18 21:59:44 UTC (rev 26615)
@@ -6,7 +6,7 @@
     padding: 0pt;
 }
 
-code, pre {
+code, pre, span.pre {
   font-size: 120%;
 }
 
@@ -147,3 +147,23 @@
   padding: 0;
   margin: 0 0.5em 0 0;
 }
+
+/* Styles for ZCML markup */
+
+code.zcml a {
+  color: inherit;
+  font-style: italic;
+}
+
+code.zcml .attribute {
+  color: MediumVioletRed;
+}
+
+code.zcml .tagname {
+  color: blue;
+  font-weight: bold;
+}
+
+code.zcml .objectref {
+  color: DarkSalmon;
+}
\ No newline at end of file

Modified: Zope3/trunk/src/zope/app/apidoc/classmodule/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classmodule/__init__.py	2004-07-18 21:56:36 UTC (rev 26614)
+++ Zope3/trunk/src/zope/app/apidoc/classmodule/__init__.py	2004-07-18 21:59:44 UTC (rev 26615)
@@ -28,7 +28,6 @@
 import zope
 from zope.security.checker import getCheckerForInstancesOf
 from zope.interface import Interface, Attribute, implements, implementedBy
-from zope.app import zapi
 from zope.app.container.interfaces import IReadContainer
 from zope.app.i18n import ZopeMessageIDFactory as _
 
@@ -126,6 +125,12 @@
         second is the attribute object itself.
         """
 
+class IZCMLFileDocumentation(Interface):
+    """Representation of a function for documentation."""
+
+    path = Attribute('Full path of the ZCML file.')
+
+
 class Module(ReadContainerBase):
     """This class represents a Python module.
 
@@ -155,9 +160,8 @@
 
       >>> keys = module.keys()
       >>> keys.sort()
-      >>> keys[:5]
-      ['APIDocumentation', 'apidocNamespace', 'browser', 'classmodule', """ \
-          """'handleNamespace']
+      >>> keys[:3]
+      ['APIDocumentation', 'apidoc-configure.zcml', 'apidocNamespace']
 
       >>> print module['browser'].getPath()
       zope.app.apidoc.browser
@@ -216,6 +220,9 @@
                     if module is not None:
                         self.__children[name] = Module(self, name, module)
 
+                elif os.path.isfile(path) and file.endswith('.zcml'):
+                    self.__children[file] = ZCMLFile(self, file, path)
+
         # Setup classes in module, if any are available.
         for name in self.__module.__dict__.keys():
             attr = getattr(self.__module, name)
@@ -488,6 +495,30 @@
         return self.__func.__dict__.items()
 
 
+class ZCMLFile(object):
+    """Represent the documentation of any ZCML file.
+
+    This object in itself is rather simple, since it only stores the full path
+    of the ZCML file and its location in the documentation tree.
+
+    >>> zcml = ZCMLFile(None, 'foo.zcml', '/Zope3/src/zope/app/foo.zcml')
+    >>> zcml.__parent__ is None
+    True
+    >>> zcml.__name__
+    'foo.zcml'
+    >>> zcml.path
+    '/Zope3/src/zope/app/foo.zcml'
+    """
+    
+    implements(ILocation, IZCMLFileDocumentation)
+
+    def __init__(self, module, name, path):
+        """Initialize the object."""
+        self.__parent__ = module
+        self.__name__ = name
+        self.path = path
+
+
 class ClassModule(Module):
     """Represent the Documentation of any possible class.
 

Modified: Zope3/trunk/src/zope/app/apidoc/classmodule/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classmodule/browser.py	2004-07-18 21:56:36 UTC (rev 26614)
+++ Zope3/trunk/src/zope/app/apidoc/classmodule/browser.py	2004-07-18 21:59:44 UTC (rev 26615)
@@ -16,24 +16,35 @@
 $Id$
 """
 __docformat__ = 'restructuredtext'
-
+import inspect
 import os
-import inspect
+import re
+from types import TypeType, ClassType, FunctionType, ModuleType
+import xml.dom.minidom
+import xml.sax.saxutils
 
+from zope.configuration import docutils, xmlconfig
 from zope.configuration.config import ConfigurationContext
+from zope.configuration.fields import GlobalObject, Tokens
 from zope.exceptions import NotFoundError
 from zope.interface import implementedBy
+from zope.interface.interface import InterfaceClass
 from zope.proxy import removeAllProxies
+from zope.schema import getFieldsInOrder
 
+import zope.app
 from zope.app import zapi
 from zope.app.i18n import ZopeMessageIDFactory as _
+from zope.app.apidoc.classmodule import Module, Class, Function, ZCMLFile
+from zope.app.apidoc.classmodule import classRegistry
+from zope.app.apidoc.interfaces import IDocumentationModule
 from zope.app.apidoc.utilities import getPythonPath, renderText, columnize
 from zope.app.apidoc.utilities import getPermissionIds, getFunctionSignature
 from zope.app.apidoc.utilities import getPublicAttributes
 from zope.app.apidoc.utilities import getInterfaceForAttribute
-from zope.app.apidoc.classmodule import Module, Class, Function, classRegistry
-from zope.app.apidoc.interfaces import IDocumentationModule
+from zope.app.apidoc.zcmlmodule import quoteNS
 
+
 class Menu(object):
     """Menu for the Class Documentation Module.
 
@@ -111,6 +122,244 @@
         return results
 
 
+_obj_attr_re = r'(?P<start>&lt;%s.*?%s=".*?)(?P<value>%s)(?P<end>".*?&gt;)'
+_attrname_re = r'(?P<start>&lt;.*?%s.*?)(?P<attr>%s)(?P<end>=".*?".*?&gt;)'
+directives = {}
+
+class ZCMLFileDetails(ConfigurationContext):
+    """Represents the details of the ZCML file."""
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        package = removeAllProxies(zapi.getParent(context))._Module__module
+        # Keep track of the package that is used for relative paths
+        self._package_stack = [package]
+        # Keep track of completed actions, so none is executed twice
+        self._done = []
+        # Kepp track of the parent node, so that we know whether we deal with
+        # a directive or sub-directive 
+        self._parent_node_info = None
+
+    # See zope.configuration.config.ConfigurationContext
+    # The package is used to resolve relative paths.
+    package = property(lambda self: self._package_stack[-1])
+
+    # All registered directives. The availability of directives depends on
+    # whether we a re looking for a directive or a sub-directive.
+    directives = property(lambda self: directives[self._parent_node_info])
+
+    def getBaseURL(self):
+        """Return the URL for the API Documentation Tool.
+
+        Example::
+
+          >>> from tests import getClassDetailsView
+          >>> view = getClassDetailsView()
+
+          Note that the following output is a bit different than usual, since
+          we have not setup all path elements.
+
+          >>> view.getBaseURL()
+          'http://127.0.0.1'
+        """
+        m = zapi.getUtility(IDocumentationModule, "Class")
+        return zapi.getView(zapi.getParent(m), 'absolute_url', self.request)()
+
+    def getHTMLContents(self):
+        """Return an HTML markup version of the ZCML file with links to other
+        documentation modules."""
+        if not directives:
+            self._makeDocStructure()
+        dom = xml.dom.minidom.parse(self.context.path)
+        self.text = file(self.context.path, 'r').read()
+        self.text = xml.sax.saxutils.escape(self.text)
+        self.text = self.text.replace(' ', '&nbsp;')
+        self.text = self.text.replace('\n', '<br />\n')
+
+        # Link interface and other object references
+        for node in dom.childNodes:
+            self._linkObjectReferences(node)
+
+        # Link ZCML directives
+        for node in dom.childNodes:
+            self._linkDirectives(node)
+
+        # Color directive attributes
+        for node in dom.childNodes:
+            self._colorAttributeNames(node)
+
+        # Color comments
+        self._colorComments()
+        
+        return self.text
+
+    def _colorComments(self):
+        self.text = self.text.replace('&lt;!--',
+                                      '<span style="color: red">&lt;!--')
+        self.text = self.text.replace('--&gt;',
+                                      '--&gt;</span>')
+
+    def _colorAttributeNames(self, node):
+        if node.nodeType in (node.TEXT_NODE, node.COMMENT_NODE):
+            return
+
+        for attrName in node.attributes.keys():
+            disc = ('color attribute name', node.tagName, attrName)
+            if disc in self._done:
+                continue
+
+            expr = re.compile(_attrname_re %(node.tagName, attrName),
+                              re.DOTALL)
+            self.text = re.sub(
+                expr,
+                r'\1<span class="attribute">\2</span>\3',
+            self.text)
+            
+            self._done.append(disc)
+
+        for child in node.childNodes:
+            self._colorAttributeNames(child)
+
+
+    def _linkDirectives(self, node):
+        if node.nodeType in (node.TEXT_NODE, node.COMMENT_NODE):
+            return
+
+        if (node.tagName,) in self._done:
+            return
+
+        if node.namespaceURI in self.directives.keys() and \
+               node.localName in self.directives[node.namespaceURI].keys():
+            namespace = quoteNS(node.namespaceURI)
+        else:
+            namespace = 'ALL'
+
+        self.text = self.text.replace(
+            '&lt;'+node.tagName,
+            '&lt;<a class="tagname" href="%s/ZCML/%s/%s/index.html">%s</a>' %(
+            self.getBaseURL(), namespace, node.localName, node.tagName))
+
+        self.text = self.text.replace(
+            '&lt;/'+node.tagName+'&gt;',
+            '&lt;/<a class="tagname" '
+            'href="%s/ZCML/%s/%s/index.html">%s</a>&gt;' %(
+            self.getBaseURL(), namespace, node.localName, node.tagName))
+
+        self._done.append((node.tagName,))
+        
+        for child in node.childNodes:
+            self._linkDirectives(child)
+
+
+    def _linkObjectReferences(self, node):
+        if node.nodeType in (node.TEXT_NODE, node.COMMENT_NODE):
+            return
+
+        if node.localName == 'configure' and node.hasAttribute('package'):
+            self._package_stack.push(
+                self.resolve(node.getAttribute('package')))
+
+        for child in node.childNodes:
+            self._linkObjectReferences(child)
+
+        namespace = self.directives.get(node.namespaceURI, self.directives[''])
+        directive = namespace.get(node.localName)
+        if directive is None:
+            # Try global namespace
+            directive = self.directives[''].get(node.localName)
+
+        for name, field in getFieldsInOrder(directive[0]):
+            if node.hasAttribute(name.strip('_')):
+                self._evalField(field, node)
+
+        if node.localName == 'configure' and node.hasAttribute('package'):
+            self._package_stack.pop()
+
+
+    def _evalField(self, field, node, attrName=None, dottedName=None):
+        bound = field.bind(self)
+        if attrName is None:
+            attrName = field.getName().strip('_')
+        if dottedName is None:
+            dottedName = node.getAttribute(attrName)
+
+        if isinstance(field, Tokens) and \
+               isinstance(field.value_type, GlobalObject):
+                       
+            tokens = [token.strip()
+                      for token in node.getAttribute(attrName).split(' \n\t')
+                      if token.strip() != '']
+
+            for token in tokens:
+                self._evalField(field.value_type, node, attrName, dottedName)
+
+        if isinstance(field, GlobalObject):
+            obj = bound.fromUnicode(dottedName)
+            disc = (node.tagName, attrName, dottedName)
+
+            if disc in self._done:
+                return
+
+            if isinstance(obj, InterfaceClass):
+                expr = re.compile(_obj_attr_re %(
+                    node.tagName, attrName, dottedName.replace('.', r'\.')),
+                                  re.DOTALL)                
+                path = obj.__module__ + '.' + obj.__name__
+                self.text = re.sub(
+                    expr,
+                    r'\1<a class="objectref" '
+                    r'href="%s/Interface/%s/apiindex.html">\2</a>\3' %(
+                    self.getBaseURL(), path),
+                    self.text)
+
+            elif isinstance(obj, (TypeType, ClassType, FunctionType)):
+                expr = re.compile(_obj_attr_re %(
+                    node.tagName, attrName, dottedName.replace('.', r'\.')),
+                                  re.DOTALL)
+                path = (obj.__module__ + '.' + obj.__name__).replace('.', '/')
+                self.text = re.sub(
+                    expr,
+                    r'\1<a class="objectref" '
+                    r'href="%s/Class/%s/index.html">\2</a>\3' %(
+                    self.getBaseURL(), path),
+                    self.text)
+
+            elif isinstance(obj, ModuleType):
+                expr = re.compile(_obj_attr_re %(
+                    node.tagName, attrName, dottedName.replace('.', r'\.')),
+                                  re.DOTALL)
+                path = obj.__name__.replace('.', '/')
+                self.text = re.sub(
+                    expr,
+                    r'\1<a class="objectref" '
+                    r'href="%s/Class/%s/index.html">\2</a>\3' %(
+                    self.getBaseURL(), path),
+                    self.text)
+
+            self._done.append(disc)
+                        
+    def _makeDocStructure(self):
+        # Some trivial caching
+        global directives
+        context = xmlconfig.file(
+            zope.app.appsetup.appsetup.getConfigSource(),
+            execute=False)
+        namespaces, subdirs = docutils.makeDocStructures(context)
+
+        for ns_name, dirs in namespaces.items():
+            for dir_name, dir in dirs.items():
+                parent = directives.setdefault(None, {})
+                namespace = parent.setdefault(ns_name, {})
+                namespace[dir_name] = dir
+
+        for parent_info, dirs in subdirs.items():
+            for dir in dirs:
+                parent = directives.setdefault(parent_info, {})
+                namespace = parent.setdefault(dir[0], {})
+                namespace[dir[1]] = dir[2:]
+
+
 class FunctionDetails(object):
     """Represents the details of the function."""
 
@@ -365,15 +614,17 @@
 
           >>> entries = view.getEntries(False)
           >>> entries.sort()
-          >>> pprint(entries[:2])
+          >>> pprint(entries[1:3])
           [[('isclass', False),
             ('isfunction', False),
             ('ismodule', True),
+            ('iszcmlfile', False),
             ('name', 'browser'),
             ('url', 'http://127.0.0.1/zope/app/apidoc/classmodule/browser')],
            [('isclass', False),
             ('isfunction', True),
             ('ismodule', False),
+            ('iszcmlfile', False),
             ('name', 'cleanUp'),
             ('url', 'http://127.0.0.1/zope/app/apidoc/classmodule/cleanUp')]]
         """
@@ -381,7 +632,8 @@
                     'url': zapi.getView(obj, 'absolute_url', self.request)(),
                     'ismodule': type(removeAllProxies(obj)) is Module,
                     'isclass': type(removeAllProxies(obj)) is Class,
-                    'isfunction': type(removeAllProxies(obj)) is Function,}
+                    'isfunction': type(removeAllProxies(obj)) is Function,
+                    'iszcmlfile': type(removeAllProxies(obj)) is ZCMLFile,}
                    for name, obj in self.context.items()]
         entries.sort(lambda x, y: cmp(x['name'], y['name']))
         if columns:

Modified: Zope3/trunk/src/zope/app/apidoc/classmodule/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classmodule/configure.zcml	2004-07-18 21:56:36 UTC (rev 26614)
+++ Zope3/trunk/src/zope/app/apidoc/classmodule/configure.zcml	2004-07-18 21:59:44 UTC (rev 26615)
@@ -14,6 +14,10 @@
     <allow interface=".IFunctionDocumentation" />
   </class>
 
+  <class class=".ZCMLFile">
+    <allow interface=".IZCMLFileDocumentation" />
+  </class>
+
   <class class=".ClassModule">
     <allow interface="zope.app.apidoc.interfaces.IDocumentationModule" />
     <allow interface=".IModuleDocumentation" />
@@ -53,4 +57,11 @@
     name="index.html"
     template="function_index.pt" />
 
+  <browser:page
+    for=".IZCMLFileDocumentation"
+    permission="zope.View"
+    class=".browser.ZCMLFileDetails"
+    name="index.html"
+    template="zcmlfile_index.pt" />
+
 </configure>

Modified: Zope3/trunk/src/zope/app/apidoc/classmodule/ftests.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classmodule/ftests.py	2004-07-18 21:56:36 UTC (rev 26614)
+++ Zope3/trunk/src/zope/app/apidoc/classmodule/ftests.py	2004-07-18 21:59:44 UTC (rev 26615)
@@ -70,6 +70,16 @@
         self.checkForBrokenLinks(
             body, '/++apidoc++/Class/zope/app/apidoc/handleNamesapce',
             basic='mgr:mgrpw')
+
+    def testZCMLFileDetailsView(self):
+        response = self.publish(
+            '/++apidoc++/Class/zope/app/configure.zcml',
+            basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        body = response.getBody()
+        self.checkForBrokenLinks(
+            body, '/++apidoc++/Class/zope/app/configure.zcml',
+            basic='mgr:mgrpw')
         
 
 def test_suite():

Modified: Zope3/trunk/src/zope/app/apidoc/classmodule/module_index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classmodule/module_index.pt	2004-07-18 21:56:36 UTC (rev 26614)
+++ Zope3/trunk/src/zope/app/apidoc/classmodule/module_index.pt	2004-07-18 21:59:44 UTC (rev 26615)
@@ -36,6 +36,10 @@
          tal:condition="entry/isfunction"
          tal:attributes="href string:./${entry/name}/index.html"
          tal:content="structure string:<i>${entry/name}</i>" />
+      <a href=""
+         tal:condition="entry/iszcmlfile"
+         tal:attributes="href string:./${entry/name}/index.html"
+         tal:content="structure string:<i>${entry/name}</i>" />
     </li>
   </ul></td>
 

Added: Zope3/trunk/src/zope/app/apidoc/classmodule/zcmlfile_index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classmodule/zcmlfile_index.pt	2004-07-18 21:56:36 UTC (rev 26614)
+++ Zope3/trunk/src/zope/app/apidoc/classmodule/zcmlfile_index.pt	2004-07-18 21:59:44 UTC (rev 26615)
@@ -0,0 +1,15 @@
+<html metal:use-macro="views/apidoc_macros/details">
+<body metal:fill-slot="contents">
+
+  <h1 class="details-header"
+      tal:content="context/path">
+    zope.app.Klass
+  </h1>
+
+  <code class="zcml" tal:content="structure view/getHTMLContents">
+    <configure> </configure>
+  </code>
+
+
+</body>
+</html>



More information about the Zope3-Checkins mailing list