[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/ Using the static apidoc generator I have fixed many problems with

Stephan Richter srichter at cosmos.phy.tufts.edu
Sat Oct 29 19:25:40 EDT 2005


Log message for revision 39722:
  Using the static apidoc generator I have fixed many problems with 
  unreachable URLs. Changes include:
  
  - Always use absolute URLs to reference to other modules in the interface 
    details.
  
  - Be much more careful in deciding which objects are referencable, and 
    this have links. Also, now the calculation of the object to be 
    referenced is much more careful.
  
  - The details page for interfaces is now called "index.html" like for all 
    other objects. That allowed some code simplification.
  
  - Allow the exclusion of certain modules from the safe_import.
  
  - The ZCML file view has now many more links thanks to a bug fix.
  
  - Docstrings are now dedented as required for good ReST formatting. Doing 
    this was suggested by the docutils mailing list.
  
  - The generator now tells you how many URL handling errors occurred during 
    the generation phase.
  
  

Changed:
  U   Zope3/trunk/src/zope/app/apidoc/classregistry.py
  U   Zope3/trunk/src/zope/app/apidoc/classregistry.txt
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/README.txt
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_.py
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_index.pt
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/function.py
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/module_index.pt
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/browser/zcml.py
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/class_.py
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/codemodule.py
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/module.py
  U   Zope3/trunk/src/zope/app/apidoc/codemodule/zcml.py
  U   Zope3/trunk/src/zope/app/apidoc/component.py
  U   Zope3/trunk/src/zope/app/apidoc/component.txt
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.txt
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/component_macros.pt
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/ftests.py
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/index.pt
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/menu.py
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/presentation_macros.pt
  U   Zope3/trunk/src/zope/app/apidoc/interface.py
  U   Zope3/trunk/src/zope/app/apidoc/interface.txt
  U   Zope3/trunk/src/zope/app/apidoc/static.py
  U   Zope3/trunk/src/zope/app/apidoc/tests.py
  U   Zope3/trunk/src/zope/app/apidoc/typemodule/browser.py
  U   Zope3/trunk/src/zope/app/apidoc/utilities.py
  U   Zope3/trunk/src/zope/app/apidoc/utilities.txt
  U   Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.py
  U   Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.txt
  U   Zope3/trunk/src/zope/app/apidoc/utilitymodule/index.pt
  U   Zope3/trunk/src/zope/app/apidoc/utilitymodule/utilitymodule.py
  U   Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.py
  U   Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.txt
  U   Zope3/trunk/src/zope/app/apidoc/zcmlmodule/index.pt
  U   Zope3/trunk/src/zope/app/interpreter/python.py
  U   Zope3/trunk/src/zope/app/onlinehelp/browser/tree.py

-=-
Modified: Zope3/trunk/src/zope/app/apidoc/classregistry.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classregistry.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/classregistry.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -19,6 +19,9 @@
 
 __import_unknown_modules__ = False
 
+# List of modules that should never be imported.
+IGNORE_MODULES = []
+
 import sys
 
 from zope.app import zapi
@@ -55,9 +58,16 @@
 def safe_import(path, default=None):
     """Import a given path as efficiently as possible and without failure."""
     module = sys.modules.get(path, default)
+    for exclude_name in IGNORE_MODULES:
+        if path.startswith(exclude_name):
+            return default
     if module is default and __import_unknown_modules__:
         try:
             module = __import__(path, {}, {}, ('*',))
         except ImportError:
             return default
+        # Some software, we cannot control, might raise all sorts of errors;
+        # thus catch all exceptions and return the default.
+        except Exception, error:
+            return default
     return module

Modified: Zope3/trunk/src/zope/app/apidoc/classregistry.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/classregistry.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/classregistry.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -161,7 +161,42 @@
 
   >>> del sys.modules['zope.app.apidoc.testmodule']
 
+Importing some code we cannot control, such as twisted, might raise errors
+when imported without having a certain environment. In those cases, the safe
+import should prevent the error from penetrating:
+
+  >>> import os, tempfile
+  >>> dir = tempfile.mkdtemp()
+  >>> open(os.path.join(dir, 'alwaysfail.py'), 'w').write('raise ValueError\n')
+  >>> sys.path.insert(0, dir)
+
+  >>> safe_import('alwaysfail') is None
+  True
+
+  >>> del sys.path[0]
+
+  >>> import shutil
+  >>> shutil.rmtree(dir)
+
+Another method to explicitely turning off the import of certain modules is to
+declare that they should be ignored. For example, if we tell the class
+registry to ignore ``zope.app``,
+
+  >>> classregistry.IGNORE_MODULES.append('zope.app')
+
+then we cannot import it anymore, even though we know it is available:
+
+  >>> safe_import('zope.app') is None
+  True
+
+Note that all sub-packages are also unavailable:
+
+  >>> safe_import('zope.app.apidoc') is None
+  True
+
 We also need to play nice concerning variables and have to reset the module
 globals:
 
-  >>> classregistry.__import_unknown_modules__ = False
\ No newline at end of file
+  >>> classregistry.IGNORE_MODULES.pop()
+  'zope.app'
+  >>> classregistry.__import_unknown_modules__ = False

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/README.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/README.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -62,6 +62,7 @@
   >>> 'meta.zcml' in keys
   True
 
+  >>> import zope.app.apidoc.browser
   >>> print module['browser'].getPath()
   zope.app.apidoc.browser
 
@@ -74,13 +75,14 @@
 classes hierarchy (since they do not provide or implement any API), we can
 still get to them:
 
+  >>> import zope.app.apidoc.tests
   >>> print module['tests'].getPath()
   zope.app.apidoc.tests
 
   >>> names = module['tests'].keys()
   >>> names.sort()
   >>> names
-  ['Root', 'rootLocation', 'setUp', 'test_suite']
+  ['Root', 'rootLocation', 'setUp', 'tearDown', 'test_suite']
 
 
 Classes

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/README.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -311,18 +311,6 @@
 The result is a bit strange, since the ZCML Documentation module is the
 containment root.
 
-`ifaceURL(value, field, rootURL)`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This method converts the string value of the field to an interface and then
-crafts a documentation URL for it:
-
-  >>> from zope.configuration.fields import GlobalInterface
-  >>> field = GlobalInterface()
-
-  >>> details.ifaceURL('.interfaces.IZCMLFile', field, '')
-  '/../Interface/zope.app.apidoc.codemodule.interfaces.IZCMLFile/apiindex.html'
-
 `objectURL(value, field, rootURL)`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -333,7 +321,7 @@
   >>> field = GlobalObject()
 
   >>> details.objectURL('.interfaces.IZCMLFile', field, '')
-  '/../Interface/zope.app.apidoc.codemodule.interfaces.IZCMLFile/apiindex.html'
+  '/../Interface/zope.app.apidoc.codemodule.interfaces.IZCMLFile/index.html'
 
   >>> details.objectURL('.zcml.ZCMLFile', field, '')
   '/zope/app/apidoc/codemodule/zcml/ZCMLFile/index.html'
@@ -534,7 +522,7 @@
       'signature': '()',
       'write_perm': None},
      ...
-     {'doc': u'<dl class="docutils">\n<dt>Return a sequence-like...',
+     {'doc': u'<p>Return a sequence-like...',
       'interface': 'zope.interface.common.mapping.IEnumerableMapping',
       'name': 'keys',
       'read_perm': None,

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -24,6 +24,7 @@
 from zope.app.apidoc.interfaces import IDocumentationModule
 from zope.app.apidoc.utilities import getPythonPath, getPermissionIds
 from zope.app.apidoc.utilities import renderText, getFunctionSignature
+from zope.app.apidoc.utilities import isReferencable
 from zope.app.traversing.interfaces import TraversalError
 
 
@@ -31,7 +32,7 @@
     if type is types.NoneType:
         return None
     path = getPythonPath(type)
-    return path.replace('.', '/')
+    return isReferencable(path) and path.replace('.', '/') or None
 
 class ClassDetails(object):
     """Represents the details of the class."""
@@ -65,7 +66,7 @@
                 # If one of the classes is implemented in C, we will not
                 # be able to find it.
                 url = None
-            info.append({'path': path, 'url': url})
+            info.append({'path': path or None, 'url': url})
         return info
 
 

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_index.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/class_index.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -48,7 +48,7 @@
       <li tal:repeat="iface ifaces">
         <a href=""
            tal:attributes="href
-               string:${view/getBaseURL}/Interface/$iface/apiindex.html"
+               string:${view/getBaseURL}/Interface/$iface/index.html"
            tal:content="iface" />
       </li>
     </ul>
@@ -75,7 +75,7 @@
       <tal:omit-tag condition="attr/type_link">
         (<span i18n:translate="">type:</span>
         <a href=""
-           tal:attributes="href 
+           tal:attributes="href
                string:${view/getBaseURL}/Code/${attr/type_link}/index.html">
           <code tal:content="attr/type" /></a>)
       </tal:omit-tag>
@@ -87,7 +87,7 @@
         <i i18n:translate="">Interface:</i>
         <a href=""
            tal:attributes="href
-          string:${view/getBaseURL}/Interface/${attr/interface}/apiindex.html"
+               string:${view/getBaseURL}/Interface/${attr/interface}/index.html"
            tal:content="attr/interface">Iface</a><br />
       </span>
       <span class="small"
@@ -128,7 +128,7 @@
         <i i18n:translate="">Interface:</i>
         <a href=""
            tal:attributes="href
-          string:${view/getBaseURL}/Interface/${method/interface}/apiindex.html"
+             string:${view/getBaseURL}/Interface/${method/interface}/index.html"
            tal:content="method/interface">Iface</a><br/>
       </span>
 

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/function.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/function.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/function.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -18,8 +18,10 @@
 __docformat__ = 'restructuredtext'
 from zope.app import zapi
 from zope.app.apidoc.interfaces import IDocumentationModule
-from zope.app.apidoc.utilities import getPythonPath, renderText
+from zope.app.apidoc.utilities import renderText
 
+from class_ import getTypeLink
+
 class FunctionDetails(object):
     """Represents the details of the function."""
 
@@ -34,7 +36,7 @@
         return [{'name': name,
                  'value': `attr`,
                  'type': type(attr).__name__,
-                 'type_link': getPythonPath(type(attr)).replace('.', '/')}
+                 'type_link': getTypeLink(type(attr))}
 
                 for name, attr in self.context.getAttributes()]
 

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -43,7 +43,7 @@
       <li tal:repeat="iface ifaces">
         <a href=""
            tal:attributes="href
-             string:${view/getBaseURL}/Interface/$iface/apiindex.html"
+             string:${view/getBaseURL}/Interface/$iface/index.html"
            tal:content="iface" />
       </li>
     </ul>
@@ -63,7 +63,7 @@
       <li tal:repeat="iface ifaces">
         <a href=""
            tal:attributes="href
-             string:${view/getBaseURL}/Interface/$iface/apiindex.html"
+             string:${view/getBaseURL}/Interface/$iface/index.html"
            tal:content="iface" />
       </li>
     </ul>
@@ -129,7 +129,7 @@
         <i i18n:translate="">Interface:</i>
         <a href=""
            tal:attributes="href
-          string:${view/getBaseURL}/Interface/${attr/interface}/apiindex.html"
+               string:${view/getBaseURL}/Interface/${attr/interface}/index.html"
            tal:content="attr/interface">Iface</a><br />
       </span>
       <span class="small"
@@ -169,7 +169,7 @@
         <i i18n:translate="">Interface:</i>
         <a href=""
            tal:attributes="href
-          string:${view/getBaseURL}/Interface/${method/interface}/apiindex.html"
+             string:${view/getBaseURL}/Interface/${method/interface}/index.html"
            tal:content="method/interface">Iface</a><br/>
       </span>
 

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -34,7 +34,8 @@
     if type is types.NoneType:
         return None
     path = apidoc.utilities.getPythonPath(type)
-    return path.replace('.', '/')
+    importable = apidoc.utilities.isReferencable(path)
+    return importable and path.replace('.', '/') or None
 
 
 class annotationsNamespace(object):

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/introspector.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -32,10 +32,10 @@
 see those:
 
     >>> browser.getLink('zope.app.component.interfaces.ISite').url
-    '.../++apidoc++/Interface/zope.app.component.interfaces.ISite/apiindex.html'
+    '.../++apidoc++/Interface/zope.app.component.interfaces.ISite/index.html'
 
     >>> browser.getLink('zope.app.folder.interfaces.IRootFolder').url
-    '...apidoc++/Interface/zope.app.folder.interfaces.IRootFolder/apiindex.html'
+    '...apidoc++/Interface/zope.app.folder.interfaces.IRootFolder/index.html'
 
 The next two section, the implemented interfaces and the base classes, are not
 instance specific pieces of information, but they are still nice to see at
@@ -43,16 +43,16 @@
 interfaces:
 
     >>> browser.getLink('zope.app.folder.interfaces.IFolder').url
-    '.../++apidoc++/Interface/zope.app.folder.interfaces.IFolder/apiindex.html'
+    '.../++apidoc++/Interface/zope.app.folder.interfaces.IFolder/index.html'
 
     >>> browser.getLink('persistent.interfaces.IPersistent').url
-    '.../++apidoc++/Interface/persistent.interfaces.IPersistent/apiindex.html'
+    '.../++apidoc++/Interface/persistent.interfaces.IPersistent/index.html'
 
     >>> browser.getLink('zope.app.component.interfaces.IPossibleSite').url
-    '.../Interface/zope.app.component.interfaces.IPossibleSite/apiindex.html'
+    '.../Interface/zope.app.component.interfaces.IPossibleSite/index.html'
 
     >>> browser.getLink('zope.app.container.interfaces.IContained').url
-    '...doc++/Interface/zope.app.container.interfaces.IContained/apiindex.html'
+    '...doc++/Interface/zope.app.container.interfaces.IContained/index.html'
 
 The base classes of the ``Folder`` are as follows:
 
@@ -110,10 +110,7 @@
       <li>
         <b><code>get(name, default=None)</code>
         </b><br>
-        <div class="inline documentation"><dl class="docutils">
-          <dt>Return ...
-        </dl>
-        </div>
+        <div class="inline documentation"><p>Return ... </div>
         <span class="small">
           <i>Interface:</i>
           <a href="...">zope.interface.common.mapping.IReadMapping</a><br />

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/module_index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/module_index.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/module_index.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -32,7 +32,7 @@
          tal:content="entry/name" />
       <a href=""
          tal:condition="entry/isinterface"
-         tal:attributes="href string:./${entry/name}/apiindex.html"
+         tal:attributes="href string:./${entry/name}/index.html"
          tal:content="structure string:<b><i>${entry/name}</i></b>" />
       <a href=""
          tal:condition="entry/isclass"

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/browser/zcml.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/browser/zcml.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/browser/zcml.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -26,7 +26,7 @@
 from zope.app import zapi
 from zope.app.tree.interfaces import IUniqueId
 from zope.app.apidoc.interfaces import IDocumentationModule
-from zope.app.apidoc.utilities import getPythonPath
+from zope.app.apidoc.utilities import getPythonPath, isReferencable
 
 from zope.app.apidoc.zcmlmodule import quoteNS
 from zope.app.apidoc.codemodule.interfaces import IRootDirective
@@ -88,13 +88,6 @@
             link += '#' + subDirective.name[1]
         return link
 
-    def ifaceURL(self, value, field, rootURL):
-        bound = field.bind(self.context.context)
-        iface = bound.fromUnicode(value)
-        if iface is None:
-            return
-        return rootURL+'/../Interface/%s/apiindex.html' %(getPythonPath(iface))
-
     def objectURL(self, value, field, rootURL):
         bound = field.bind(self.context.context)
         obj = bound.fromUnicode(value)
@@ -105,15 +98,15 @@
         except AttributeError:
             # probably an object that does not like to play nice with the CA
             isInterface = False
+
+        # The object mught be an instance; in this case get a link to the class
+        if not hasattr(obj, '__name__'):
+            obj = getattr(obj, '__class__')
+        path = getPythonPath(obj)
         if isInterface:
-            return rootURL+'/../Interface/%s/apiindex.html' %(
-                getPythonPath(obj))
-        try:
-            return rootURL + '/%s/index.html' %(
-                getPythonPath(obj).replace('.', '/'))
-        except AttributeError:
-            # probably an instance
-            pass
+            return rootURL+'/../Interface/%s/index.html' % path
+        if isReferencable(path):
+            return rootURL + '/%s/index.html' %(path.replace('.', '/'))
 
     def attributes(self):
         context = removeSecurityProxy(self.context)
@@ -126,10 +119,8 @@
         for attr in attrs:
             name = (attr['name'] in names) and attr['name'] or attr['name']+'_'
             field = context.schema.get(name)
-            if zapi.isinstance(field, GlobalInterface):
-                attr['url'] = self.ifaceURL(attr['value'], field, rootURL)
 
-            elif zapi.isinstance(field, GlobalObject):
+            if zapi.isinstance(field, (GlobalObject, GlobalInterface)):
                 attr['url'] = self.objectURL(attr['value'], field, rootURL)
 
             elif zapi.isinstance(field, Tokens):
@@ -137,15 +128,11 @@
                 values = attr['value'].strip().split()
                 if len(values) == 1:
                     attr['value'] = values[0]
-                    if zapi.isinstance(field, GlobalInterface):
-                        attr['url'] = self.ifaceURL(values[0], field, rootURL)
-                    elif zapi.isinstance(field, GlobalObject):
-                        attr['url'] = self.objectURL(values[0], field, rootURL)
+                    attr['url'] = self.objectURL(values[0], field, rootURL)
                 else:
                     for value in values:
-                        if zapi.isinstance(field, GlobalInterface):
-                            url = self.ifaceURL(value, field, rootURL)
-                        elif zapi.isinstance(field, GlobalObject):
+                        if zapi.isinstance(field,
+                                           (GlobalObject, GlobalInterface)):
                             url = self.objectURL(value, field, rootURL)
                         else:
                             break

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/class_.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/class_.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/class_.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -11,7 +11,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""Class representation for code browser 
+"""Class representation for code browser
 
 $Id: __init__.py 29143 2005-02-14 22:43:16Z srichter $
 """
@@ -27,7 +27,6 @@
 from zope.app.apidoc.classregistry import classRegistry
 from zope.app.apidoc.utilities import getInterfaceForAttribute
 from zope.app.apidoc.utilities import getPublicAttributes
-from zope.app.apidoc.utilities import getPythonPath
 from interfaces import IClassDocumentation
 
 

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/codemodule.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/codemodule.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/codemodule.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -69,7 +69,9 @@
         if self.__isSetup:
             return
         for name, mod in zapi.getUtilitiesFor(IAPIDocRootModule):
-            self._children[name] = Module(self, name, safe_import(mod))
+            module = safe_import(mod)
+            if module is not None:
+                self._children[name] = Module(self, name, module)
         self.__isSetup = True
 
     def getDocString(self):

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/module.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/module.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/module.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -11,7 +11,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""Module representation for code browser 
+"""Module representation for code browser
 
 $Id: __init__.py 29143 2005-02-14 22:43:16Z srichter $
 """
@@ -64,14 +64,14 @@
                     if file in IGNORE_FILES or file in self._children:
                         continue
                     path = os.path.join(dir, file)
-    
+
                     if (os.path.isdir(path) and
                         '__init__.py' in os.listdir(path)):
                         fullname = self._module.__name__ + '.' + file
                         module = safe_import(fullname)
                         if module is not None:
                             self._children[file] = Module(self, file, module)
-    
+
                     elif os.path.isfile(path) and file.endswith('.py') and \
                              not file.startswith('__init__'):
                         name = file[:-3]
@@ -79,11 +79,11 @@
                         module = safe_import(fullname)
                         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(path, self._module,
                                                         self, file)
-    
+
                     elif os.path.isfile(path) and file.endswith('.txt'):
                         self._children[file] = TextFile(path, file, self)
 
@@ -136,11 +136,18 @@
         else:
             path = key
         obj = safe_import(path)
+
         if obj is not None:
             return Module(self, key, obj)
 
-        return default
+        # Maybe it is a simple attribute of the module
+        if obj is None:
+            obj = getattr(self._module, key, default)
+            if obj is not default:
+                obj = LocationProxy(obj, self, key)
 
+        return obj
+
     def items(self):
         """See zope.app.container.interfaces.IReadContainer."""
         return self._children.items()

Modified: Zope3/trunk/src/zope/app/apidoc/codemodule/zcml.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/codemodule/zcml.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/codemodule/zcml.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -109,6 +109,9 @@
         context = zope.app.appsetup.appsetup.getConfigContext()
         context.package = self.package
 
+        # Shut up i18n domain complaints
+        context.i18n_domain = 'zope'
+
         # Since we want to use a custom configuration handler, we need to
         # instantiate the parser object ourselves
         parser = make_parser()

Modified: Zope3/trunk/src/zope/app/apidoc/component.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/component.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/component.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -22,13 +22,14 @@
 from zope.component.site import AdapterRegistration, SubscriptionRegistration
 from zope.component.site import UtilityRegistration
 from zope.interface import Interface
+from zope.interface.interface import InterfaceClass
 from zope.publisher.interfaces import IRequest
 
 from zope.app import zapi
 from zope.app.i18n import ZopeMessageFactory as _
 from zope.app.apidoc.classregistry import classRegistry
 from zope.app.apidoc.utilities import relativizePath, truncateSysPath
-from zope.app.apidoc.utilities import getPythonPath, renderText
+from zope.app.apidoc.utilities import getPythonPath, isReferencable, renderText
 from zope.app.apidoc.utilitymodule import utilitymodule
 
 SPECIFIC_INTERFACE_LEVEL = 1
@@ -169,10 +170,10 @@
     factory = getRealFactory(reg.value)
     path = getPythonPath(factory)
 
-    if isinstance(factory, types.MethodType):
-       url = None
-    else:
+    url = None
+    if isReferencable(path):
         url = path.replace('.', '/')
+
     if isinstance(reg.doc, (str, unicode)):
         doc = reg.doc
         zcml = None
@@ -195,7 +196,6 @@
 def getFactoryInfoDictionary(reg):
     """Return a PT-friendly info dictionary for a factory."""
     factory = reg.component
-
     callable = factory
 
     # Usually only zope.component.factory.Factory instances have this attribute
@@ -210,22 +210,29 @@
             'title': getattr(factory, 'title', u''),
             'description': renderText(getattr(factory, 'description', u''),
                                       module=callable.__module__),
-            'url': path.replace('.', '/')}
+            'url': isReferencable(path) and path.replace('.', '/') or None}
 
 
 def getUtilityInfoDictionary(reg):
     """Return a PT-friendly info dictionary for a factory."""
-    if type(reg.component) in (types.ClassType, types.TypeType):
-        klass = reg.component
-    else:
-        klass = reg.component.__class__
+    component = reg.component
+    # Check whether we have an instance of some custom type or not
+    # Unfortunately, a lot of utilities have a `__name__` attribute, so we
+    # cannot simply check for its absence
+    # TODO: Once we support passive display of instances, this insanity can go
+    #       away.
+    if not isinstance(component, (types.MethodType, types.FunctionType,
+                                  types.ClassType, types.TypeType,
+                                  InterfaceClass)):
+        component = getattr(component, '__class__', component)
 
+    path = getPythonPath(component)
+
     # provided interface id
     iface_id = '%s.%s' % (reg.provided.__module__, reg.provided.getName())
 
-    path = getPythonPath(klass)
     return {'name': reg.name or _('<i>no name</i>'),
             'url_name': utilitymodule.encodeName(reg.name or '__noname__'),
             'iface_id': iface_id,
             'path': path,
-            'url': path.replace('.', '/')}
+            'url': isReferencable(path) and path.replace('.', '/') or None}

Modified: Zope3/trunk/src/zope/app/apidoc/component.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/component.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/component.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -138,12 +138,13 @@
   >>> classes = component.getClasses(IFooBar)
   >>> classes.sort()
   >>> classes
-  [('MyFooBar', <class 'MyFooBar'>)]
+  [('MyFooBar', <class 'zope.app.apidoc.doctest.MyFooBar'>)]
 
   >>> classes = component.getClasses(IFoo)
   >>> classes.sort()
   >>> classes
-  [('MyFoo', <class 'MyFoo'>), ('MyFooBar', <class 'MyFooBar'>)]
+  [('MyFoo', <class 'zope.app.apidoc.doctest.MyFoo'>),
+   ('MyFooBar', <class 'zope.app.apidoc.doctest.MyFooBar'>)]
 
 
 `getFactories(ifaces)`
@@ -167,15 +168,15 @@
   >>> regs.sort()
   >>> regs
   [UtilityRegistration('IFactory', 'MyFooBar',
-                       <Factory for <class 'MyFooBar'>>, '')]
+      <Factory for <class 'zope.app.apidoc.doctest.MyFooBar'>>, '')]
 
   >>> regs = list(component.getFactories(IFoo))
   >>> regs.sort()
   >>> regs
   [UtilityRegistration('IFactory', 'MyFoo',
-                       <Factory for <class 'MyFoo'>>, ''),
+      <Factory for <class 'zope.app.apidoc.doctest.MyFoo'>>, ''),
    UtilityRegistration('IFactory', 'MyFooBar',
-                       <Factory for <class 'MyFooBar'>>, '')]
+      <Factory for <class 'zope.app.apidoc.doctest.MyFooBar'>>, '')]
 
 
 `getUtilities(iface)`
@@ -195,13 +196,16 @@
   >>> regs = list(component.getUtilities(IFooBar))
   >>> regs.sort()
   >>> regs #doctest:+ELLIPSIS
-  [UtilityRegistration('IFooBar', '', <MyFooBar object at ...>, '')]
+  [UtilityRegistration('IFooBar', '',
+                       <zope.app.apidoc.doctest.MyFooBar object at ...>, '')]
 
   >>> regs = list(component.getUtilities(IFoo))
   >>> regs.sort()
   >>> regs #doctest:+ELLIPSIS
-  [UtilityRegistration('IFoo', '', <MyFoo object at ...>, ''),
-   UtilityRegistration('IFooBar', '', <MyFooBar object at ...>, '')]
+  [UtilityRegistration('IFoo', '',
+                       <zope.app.apidoc.doctest.MyFoo object at ...>, ''),
+   UtilityRegistration('IFooBar', '',
+                       <zope.app.apidoc.doctest.MyFooBar object at ...>, '')]
 
 
 `getInterfaceInfoDictionary(iface)`
@@ -213,7 +217,7 @@
 utilities.
 
   >>> pprint(component.getInterfaceInfoDictionary(IFoo))
-  {'module': '__builtin__', 'name': 'IFoo'}
+  {'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'}
 
 The functions using this function use it with little care and can also
 sometimes pass in `None`. In these cases we want to return `None`:
@@ -241,16 +245,36 @@
 
   >>> pprint(component.getAdapterInfoDictionary(reg))
   {'doc': 'doc info',
-   'factory': '__builtin__.MyResult',
-   'factory_url': '__builtin__/MyResult',
+   'factory': 'zope.app.apidoc.doctest.MyResult',
+   'factory_url': 'zope/app/apidoc/doctest/MyResult',
    'name': 'FooToResult',
-   'provided': {'module': '__builtin__', 'name': 'IResult'},
-   'required': [{'module': '__builtin__', 'name': 'IFoo'},
-                {'module': '__builtin__', 'name': 'IBar'}],
+   'provided': {'module': 'zope.app.apidoc.doctest', 'name': 'IResult'},
+   'required': [{'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'},
+                {'module': 'zope.app.apidoc.doctest', 'name': 'IBar'}],
    'zcml': None}
 
+If the factory's path cannot be referenced, for example if a type has been
+created using the ``type()`` builtin function, then the URL of the factory
+will be ``None``:
+
+  >>> MyResultType = type('MyResult2', (object,), {})
+  >>> from zope.interface import classImplements
+  >>> classImplements(MyResultType, IResult)
+
+  >>> reg = AdapterRegistration((IFoo, IBar), IResult, 'FooToResult',
+  ...                            MyResultType, 'doc info')
+  >>> pprint(component.getAdapterInfoDictionary(reg))
+  {'doc': 'doc info',
+   'factory': 'zope.app.apidoc.doctest.MyResult2',
+   'factory_url': None,
+   'name': 'FooToResult',
+   'provided': {'module': 'zope.app.apidoc.doctest', 'name': 'IResult'},
+   'required': [{'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'},
+                {'module': 'zope.app.apidoc.doctest', 'name': 'IBar'}],
+   'zcml': None}
+
 This function can also handle subscription registrations, which are pretty
-much like adapter registrations, except that they do not have name. So let's
+much like adapter registrations, except that they do not have a name. So let's
 see how the function handles subscriptions:
 
   >>> from zope.component.site import SubscriptionRegistration
@@ -258,12 +282,12 @@
 
   >>> pprint(component.getAdapterInfoDictionary(reg))
   {'doc': 'doc info',
-   'factory': '__builtin__.MyResult',
-   'factory_url': '__builtin__/MyResult',
+   'factory': 'zope.app.apidoc.doctest.MyResult',
+   'factory_url': 'zope/app/apidoc/doctest/MyResult',
    'name': u'<subscription>',
    'provided': None,
-   'required': [{'module': '__builtin__', 'name': 'IFoo'},
-                {'module': '__builtin__', 'name': 'IBar'}],
+   'required': [{'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'},
+                {'module': 'zope.app.apidoc.doctest', 'name': 'IBar'}],
    'zcml': None}
 
 
@@ -281,9 +305,31 @@
   {'description': u'<p>My Foo Bar</p>\n',
    'name': 'MyFooBar',
    'title': 'MyFooBar',
-   'url': '__builtin__/MyFooBar'}
+   'url': 'zope/app/apidoc/doctest/MyFooBar'}
 
+If the factory's path cannot be referenced, for example if a type has been
+created using the ``type()`` builtin function, then the URL of the factory
+will be ``None``:
 
+  >>> class IMine(Interface):
+  ...     pass
+
+  >>> class FactoryBase(object):
+  ...     def getInterfaces(self): return [IMine]
+
+  >>> MyFactoryType = type('MyFactory', (FactoryBase,), {})
+  >>> from zope.interface import classImplements
+  >>> classImplements(MyFactoryType, IFactory)
+  >>> ztapi.provideUtility(IFactory, MyFactoryType(), 'MyFactory')
+
+  >>> pprint(component.getFactoryInfoDictionary(
+  ...     component.getFactories(IMine).next()))
+  {'description': u'',
+   'name': 'MyFactory',
+   'title': u'',
+   'url': None}
+
+
 `getUtilityInfoDictionary(name, factory)`
 -----------------------------------------
 
@@ -295,8 +341,8 @@
 
   >>> pprint(component.getUtilityInfoDictionary(
   ...     component.getUtilities(IFooBar).next()))
-  {'iface_id': '__builtin__.IFooBar',
+  {'iface_id': 'zope.app.apidoc.doctest.IFooBar',
    'name': u'<i>no name</i>',
-   'path': '__builtin__.MyFooBar',
-   'url': '__builtin__/MyFooBar',
+   'path': 'zope.app.apidoc.doctest.MyFooBar',
+   'url': 'zope/app/apidoc/doctest/MyFooBar',
    'url_name': 'X19ub25hbWVfXw=='}

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -64,14 +64,14 @@
   >>> menu.request['search_str'] = 'Elem'
   >>> pprint(menu.findInterfaces())
   [{'name': 'IElement',
-    'url': './IElement/apiindex.html'}]
+    'url': './IElement/index.html'}]
 
   >>> menu.request['search_str'] = 'I'
   >>> pprint(menu.findInterfaces())
   [{'name': 'IAttribute',
-    'url': './IAttribute/apiindex.html'},
+    'url': './IAttribute/index.html'},
    {'name': 'IElement',
-    'url': './IElement/apiindex.html'}]
+    'url': './IElement/index.html'}]
 
 Now using the full text search:
 
@@ -80,14 +80,14 @@
   >>> menu.request['search_str'] = 'object'
   >>> pprint(menu.findInterfaces())
   [{'name': 'IAttribute',
-    'url': './IAttribute/apiindex.html'},
+    'url': './IAttribute/index.html'},
    {'name': 'IElement',
-    'url': './IElement/apiindex.html'}]
+    'url': './IElement/index.html'}]
 
   >>> menu.request['search_str'] = 'Stores'
   >>> pprint(menu.findInterfaces())
   [{'name': 'IAttribute',
-    'url': './IAttribute/apiindex.html'}]
+    'url': './IAttribute/index.html'}]
 
 
 `InterfaceDetails` class
@@ -311,12 +311,10 @@
   >>> pprint(details.getProvidedAdapters())
   [{'doc': '',
     'factory': '__builtin__.Foo',
-    'factory_url': '__builtin__/Foo',
+    'factory_url': None,
     'name': '',
-    'provided': {'module': '__builtin__',
-                 'name': 'IFoo'},
-    'required': [{'module': '__builtin__',
-                  'name': 'IBar'}],
+    'provided': {'module': '__builtin__', 'name': 'IFoo'},
+    'required': [{'module': '__builtin__', 'name': 'IBar'}],
     'zcml': None}]
 
 
@@ -339,7 +337,7 @@
   [{'description': u'',
     'name': 'FooFactory',
     'title': 'Foo Factory',
-    'url': '__builtin__/Foo'}]
+    'url': None}]
 
 `getUtilities()`
 ----------------
@@ -350,5 +348,5 @@
   [{'iface_id': '__builtin__.IFoo',
     'name': 'The Foo',
     'path': '__builtin__.Foo',
-    'url': '__builtin__/Foo',
+    'url': None,
     'url_name': 'VGhlIEZvbw=='}]
\ No newline at end of file

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/component_macros.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/component_macros.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/component_macros.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -5,7 +5,7 @@
 <metal:block define-macro="zcml" i18n:domain="zope">
   <a href=""
       tal:attributes="href
-        string:../../Code/${zcml/url}/index.html?line=${zcml/line}#${zcml/line}"
+     string:$rootURL/Code/${zcml/url}/index.html?line=${zcml/line}#${zcml/line}"
       tal:content="zcml/file">
     zope/app/configure.zcml
   </a>
@@ -42,7 +42,7 @@
       <a href=""
           tal:condition="iface"
           tal:attributes="href
-              string:../${iface/module}.${iface/name}/apiindex.html">
+            string:$rootURL/Interface/${iface/module}.${iface/name}/index.html">
         <metal:block use-macro="context/@@interface_macros/ifacename"
         /></a><tal:block condition="not:repeat/iface/end">, </tal:block>
     </tal:block>
@@ -56,7 +56,7 @@
     <a href=""
         tal:condition="iface"
         tal:attributes="href
-              string:../${iface/module}.${iface/name}/apiindex.html">
+          string:$rootURL/Interface/${iface/module}.${iface/name}/index.html">
       <metal:block use-macro="context/@@interface_macros/ifacename" />
     </a>
     <span tal:condition="not:iface" i18n:translate="">
@@ -74,7 +74,7 @@
 
 <metal:block define-macro="factory">
   <a href=""
-     tal:attributes="href string:../../Code/${factory/url}/"
+     tal:attributes="href string:$rootURL/Code/${factory/url}/"
      tal:content="factory/name" />
   <tal:block replace="string:(${factory/title})" condition="factory/title" />
 </metal:block>
@@ -87,10 +87,13 @@
      tal:content="structure utility/name" />
   <br />
   <div style="padding-bottom: 3pt;"><span class="small">
-    <span i18n:translate="">Class:</span> <code style="font-size: 100%">
-    <a href=""
-       tal:attributes="href string:$rootURL/Code/${utility/url}/index.html"
-       tal:content="utility/path" />
+    <span i18n:translate="">Component:</span>
+    <code style="font-size: 100%">
+      <a href=""
+         tal:attributes="href string:$rootURL/Code/${utility/url}/index.html"
+         tal:content="utility/path"
+         tal:condition="utility/url" />
+      <span tal:condition="not: utility/url" tal:replace="utility/path" />
   </code></span></div>
 </metal:block>
 

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/configure.zcml	2005-10-29 23:25:40 UTC (rev 39722)
@@ -52,7 +52,7 @@
       for="zope.interface.interfaces.IInterface"
       permission="zope.app.apidoc.UseAPIDoc"
       class=".browser.InterfaceDetails"
-      name="apiindex.html"
+      name="index.html"
       template="index.pt"
       />
 

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/ftests.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/ftests.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/ftests.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -37,7 +37,7 @@
         response = self.publish(
             '/++apidoc++/Interface'
             '/zope.app.apidoc.ifacemodule.ifacemodule.IInterfaceModule'
-            '/apiindex.html',
+            '/index.html',
             basic='mgr:mgrpw')
         self.assertEqual(response.getStatus(), 200)
         body = response.getBody()
@@ -46,7 +46,7 @@
             body,
             '/++apidoc++/Interface'
             '/zope.app.apidoc.ifacemodule.IInterfaceModule'
-            '/apiindex.html',
+            '/index.html',
             basic='mgr:mgrpw')
 
 

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/index.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/index.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -12,7 +12,7 @@
     <tal:omit-tag tal:repeat="type view/getTypes" >
       <a href=""
           tal:attributes="href
-              string:${rootURL}/Interface/${type/path}/apiindex.html"
+              string:${rootURL}/Interface/${type/path}/index.html"
           tal:content="type/name"/><tal:block
           condition="not:repeat/type/end">, </tal:block>
     </tal:omit-tag>
@@ -35,7 +35,7 @@
       <li tal:repeat="base bases">
         <a href=""
            tal:attributes="href
-               string:${rootURL}/Interface/$base/apiindex.html"
+               string:${rootURL}/Interface/$base/index.html"
            tal:content="base" />
       </li>
     </ul>

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/menu.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/menu.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/menu.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -48,7 +48,7 @@
                 (not name_only and search_str in getAllTextOfInterface(iface))):
                 results.append(
                     {'name': name,
-                     'url': './%s/apiindex.html' %name
+                     'url': './%s/index.html' %name
                      })
         results.sort(lambda x, y: cmp(x['name'], y['name']))
         return results
@@ -63,7 +63,7 @@
             rtext = getAllTextOfInterface(iface)
             results.append(
                 {'name': name,
-                 'url': './%s/apiindex.html' %name,
+                 'url': './%s/index.html' %name,
                  'counter': counter,
                  'doc': whitepattern.sub(' ',getAllTextOfInterface(iface))
                  })

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/presentation_macros.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/presentation_macros.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/presentation_macros.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -16,7 +16,7 @@
       <i i18n:translate="">presentation type:</i>
       <a href=""
          tal:attributes="
-             href string:../${iface/module}.${iface/name}/apiindex.html">
+       href string:$rootURL/Interface/${iface/module}.${iface/name}/index.html">
           <metal:block use-macro="context/@@interface_macros/ifacename" />
       </a>
     </div>
@@ -27,7 +27,7 @@
         <a href=""
             tal:condition="iface"
             tal:attributes="
-                href string:../${iface/module}.${iface/name}/apiindex.html">
+       href string:$rootURL/Interface/${iface/module}.${iface/name}/index.html">
           <metal:block use-macro="context/@@interface_macros/ifacename"
         /></a><tal:block condition="not:repeat/iface/end">, </tal:block>
       </tal:block>
@@ -41,7 +41,7 @@
       <i i18n:translate="">provides:</i>
       <a href=""
          tal:attributes="
-             href string:../${iface/module}.${iface/name}/apiindex.html">
+       href string:$rootURL/Interface/${iface/module}.${iface/name}/index.html">
         <metal:block use-macro="context/@@interface_macros/ifacename" />
       </a>
     </div>
@@ -51,7 +51,7 @@
       <i i18n:translate="">layer:</i>
       <a href=""
          tal:attributes="
-             href string:../${iface/module}.${iface/name}/apiindex.html">
+      href string:$rootURL/Interface/${iface/module}.${iface/name}/index.html">
         <metal:block use-macro="context/@@interface_macros/ifacename" />
       </a>
     </div>
@@ -60,7 +60,8 @@
       <i i18n:translate="">factory path:</i>
       <a href=""
          tal:condition="View/factory/referencable"
-         tal:attributes="href string:../../Code/${View/factory/url}/index.html"
+         tal:attributes="
+             href string:$rootURL/Code/${View/factory/url}/index.html"
          tal:content="View/factory/path" />
       <span
          tal:condition="not:View/factory/referencable"

Modified: Zope3/trunk/src/zope/app/apidoc/interface.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/interface.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/interface.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -131,5 +131,5 @@
 
     # Render the field description
     info['description'] = renderText(field.description or u'', format=format)
-                                     
+
     return info

Modified: Zope3/trunk/src/zope/app/apidoc/interface.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/interface.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/interface.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -19,7 +19,7 @@
   ...                    required=True,
   ...                    default=u"My Bar")
   ...
-  ...     baz = Attribute('baz', 
+  ...     baz = Attribute('baz',
   ...                     'This is the baz attribute')
   ...
   ...     def blah(one, two, three=None, *args, **kwargs):
@@ -171,7 +171,7 @@
 the interface types again:
 
   >>> interface.getInterfaceTypes(IFoo)
-  [<InterfaceClass __builtin__.IContentType>]
+  [<InterfaceClass zope.app.apidoc.doctest.IContentType>]
 
 Again note that the interface passed to this function *cannot* be proxied,
 otherwise this method will pick up the proxy's interfaces as well.
@@ -196,7 +196,7 @@
 
   >>> from zope.schema.interfaces import IField
   >>> class ISpecialField(IField):
-  ...     pass 
+  ...     pass
   >>> class ISomething(Interface):
   ...     pass
 
@@ -205,7 +205,7 @@
   ...     implements(ISomething, ISpecialField)
 
   >>> interface.getFieldInterface(MyField())
-  <InterfaceClass __builtin__.ISpecialField>
+  <InterfaceClass zope.app.apidoc.doctest.ISpecialField>
 
 
 `getAttributeInfoDictionary(attr, format='restructuredtext')`

Modified: Zope3/trunk/src/zope/app/apidoc/static.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/static.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/static.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -28,9 +28,12 @@
 import mechanize
 
 from zope.app.testing import functional
+from zope.deprecation import __show__
 
+from zope.app.apidoc import classregistry
+
 # Setup the user feedback detail level.
-VERBOSITY = 2
+VERBOSITY = 4
 
 VERBOSITY_MAP = {1: 'ERROR', 2: 'WARNING', 3: 'INFO'}
 
@@ -38,10 +41,13 @@
 
 URL = 'http://localhost:8080/'
 
-START_PAGE = '++apidoc++/Type/@@staticmenu.html'
+START_PAGE = '++apidoc++/static.html'
 
 BASE_DIR = 'apidoc'
 
+USERNAME = 'mgr'
+PASSWORD = 'mgrpw'
+
 # A mapping of HTML elements that can contain links to the attribute that
 # actually contains the link
 urltags = {
@@ -103,7 +109,7 @@
 class Link(object):
     """A link in the page."""
 
-    def __init__(self, mechLink, referenceURL=None):
+    def __init__(self, mechLink, referenceURL='None'):
         self.referenceURL = referenceURL
         self.originalURL = mechLink.url
         self.callableURL = mechLink.absolute_url
@@ -190,9 +196,11 @@
     def start(self):
         """Start the retrieval of the apidoc."""
         t0 = time.time()
+        __show__.off()
 
         self.visited = []
         self.counter = 0
+        self.errors = 0
 
         if not os.path.exists(self.rootDir):
             os.mkdir(self.rootDir)
@@ -202,9 +210,12 @@
         else:
             self.browser = OnlineBrowser()
 
-        self.browser.setUserAndPassword('mgr', 'mgrpw')
+        self.browser.setUserAndPassword(USERNAME, PASSWORD)
         self.browser.urltags = urltags
+        #self.browser.addheaders.append(('X-zope-handle-errors', False))
 
+        classregistry.IGNORE_MODULES = ['twisted']
+
         # Work through all links until there are no more to work on.
         self.sendMessage('Starting retrieval.')
         while self.linkQueue:
@@ -216,9 +227,12 @@
                 self.showStatistics(link)
                 self.processLink(link)
 
+        __show__.on()
         t1 = time.time()
 
         self.sendMessage("Run time: %.3f sec real" % (t1-t0))
+        self.sendMessage("Links: %i" %self.counter)
+        self.sendMessage("Errors: %i" %self.errors)
 
     def showStatistics(self, link):
         self.counter += 1
@@ -251,15 +265,20 @@
             self.browser.open(link.callableURL)
         except urllib2.HTTPError, error:
             # Something went wrong with retrieving the page.
+            self.errors += 1
             self.sendMessage(
                 '%s (%i): %s' % (error.msg, error.code, link.callableURL), 2)
             self.sendMessage('+-> Reference: ' + link.referenceURL, 2)
             return
         except (urllib2.URLError, ValueError):
             # We had a bad URL running the publisher browser
+            self.errors += 1
             self.sendMessage('Bad URL: ' + link.callableURL, 2)
             self.sendMessage('+-> Reference: ' + link.referenceURL, 2)
             return
+        #except Exception, error:
+        #    import pdb; pdb.set_trace()
+        #    return
 
         # Make sure the directory exists and get a file path.
         relativeURL = url.replace(URL, '')

Modified: Zope3/trunk/src/zope/app/apidoc/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/tests.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/tests.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -24,7 +24,7 @@
 
 from zope.app.traversing.interfaces import IContainmentRoot
 from zope.app.location import LocationProxy
-from zope.app.testing import placelesssetup, ztapi
+from zope.app.testing import placelesssetup, ztapi, setup
 
 from zope.app.renderer.rest import ReStructuredTextSourceFactory
 from zope.app.renderer.rest import IReStructuredTextSource
@@ -35,16 +35,21 @@
     placelesssetup.setUp()
     # Register Renderer Components
     ztapi.provideUtility(IFactory, ReStructuredTextSourceFactory,
-                         'zope.source.rest')    
-    ztapi.browserView(IReStructuredTextSource, '', 
+                         'zope.source.rest')
+    ztapi.browserView(IReStructuredTextSource, '',
                       ReStructuredTextToHTMLRenderer)
     # Cheat and register the ReST renderer as the STX one as well.
     ztapi.provideUtility(IFactory, ReStructuredTextSourceFactory,
-                         'zope.source.stx')    
-    ztapi.browserView(IReStructuredTextSource, '', 
+                         'zope.source.stx')
+    ztapi.browserView(IReStructuredTextSource, '',
                       ReStructuredTextToHTMLRenderer)
+    setup.setUpTestAsModule(test, 'zope.app.apidoc.doctest')
 
 
+def tearDown(test):
+    placelesssetup.tearDown()
+    setup.tearDownTestAsModule(test)
+
 # Generally useful classes and functions
 
 class Root:
@@ -56,7 +61,7 @@
 def rootLocation(obj, name):
     return LocationProxy(obj, Root(), name)
 
-     
+
 def test_suite():
     return unittest.TestSuite((
         doctest.DocTestSuite('zope.app.apidoc.browser.apidoc',

Modified: Zope3/trunk/src/zope/app/apidoc/typemodule/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/typemodule/browser.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/typemodule/browser.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -34,4 +34,4 @@
 
     def getMenuLink(self, node):
         """Return the HTML link of the node that is displayed in the menu."""
-        return '../Interface/%s/apiindex.html' %zapi.name(node.context)
+        return '../Interface/%s/index.html' %zapi.name(node.context)

Modified: Zope3/trunk/src/zope/app/apidoc/utilities.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/utilities.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/utilities.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -34,6 +34,8 @@
 from zope.app.i18n import ZopeMessageFactory as _
 from zope.app.container.interfaces import IReadContainer
 
+from zope.app.apidoc.classregistry import safe_import
+
 _remove_html_overhead = re.compile(
     r'(?sm)^<html.*<body.*?>\n(.*)</body>\n</html>\n')
 
@@ -87,7 +89,11 @@
 
 
 def getPythonPath(obj):
-    """Return the path of the object in standard Python notation."""
+    """Return the path of the object in standard Python notation.
+
+    This method should try very hard to return a string, even if it is not a
+    valid Python path.
+    """
     if obj is None:
         return None
 
@@ -100,10 +106,31 @@
     module = getattr(naked, '__module__', _marker)
     if module is _marker:
         return naked.__name__
-    else:
-        return '%s.%s' %(module, naked.__name__)
+    return '%s.%s' %(module, naked.__name__)
 
 
+def isReferencable(path):
+    """Return whether the Python path is referencable."""
+    # Sometimes no path exists, so make a simple check first; example: None
+    if path is None:
+        return False
+    module_name, obj_name = path.rsplit('.', 1)
+    # Do not allow private attributes to be accessible
+    if (obj_name.startswith('_') and
+        not (obj_name.startswith('__') and obj_name.endswith('__'))):
+        return False
+    module = safe_import(module_name)
+    if module is None:
+        return False
+    obj = getattr(module, obj_name, _marker)
+    if obj is _marker:
+        return False
+    # Detect singeltons; those are not referencable in apidoc (yet)
+    if hasattr(obj, '__class__') and getPythonPath(obj.__class__) == path:
+        return False
+    return True
+
+
 def _evalId(id):
     if zapi.isinstance(id, Global):
         id = id.__name__
@@ -247,7 +274,25 @@
     return _format_dict.get(format, 'zope.source.stx')
 
 
-def renderText(text, module=None, format=None):
+def dedentString(text):
+    """Dedent the docstring, so that docutils can correctly render it."""
+    dedent = 0
+    lines = text.split('\n')
+    for line in lines[1:]:
+        if line != '':
+            for char in line:
+                if char == ' ':
+                    dedent += 1
+                else:
+                    break
+            break
+
+    for index in range(1, len(lines)):
+        lines[index] = lines[index][dedent:]
+    return '\n'.join(lines)
+
+
+def renderText(text, module=None, format=None, dedent=True):
     if not text:
         return u''
 
@@ -261,6 +306,8 @@
 
     assert format in _format_dict.values()
 
+    text = dedentString(text)
+
     source = zapi.createObject(format, text)
     renderer = zapi.getMultiAdapter((source, TestRequest()))
     return renderer.render()

Modified: Zope3/trunk/src/zope/app/apidoc/utilities.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/utilities.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/utilities.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -164,7 +164,7 @@
   ...     pass
 
   >>> utilities.getPythonPath(ISample)
-  '__builtin__.ISample'
+  'zope.app.apidoc.doctest.ISample'
 
 and for classes
 
@@ -173,21 +173,20 @@
   ...         pass
 
   >>> utilities.getPythonPath(Sample.sample)
-  '__builtin__.Sample'
+  'zope.app.apidoc.doctest.Sample'
 
 One can also pass functions
 
   >>> def sample():
   ...     pass
 
-  >>> # Result is a bit strange due to doctests
   >>> utilities.getPythonPath(sample)
-  'None.sample'
+  'zope.app.apidoc.doctest.sample'
 
 and even methods. If a method is passed in, its class path is returned.
 
   >>> utilities.getPythonPath(Sample.sample)
-  '__builtin__.Sample'
+  'zope.app.apidoc.doctest.Sample'
 
 Modules are another kind of objects that can return a python path:
 
@@ -206,6 +205,65 @@
   AttributeError: 'Sample' object has no attribute '__name__'
 
 
+`isReferencable(path)`
+----------------------
+
+Determine whether a path can be referenced in the API doc, usually by the code
+browser module. Initially you might think that all objects that have paths can
+be referenced somehow. But that's not true, partially by design of apidoc, but
+also due to limitations of the Python language itself.
+
+The first case is ``None``. When you ask for the python path of ``None``, you
+get ``None``, so that result should not be referencable:
+
+  >>> utilities.isReferencable(None)
+  False
+
+By design we also do not document any private classes and functions:
+
+  >>> utilities.isReferencable('some.path.to._Private')
+  False
+  >>> utilities.isReferencable('some.path.to.__Protected')
+  False
+  >>> utilities.isReferencable('zope.app.apidoc.__doc__')
+  True
+
+Some objects might fake their module name, so that it does not exist:
+
+  >>> utilities.isReferencable('foo.bar')
+  False
+
+On the other hand, you might have a valid module, but non-existent attribute:
+
+  >>> utilities.isReferencable('zope.app.apidoc.MyClass')
+  False
+
+Note that this case is also used for types that are generated using the
+``type()`` function:
+
+  >>> mytype = type('MyType', (object,), {})
+  >>> path = utilities.getPythonPath(mytype)
+  >>> path
+  'zope.app.apidoc.doctest.MyType'
+
+  >>> utilities.isReferencable(path)
+  False
+
+Finally, since API doc does not allow the documentation of instances yet, it
+is not possible to document singletons, so they are not referencable:
+
+  >>> class Singelton(object):
+  ...     pass
+
+  >>> utilities.isReferencable('zope.app.apidoc.doctest.Singelton')
+  True
+
+  >>> Singelton = Singelton()
+
+  >>> utilities.isReferencable('zope.app.apidoc.doctest.Singelton')
+  False
+
+
 `getPermissionIds(name, checker=_marker, klass=_marker)`
 --------------------------------------------------------
 
@@ -455,23 +513,23 @@
 First we check whether an aatribute can be found in a list of interfaces:
 
   >>> utilities.getInterfaceForAttribute('attr', (I1, I2), asPath=False)
-  <InterfaceClass __builtin__.I1>
+  <InterfaceClass zope.app.apidoc.doctest.I1>
   >>> utilities.getInterfaceForAttribute('getAttr', (I1, I2), asPath=False)
-  <InterfaceClass __builtin__.I2>
+  <InterfaceClass zope.app.apidoc.doctest.I2>
 
 Now we are repeating the same lookup, but using the class, instead of a list
 of interfaces:
 
   >>> utilities.getInterfaceForAttribute('attr', klass=Sample, asPath=False)
-  <InterfaceClass __builtin__.I1>
+  <InterfaceClass zope.app.apidoc.doctest.I1>
   >>> utilities.getInterfaceForAttribute('getAttr', klass=Sample, asPath=False)
-  <InterfaceClass __builtin__.I2>
+  <InterfaceClass zope.app.apidoc.doctest.I2>
 
 By default, `asPath` is `True`, which means the path of the interface is
 returned:
 
   >>> utilities.getInterfaceForAttribute('attr', (I1, I2))
-  '__builtin__.I1'
+  'zope.app.apidoc.doctest.I1'
 
 If no match is found, ``None`` is returned.
 
@@ -552,6 +610,70 @@
 format. All converted and new modules will have the `__docformat__` attribute.
 
 
+`dendentString(text)`
+---------------------
+
+Before doc strings can be processed using STX or ReST they must be dendented,
+since otherwise the output will be incorrect. Let's have a look at some
+docstrings and see how they are correctly dedented.
+
+Let's start with a simple one liner. Nothing should happen:
+
+  >>> def func():
+  ...     '''One line documentation string'''
+
+  >>> utilities.dedentString(func.__doc__)
+  'One line documentation string'
+
+Now what about one line docstrings that start on the second line? While this
+format is discouraged, it is frequently used:
+
+  >>> def func():
+  ...     '''
+  ...     One line documentation string
+  ...     '''
+
+  >>> utilities.dedentString(func.__doc__)
+  '\nOne line documentation string\n'
+
+We can see that the leading whitespace on the string is removed, but not the
+newline character. Let's now try a simple multi-line docstring:
+
+  >>> def func():
+  ...     '''Short description
+  ...
+  ...     Lengthy description, giving some more background information and
+  ...     discuss some edge cases.
+  ...     '''
+
+  >>> print utilities.dedentString(func.__doc__)
+  Short description
+  <BLANKLINE>
+  Lengthy description, giving some more background information and
+  discuss some edge cases.
+  <BLANKLINE>
+
+Again, the whitespace was removed only after the first line. Also note that
+the function determines the indentation level correctly. So what happens if
+there are multiple indentation levels? Well, the first occurrence wins:
+
+  >>> def func():
+  ...     '''Short description
+  ...
+  ...     Root Level
+  ...
+  ...       Second Level
+  ...     '''
+
+  >>> print utilities.dedentString(func.__doc__)
+  Short description
+  <BLANKLINE>
+  Root Level
+  <BLANKLINE>
+    Second Level
+  <BLANKLINE>
+
+
 `renderText(text, module=None, format=None)`
 --------------------------------------------
 

Modified: Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -17,9 +17,12 @@
 """
 __docformat__ = 'restructuredtext'
 
+from zope.security.proxy import removeSecurityProxy
+
 from zope.app import zapi
 from zope.app.location import LocationProxy
 from zope.app.apidoc.ifacemodule.browser import InterfaceDetails
+from zope.app.apidoc.component import getUtilityInfoDictionary
 from zope.app.apidoc.utilities import getPythonPath
 from zope.app.apidoc.utilitymodule.utilitymodule import NONAME, Utility
 from zope.app.apidoc.utilitymodule.utilitymodule import UtilityInterface
@@ -44,15 +47,12 @@
 
     def getComponent(self):
         """Return the python path of the implementation class."""
-        # We could use `type()` here, but then we would need to remove the
-        # security proxy from the component. This is easier and also supports
-        # old-style classes
-        klass = self.context.component.__class__
+        # Remove proxy here, so that we can determine the type correctly
+        naked = removeSecurityProxy(self.context.registration)
+        result = getUtilityInfoDictionary(naked)
+        return {'path': result['path'], 'url': result['url']}
 
-        return {'path': getPythonPath(klass),
-                'url':  getPythonPath(klass).replace('.', '/')}
 
-
 class Menu(object):
     """Menu View Helper Class"""
 
@@ -72,5 +72,5 @@
             iface = zapi.getParent(obj)
             return './'+zapi.name(iface) + '/' + zapi.name(obj) + '/index.html'
         if zapi.isinstance(obj, UtilityInterface):
-            return '../Interface/'+zapi.name(obj) + '/apiindex.html'
+            return '../Interface/'+zapi.name(obj) + '/index.html'
         return None

Modified: Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/utilitymodule/browser.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -32,7 +32,7 @@
   >>> menu.getMenuTitle(node)
   'iface'
   >>> menu.getMenuLink(node)
-  '../Interface/foo.bar.iface/apiindex.html'
+  '../Interface/foo.bar.iface/index.html'
 
 Next, let's get the menu title and link for a utility with a name. We first
 have to create a utility registration
@@ -140,15 +140,14 @@
 
   >>> foo_reg = type(
   ...     'RegistrationStub', (),
-  ...     {'name': '', 'provided': None, 'component': Foo(), 'doc': ''})()
+  ...     {'name': '', 'provided': Interface, 'component': Foo(), 'doc': ''})()
 
 Then we create a utility documentation class and its details view:
 
   >>> details = UtilityDetails()
-  >>> details.context = Utility(None, foo_reg)
+  >>> details.context = Utility(Interface, foo_reg)
 
 Now we can get the component information:
 
   >>> pprint(details.getComponent())
-  {'path': '__builtin__.Foo',
-   'url': '__builtin__/Foo'}
+  {'path': '__builtin__.Foo', 'url': None}

Modified: Zope3/trunk/src/zope/app/apidoc/utilitymodule/index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/utilitymodule/index.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/utilitymodule/index.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -7,7 +7,7 @@
   <h1 class="details-header">
     <a href=""
        tal:attributes="
-           href string:../../../Interface/${iface/getId}/apiindex.html"
+           href string:../../../Interface/${iface/getId}/index.html"
        tal:content="iface/getId" /> <br />
     <tal:block i18n:translate="">(Name: "<span
         tal:replace="view/getName" i18n:name="name" />")</tal:block>

Modified: Zope3/trunk/src/zope/app/apidoc/utilitymodule/utilitymodule.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/utilitymodule/utilitymodule.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/utilitymodule/utilitymodule.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -33,14 +33,13 @@
 NONAME = '__noname__'
 
 def encodeName(name):
-    # base64 strings can have lines at most 76 chars long; so make sure we get
-    # rid of all newline chars. See RFC-3548.
-    return base64.encodestring(name.encode('utf-8')).replace('\n', '')
+    return base64.urlsafe_b64encode(name.encode('utf-8'))
 
 def decodeName(name):
     try:
-        return base64.decodestring(name).decode('utf-8')
-    except binascii.Error:
+        return base64.urlsafe_b64decode(str(name)).decode('utf-8')
+    except (binascii.Error, TypeError):
+        # Someone probably passed a non-encoded name, so let's accept that.
         return name
 
 class Utility(object):

Modified: Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -26,7 +26,8 @@
 from zope.app.location import LocationProxy
 from zope.app.apidoc.zcmlmodule import Directive, Namespace
 from zope.app.apidoc.ifacemodule.browser import InterfaceDetails
-from zope.app.apidoc.utilities import getPythonPath, relativizePath
+from zope.app.apidoc.utilities import getPythonPath, isReferencable
+from zope.app.apidoc.utilities import relativizePath
 
 class Menu(object):
     """Menu View Helper Class"""
@@ -83,7 +84,7 @@
         """Get the file where the directive was declared."""
         # ZCML directive `info` objects do not have security declarations, so
         # everything is forbidden by default. We need to remove the security
-        # proxies in order to get to the data.  
+        # proxies in order to get to the data.
         info = removeSecurityProxy(self.context.info)
         if zapi.isinstance(info, ParserInfo):
             return {'file': relativizePath(info.file),
@@ -103,8 +104,9 @@
         """Return information about the handler."""
         if self.context.handler is not None:
             path = getPythonPath(self.context.handler)
-            return {'path': path,
-                    'url': path.replace('.', '/')}
+            return {
+                'path': path,
+                'url': isReferencable(path) and path.replace('.', '/') or None}
         return None
 
     def getSubdirectives(self):
@@ -113,12 +115,12 @@
         for ns, name, schema, handler, info in self.context.subdirs:
             details = self._getInterfaceDetails(schema)
             path = getPythonPath(handler)
+            url = isReferencable(path) and path.replace('.', '/') or None
             dirs.append({
                 'namespace': ns,
                 'name': name,
                 'schema': details,
-                'handler': {'path': path,
-                            'url': path.replace('.', '/')},
+                'handler': {'path': path, 'url': url},
                 'info': info,
                 })
         return dirs

Modified: Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.txt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/zcmlmodule/browser.txt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -26,7 +26,7 @@
 
 and generate a tree node :
 
-  >>> from zope.app.tree.node import Node 
+  >>> from zope.app.tree.node import Node
   >>> node = Node(ns)
 
 We can now ask the menu for the title of the namespace
@@ -41,7 +41,7 @@
 
 Since the 'ALL' namespace is not that useful, let's create a namespace
 instance for the browser namespace:
-  
+
   >>> ns = Namespace(module, 'http://namespaces.zope.org/browser')
   >>> node = Node(ns)
 
@@ -52,7 +52,7 @@
   >>> menu.getMenuLink(node) is None
   True
 
-Now we add the `page` directive to the browser namespace: 
+Now we add the `page` directive to the browser namespace:
 
   >>> from zope.app.apidoc.zcmlmodule import Directive
   >>> dir = Directive(ns, 'page', None, None, None, None)
@@ -80,7 +80,7 @@
   >>> class IFoo(Interface):
   ...     class_ = Attribute('class_')
 
-  >>> def foo(): 
+  >>> def foo():
   ...     pass
 
   >>> directive = Directive(ns, 'page', IFoo, foo, None, ())
@@ -187,7 +187,7 @@
 
   >>> pprint(details.getHandler())
   {'path': 'None.foo',
-   'url': 'None/foo'}
+   'url': None}
 
 
 `getSubdirectives()`
@@ -210,8 +210,7 @@
 the result becomes more interesting:
 
   >>> pprint(details.getSubdirectives()) #doctest:+ELLIPSIS
-  [{'handler': {'path': 'None.handler',
-                'url': 'None/handler'},
+  [{'handler': {'path': 'None.handler', 'url': None},
     'info': 'info',
     'name': 'foo',
     'namespace': 'browser',

Modified: Zope3/trunk/src/zope/app/apidoc/zcmlmodule/index.pt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/zcmlmodule/index.pt	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/apidoc/zcmlmodule/index.pt	2005-10-29 23:25:40 UTC (rev 39722)
@@ -48,7 +48,7 @@
 
   <div class="indent">
     <a href="" tal:attributes="href
-           string:../../../Interface/${schema/getId}/apiindex.html">
+           string:../../../Interface/${schema/getId}/index.html">
       <h3 tal:content="schema/getId">zope.fields.Schema</h3>
     </a>
   </div>
@@ -105,7 +105,7 @@
   <div class="indent">
     <a href=""
        tal:attributes="href
-           string:../../../Interface/${dir/schema/getId}/apiindex.html">
+           string:../../../Interface/${dir/schema/getId}/index.html">
       <h3 tal:content="dir/schema/getId">zope.fields.Schema</h3>
     </a>
   </div>

Modified: Zope3/trunk/src/zope/app/interpreter/python.py
===================================================================
--- Zope3/trunk/src/zope/app/interpreter/python.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/interpreter/python.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -41,10 +41,10 @@
             code.exec_(globals,
                        {}, # we don't want to get local assignments saved.
                        )
-            
+
         return tmp.getvalue()
-        
 
+
     def evaluateRawCode(self, code, globals):
         """See `zope.app.interfaces.IInterpreter`"""
         # Removing probable comments
@@ -59,7 +59,7 @@
         if code.startswith(' ') or code.startswith('\t'):
             code = 'if 1 == 1:\n' + code
         return self.evaluate(code, globals)
-        
 
+
 # It's a singelton for now.
 PythonInterpreter = PythonInterpreter()

Modified: Zope3/trunk/src/zope/app/onlinehelp/browser/tree.py
===================================================================
--- Zope3/trunk/src/zope/app/onlinehelp/browser/tree.py	2005-10-29 23:19:30 UTC (rev 39721)
+++ Zope3/trunk/src/zope/app/onlinehelp/browser/tree.py	2005-10-29 23:25:40 UTC (rev 39722)
@@ -39,35 +39,35 @@
         Iterate this dict oan build from the level info
         a navigation tree in the page tmeplate.
         Each time you get a level 0 means this is a subitem of the
-        Onlinehelp itself.
+        Onlinehelp itself::
 
-        info = [('id',{infoDict}),(),()]
+          >>> info = [('id',{infoDict}),(),()]
 
-        <ul class="tree" id="tree">
-          <li><a href="#">items</a>
-            <ul>
-              <li><a href="#">item</a></li>
-            </ul>
-          </li>
-          <li><a href="#">items</a>
-            <ul>
-              <li><a href="#">items</a>
-                <ul>
-                  <li><a href="#">item</a></li>
-                  <li><a href="#">item</a></li>
-                  <li><a href="#">item</a></li>
-                </ul>
-              </li>
-              <li><a href="#">items</a>
-                <ul>
-                  <li><a href="#">item</a></li>
-                  <li id="activeTreeNode"><a href="#">active item</a></li>
-                  <li><a href="#">item</a></li>
-                </ul>
-              </li>
-            </ul>
-          </li>
-        <ul>
+          <ul class="tree" id="tree">
+            <li><a href="#">items</a>
+              <ul>
+                <li><a href="#">item</a></li>
+              </ul>
+            </li>
+            <li><a href="#">items</a>
+              <ul>
+                <li><a href="#">items</a>
+                  <ul>
+                    <li><a href="#">item</a></li>
+                    <li><a href="#">item</a></li>
+                    <li><a href="#">item</a></li>
+                  </ul>
+                </li>
+                <li><a href="#">items</a>
+                  <ul>
+                    <li><a href="#">item</a></li>
+                    <li id="activeTreeNode"><a href="#">active item</a></li>
+                    <li><a href="#">item</a></li>
+                  </ul>
+                </li>
+              </ul>
+            </li>
+          <ul>
         """
         return self.renderTree(self.onlinehelp)
 



More information about the Zope3-Checkins mailing list