[Checkins] SVN: Sandbox/ulif/grokadmin/trunk/src/grokadmin/ Add basic docgrok machinery.

Uli Fouquet uli at gnufix.de
Sun Oct 7 19:35:00 EDT 2007


Log message for revision 80700:
  Add basic docgrok machinery.

Changed:
  A   Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.py
  A   Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.txt
  U   Sandbox/ulif/grokadmin/trunk/src/grokadmin/tests/test_grokadmin.py

-=-
Added: Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.py
===================================================================
--- Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.py	                        (rev 0)
+++ Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.py	2007-10-07 23:35:00 UTC (rev 80700)
@@ -0,0 +1,656 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""The Grok's Friendly Doctor.
+
+Ask DocGrok and he will try his best, to keep you well informed about
+everything hanging around in your Zope3 and your Grok Application.
+
+See file `docgrok.txt` in this package to learn more about docgrok.
+
+"""
+
+import os
+import sys # for sys.path
+import types
+import grok
+import inspect
+from urlparse import urlparse, urlunparse
+
+import zope.component
+from zope.app.folder.interfaces import IRootFolder
+from zope.dottedname.resolve import resolve
+from zope.interface.interface import InterfaceClass
+from zope.security.proxy import isinstance
+from zope.security.proxy import removeSecurityProxy
+from zope.proxy import removeAllProxies
+
+from zope.app.apidoc.codemodule.module import Module
+from zope.app.apidoc.codemodule.class_ import Class
+from zope.app.apidoc.codemodule.text import TextFile
+from zope.app.apidoc.utilities import renderText
+from zope.app.apidoc.utilities import getFunctionSignature
+from zope.app.apidoc.utilities import getPythonPath, getPermissionIds
+from zope.app.apidoc.utilities import isReferencable
+
+import grok.interfaces
+from grok.interfaces import IApplication
+from martian.scan import is_package, ModuleInfo
+from martian import ClassGrokker, ModuleGrokker
+
+from grok.admin.objectinfo import ZopeObjectInfo
+from grokadmin.app import GrokAdmin
+
+# This is the name under which the docgrok object-browser can be
+# reached.
+DOCGROK_ITEM_NAMESPACE = 'docgrok-obj'
+
+grok.context(GrokAdmin)
+grok.define_permission('grok.ManageApplications')
+
+def find_filepath(dotted_path):
+    """Find the filepath for a dotted name.
+
+    If a dotted name denotes a filename we try to find its path
+    by concatenating it with the system paths and looking for an
+    existing file. Every dot in the filename thereby can be part
+    of the filename or of its path. Therefore we check the
+    several possible dirname/filename combinations possible.
+
+    Returns None if no suitable filepath can be found.
+
+    This functions does *not* look for Python elements like classes,
+    interfaces and the files where they were defined. Use `resolve()`
+    and the `__file__` attribute for examining this kind of stuff
+    instead.
+
+    This function finds the location of text files and the like, as
+    far as they are placed inside some Python path.
+    """
+    currpath = dotted_path
+    currname = ""
+    while '.' in currpath:
+        currpath, name = currpath.rsplit('.', 1)
+        if currname != "":
+            currname = "." + currname
+        currname = name + currname
+        tmp_path = ""
+        for elem in currpath.split('.'):
+            tmp_path = os.path.join(tmp_path, elem)
+        for syspath in sys.path:
+            filepath_to_check = os.path.join(syspath, tmp_path, currname)
+            if os.path.isfile(filepath_to_check):
+                return filepath_to_check
+    return None
+
+class DocGrokHandler(object):
+    """A handler for DocGrok objects.
+
+    The solely purpose of DocGrokHandlers is to determine, whether a
+    given dotted path denotes a special type of object. If so, it
+    should pass back an appropriate DocGrok object.
+
+    In plain English: DocGrokHandlers find a doctor for any object.
+
+    All we expect a DocGrokHandler to support, is a method
+    ``getDoctor``, which takes a dotted path (and optional the object
+    denoted by this path), to deliver an appropriate doctor or
+    ``None``.
+    """
+    def getDoctor(self, dotted_path, obj=None):
+        """The default docfinder cannot serve.
+        """
+        return
+
+
+class DocGrokModuleHandler(DocGrokHandler):
+    def getDoctor(self, dotted_path, obj=None):
+        """Find a doctor for modules or None, if the dotted_path does
+        not denote a module.
+        """
+        if obj is None:
+            try:
+                obj = resolve(dotted_path)
+            except ImportError:
+                return
+        if not hasattr(obj, '__file__'):
+            return
+        if not is_package(os.path.dirname(obj.__file__)):
+            return
+        if os.path.basename(obj.__file__) in ['__init__.py',
+                                             '__init__.pyc',
+                                             '__init__.pyo']:
+            return
+        return DocGrokModule(dotted_path)
+
+
+class DocGrokPackageHandler(DocGrokHandler):
+    def getDoctor(self, dotted_path, obj=None):
+        """Determine, whether the given path/obj references a Python
+        package.
+        """
+        if obj is None:
+            try:
+                obj = resolve(dotted_path)
+            except ImportError:
+                return
+        if not hasattr(obj, '__file__'):
+            return
+        if not is_package(os.path.dirname(obj.__file__)):
+            return
+        if os.path.basename(obj.__file__) not in ['__init__.py',
+                                                 '__init__.pyc',
+                                                 '__init__.pyo']:
+            return
+        return DocGrokPackage(dotted_path)
+
+
+class DocGrokInterfaceHandler(DocGrokHandler):
+    def getDoctor(self, dotted_path, obj=None):
+        """Determine, whether the given path/obj references an interface.
+        """
+        if obj is None:
+            try:
+                obj = resolve(dotted_path)
+            except ImportError:
+                return
+        if not isinstance(
+            removeAllProxies(obj), InterfaceClass):
+            return
+        return DocGrokInterface(dotted_path)
+
+
+class DocGrokClassHandler(DocGrokHandler):
+    def getDoctor(self, dotted_path, obj=None):
+        """Determine, whether the given path/obj references a Python class.
+        """
+        if obj is None:
+            try:
+                obj = resolve(dotted_path)
+            except ImportError:
+                return
+        if not isinstance(obj, (types.ClassType, type)):
+            return
+        return DocGrokClass(dotted_path)
+
+
+class DocGrokGrokApplicationHandler(DocGrokHandler):
+    def getDoctor(self, dotted_path, obj=None):
+        """Determine, whether the given path/obj references a Grok application.
+        """
+        if obj is None:
+            try:
+                obj = resolve(dotted_path)
+            except ImportError:
+                None
+        try:
+            if not IApplication.implementedBy(obj):
+                return
+        except TypeError:
+            return
+        return DocGrokGrokApplication(dotted_path)
+
+
+class DocGrokTextFileHandler(DocGrokHandler):
+    def getDoctor(self, dotted_path, obj=None):
+        """Determine whether the dotted_path denotes a textfile.
+        """
+        if obj is not None:
+            # Textfiles that are objects, are not text files.
+            return
+        if os.path.splitext(dotted_path)[1] != u'.txt':
+            return
+        return DocGrokTextFile(dotted_path)
+
+
+def docgrok_handle(dotted_path):
+    """Find a doctor specialized for certain things.
+    """
+    try:
+        ob = resolve(dotted_path)
+    except ImportError:
+        # There is no object of that name. Give back 404.
+        # TODO: Do something more intelligent, offer a search.
+        if not find_filepath(dotted_path):
+            return
+        ob = None
+    except:
+        return
+
+    for handler in docgrok_handlers:
+        if type(handler) is not type({}):
+            continue
+        if 'docgrok_handler' not in handler.keys():
+            continue
+        spec_handler = handler['docgrok_handler']()
+        if not isinstance(spec_handler, DocGrokHandler):
+            continue
+        doc_grok = spec_handler.getDoctor(dotted_path, ob)
+        if doc_grok is None:
+            continue
+        return doc_grok
+    # No special doctor could be found.
+    return DocGrok(dotted_path)
+
+def getInterfaceInfo(iface):
+    if iface is None:
+        return
+    path = getPythonPath(iface)
+    return {'path': path,
+            'url': isReferencable(path) and path or None}
+
+
+class DocGrokGrokker(ClassGrokker):
+    """A grokker that groks DocGroks.
+
+    This grokker can help to 'plugin' different docgroks in an easy
+    way. You can register docgroks for your special classes, modules,
+    things. All required, is a function, that determines the correct
+    kind of thing, you like to offer a docgrok for and returns a
+    specialized docgrok or None (in case the thing examined is not the
+    kind of thing your docgrok is a specialist for).
+
+    Unfortunately, order counts here. If one docgrok handler is able
+    to deliver a specialized docgrok object, no further invesitgation
+    will be done.
+
+    In principle, the following should work. First we import the
+    docgrok module, because it contains a more specific grokker: the
+    InstanceGrokker 'docgrok_grokker' ::
+
+      >>> from grok.admin import docgrok
+
+    Then we create an (empty) 'ModuleGrokker'. 'ModuleGrokkers' can
+    grok whole modules. ::
+
+      >>> from martian import ModuleGrokker
+      >>> module_grokker = ModuleGrokker()
+
+    Then we register the 'docgrok_grokker', which should contain some
+    base handlers for modules, classes, etc. by default::
+
+      >>> module_grokker.register(docgrok.docgrok_handler_grokker)
+
+    The 'docgrok_handler_grokker' is an instance of 'DocGrokGrokker'::
+
+      >>> from grok.admin.docgrok import DocGrokGrokker
+      >>> isinstance(docgrok.docgrok_handler_grokker, DocGrokGrokker)
+      True
+
+    That's it.
+
+    """
+    component_class = DocGrokHandler
+
+    def grok(self, name, obj, **kw):
+        if not issubclass(obj, DocGrokHandler):
+            return
+        if not hasattr(obj, 'getDoctor'):
+            return
+        docgrok_handlers.insert(0, {'name':name,
+                                    'docgrok_handler':obj})
+        return True
+
+
+class DocGrok(grok.Model):
+    """DocGrok helps us finding out things about ourselves.
+
+    There are DocGroks for packages, modules, interfaces, etc., each
+    one a specialist for a certain type of element. 'Pure' DocGroks
+    build the root of this specialist hierarchy and care for objects,
+    which can not be handled by other, more specialized DocGroks.
+
+    DocGrok offers a minimum of information but can easily be extended in
+    derived classes.
+    """
+    path = None
+    _traversal_root = None
+
+    def __init__(self, dotted_path):
+        self.path = dotted_path
+
+    def getPath(self):
+        return self.path
+
+    def getFilePath(self):
+        try:
+            ob = resolve(self.path)
+            return hasattr(ob, __file__) and os.path.dirname(ob.__file__) or None
+        except ImportError:
+            pass
+        return find_filepath(self.path)
+
+    def getDoc(self, heading_only=False):
+        """Get the doc string of the module STX formatted.
+        """
+        if hasattr(self, "apidoc") and hasattr(
+            self.apidoc, "getDocString"):
+            text = self.apidoc.getDocString()
+        else:
+            return
+        if text is None:
+            return
+        lines = text.strip().split('\n')
+        if len(lines) and heading_only:
+            # Find first empty line to separate heading from trailing text.
+            headlines = []
+            for line in lines:
+                if line.strip() == "":
+                    break
+                headlines.append(line)
+            lines = headlines
+        # Get rid of possible CVS id.
+        lines = [line for line in lines if not line.startswith('$Id')]
+        return renderText('\n'.join(lines), self.path)
+
+
+    def traverse(self,patient):
+        """ Do special traversing inside the surgery.
+
+        Inside the docgrok-'namespace' we only accept DocGroks and
+        colleagues. Each DocGrok cares for a patient represented by a
+        path. This path might denote an object in the ZODB or in the
+        python path.
+
+        """
+        if patient == "index.html":
+            return self
+        if self.path is None:
+            newpath = patient
+        else:
+            newpath = '.'.join([self.path, patient])
+
+        doctor = docgrok_handle(newpath)
+
+        if doctor is None:
+            # There is nothing of that name. Give back 404.
+            # XXX Do something more intelligent, offer a search.
+            return
+        doctor.__parent__ = self
+        doctor.__name__ = patient
+        doctor._traversal_root = self._traversal_root
+        doctor.path = newpath
+        return doctor
+    pass
+
+def getItemLink(name, baseurl):
+    """Get a link to a docgrok item out of an item name and a base URL.
+
+    A docgrok item is any object, which is a 'subobject' of another
+    object, for example an attribute, a memberfunction, an annotation
+    or a sequence item (if the parent object is a sequence). Those
+    objects are not neccessarily directly accessible, but need to be
+    denoted for the object browser. We put 'docgrok-item' as marker at
+    the beginning of the URL to enable the docgrok traverser and to
+    handle those URLs in a special way. Such the objectbrowser can
+    handle those 'unaccessible' items.
+    """
+    url = list(urlparse(baseurl))
+    path = url[2]
+    if path.startswith('/' + DOCGROK_ITEM_NAMESPACE + '/'):
+        path = path[len(DOCGROK_ITEM_NAMESPACE) + 2:]
+    if path.endswith('/@@inspect.html') or path.endswith('/inspect.html'):
+        path = path.rsplit('/', 1)
+    path = "/%s/%s%s/@@inspect.html" % (DOCGROK_ITEM_NAMESPACE, path, name)
+    path = path.replace('//', '/')
+    url[2] = path
+    return urlunparse(url)
+
+
+class DocGrokTraverser(grok.Traverser):
+    """If first URL element is 'docgrok', handle over to DocGrok.
+
+    This traverser binds to the RootFolder, which means, it is only
+    asked, when the publisher looks for elements in the Zope root (or
+    another IRootFolder). The further traversing is done by the Docs'
+    own traverser in it's model. See method `traverse()` in DocGrok.
+    """
+    grok.context(IRootFolder)
+
+    def traverse(self,path):
+        if path == DOCGROK_ITEM_NAMESPACE:
+            # The objectbrowser is called...
+            obj_info = ZopeObjectInfo(self.context)
+            obj_info.__parent__ = self.context
+            obj_info.__name__ = DOCGROK_ITEM_NAMESPACE
+            return obj_info
+
+        if path == "docgrok":
+            doctor = DocGrok(None)
+            # Giving a __parent__ and a __name__, we make things
+            # locatable in sense of ILocatable.
+            doctor.__parent__ = self.context
+            doctor.__name__ = 'docgrok'
+            doctor._traversal_root = doctor
+            return doctor
+        return
+
+
+class DocGrokPackage(DocGrok):
+    """This doctor cares for python packages.
+    """
+    path=None
+    apidoc = None
+    _traversal_root = None
+
+    def __init__(self,dotted_path):
+        self.path = dotted_path
+        self._module = resolve(self.path)
+        # In apidoc packages are handled like modules...
+        self.apidoc = Module(None, None, self._module, True)
+
+    def getDocString(self):
+        return self.apidoc.getDocString()
+
+    def getFilePath(self):
+        ob = resolve(self.path)
+        return os.path.dirname(ob.__file__) + '/'
+
+    def _getModuleInfos(self, filter_func=lambda x:x):
+        """Get modules and packages of a package.
+
+        The filter function will be applied to a list of modules and
+        packages of type grok.scan.ModuleInfo.
+        """
+        ob = resolve(self.path)
+        filename = ob.__file__
+        module_info = ModuleInfo(filename, self.path)
+        try:
+            infos = module_info.getSubModuleInfos(exclude_tests=False)
+        except TypeError:
+            # Another version of martian.scan is installed
+            infos = module_info.getSubModuleInfos()
+        if filter_func is not None:
+            infos = filter(filter_func, infos)
+        result = []
+        for info in infos:
+            subresult = {}
+            # Build a url string from dotted path...
+            mod_path = "docgrok"
+            for path_part in info.dotted_name.split('.'):
+                mod_path = os.path.join(mod_path, path_part)
+            subresult = {
+                'url' : mod_path,
+                'name' : info.name,
+                'dotted_name' : info.dotted_name
+                }
+            result.append(subresult)
+        return result
+
+
+    def getModuleInfos(self):
+        """Get the modules inside a package.
+        """
+        filter_func = lambda x: not x.isPackage()
+        return self._getModuleInfos(filter_func)
+
+    def getSubPackageInfos(self):
+        """Get the subpackages inside a package.
+        """
+        filter_func = lambda x: x.isPackage()
+        return self._getModuleInfos(filter_func)
+
+    def getTextFiles(self):
+        """Get the text files inside a package.
+        """
+        filter_func = lambda x: x.isinstance(TextFile)
+        return self._getModuleInfos(filter_func)
+
+    def getChildren(self):
+        result = self.apidoc.items()
+        result.sort(lambda x,y:cmp(x[0], y[0]))
+        return result
+
+
+
+class DocGrokModule(DocGrokPackage):
+    """This doctor cares for python modules.
+    """
+
+    def getFilePath(self):
+        ob = resolve(self.path)
+        filename = ob.__file__
+        if filename.endswith('o') or filename.endswith('c'):
+            filename = filename[:-1]
+        return filename
+
+
+class DocGrokClass(DocGrokPackage):
+    """This doctor cares for classes.
+    """
+    def __init__(self,dotted_path):
+        self.path = dotted_path
+        self.klass = resolve(self.path)
+        self.module_path, self.name = dotted_path.rsplit('.',1)
+        self.module = resolve(self.module_path)
+        mod_apidoc = Module(None, None, self.module, False)
+        self.apidoc = Class(mod_apidoc, self.name, self.klass)
+
+    def getFilePath(self):
+        if not hasattr(self.module, "__file__"):
+            return
+        filename = self.module.__file__
+        if filename.endswith('o') or filename.endswith('c'):
+            filename = filename[:-1]
+        return filename
+
+    def getAttributes(self):
+        """Get the attributes of this class."""
+        attrs = []
+        # See remark in getMethods()
+        klass = removeSecurityProxy(self.apidoc)
+        for name, attr, iface in klass.getAttributes():
+            entry = {'name': name,
+                     'value': `attr`,
+                     'type': type(attr).__name__,
+                     'interface': iface
+                }
+            entry.update(getPermissionIds(name,klass.getSecurityChecker()))
+            attrs.append(entry)
+        return attrs
+
+    def getMethods(self):
+        """Get all methods of this class."""
+        methods = []
+        # remove the security proxy, so that `attr` is not proxied. We could
+        # unproxy `attr` for each turn, but that would be less efficient.
+        #
+        # `getPermissionIds()` also expects the class's security checker not
+        # to be proxied.
+        klass = removeSecurityProxy(self.apidoc)
+        for name, attr, iface in klass.getMethodDescriptors():
+            entry = {'name': name,
+                     'signature': "(...)",
+                     'doc':attr.__doc__ or '',
+                     'attr' : attr,
+                     'interface' : iface}
+            entry.update(getPermissionIds(name, klass.getSecurityChecker()))
+            methods.append(entry)
+
+        for name, attr, iface in klass.getMethods():
+            entry = {'name': name,
+                     'signature': getFunctionSignature(attr),
+                     'doc':attr.__doc__ or '',
+                     'attr' : attr,
+                     'interface' : iface}
+            entry.update(getPermissionIds(name, klass.getSecurityChecker()))
+            methods.append(entry)
+        return methods
+
+
+class DocGrokInterface(DocGrokClass):
+    """This doctor cares for interfaces.
+    """
+    def __init__(self,dotted_path):
+        self.path = dotted_path
+        self.klass = resolve(self.path)
+        self.module_path, self.name = dotted_path.rsplit('.',1)
+        self.module = resolve(self.module_path)
+        mod_apidoc = Module(None, None, self.module, False)
+        self.apidoc = Class(mod_apidoc, self.name, self.klass)
+
+    def getFilePath(self):
+        if not hasattr(self.module, "__file__"):
+            return
+        filename = self.module.__file__
+        if filename.endswith('o') or filename.endswith('c'):
+            filename = filename[:-1]
+        return filename
+
+class DocGrokGrokApplication(DocGrokClass):
+    """This doctor cares for Grok applications and components.
+    """
+    pass
+
+class DocGrokTextFile(DocGrok):
+    """This doctor cares for text files.
+    """
+
+    def __init__(self,dotted_path):
+        self.path = dotted_path
+        self.filepath = find_filepath(self.path)
+        self.filename = os.path.basename(self.filepath)
+
+
+    def getPackagePath(self):
+        """Return package path as dotted name.
+        """
+        dot_num_in_filename = len([x for x in self.filename if x == '.'])
+        parts = self.path.rsplit('.', dot_num_in_filename + 1)
+        return parts[0]
+
+    def getContent(self):
+        """Get file content UTF-8 encoded.
+        """
+        file = open(self.filepath, 'rU')
+        content = file.read()
+        file.close()
+        return content.decode('utf-8')
+
+# The docgroks registry.
+#
+# We register 'manually', because the handlers
+# are defined in the same module.
+docgrok_handlers = []
+
+docgrok_handler_grokker = DocGrokGrokker()
+docgrok_handler_grokker.grok('module', DocGrokModuleHandler)
+docgrok_handler_grokker.grok('package', DocGrokPackageHandler)
+docgrok_handler_grokker.grok('interface', DocGrokInterfaceHandler)
+docgrok_handler_grokker.grok('class', DocGrokClassHandler)
+docgrok_handler_grokker.grok('grokapplication',
+                             DocGrokGrokApplicationHandler)
+docgrok_handler_grokker.grok('textfile', DocGrokTextFileHandler)
+docgrok_handlers_grokker = ModuleGrokker()
+docgrok_handlers_grokker.register(docgrok_handler_grokker)
+

Added: Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.txt
===================================================================
--- Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.txt	                        (rev 0)
+++ Sandbox/ulif/grokadmin/trunk/src/grokadmin/docgrok.txt	2007-10-07 23:35:00 UTC (rev 80700)
@@ -0,0 +1,291 @@
+=======
+DocGrok
+=======
+
+The Grok's personal doctor.
+
+DocGrok is meant as a friendly extension of the Zope 3 'builtin'
+apidoc feature. It should be easy to handle and informative in
+content. Main target are developers new to Grok.
+
+DocGrok is a helper to generate documentation for nearly everything
+living in a running Zope 3 instance. Basically it provides
+'information'-objects for different types of things, like modules,
+classes, functions, textfiles etc. 
+
+A ``DocGrok`` therefore is an object wich normally is bound to a
+certain entity, which is describable by a dotted path. It provides
+special methods to get information about the thing, a certain
+``DocGrok`` is bound to.
+
+.. contents::
+
+
+How to Use it
+-------------
+
+DocGrok documentation can be accessed through the web, calling special
+URL paths in your Zope 3 instance or (surprise!) directly via Python.
+
+
+Calling DocGrok through the web
++++++++++++++++++++++++++++++++
+
+To get documentation about a special element, call docgrok simply with
+`docgrok` as first part of the URL path.
+
+For example documentation about the grok package can be reached using
+
+    http://localhost:8080/docgrok/grok
+
+The admin package, which is located in the grok package can be
+accessed directly such:
+
+    http://localhost:8080/docgrok/grok/admin
+
+In this way nearly all things can be described, which can be described
+by a dotted name notation and which are accessible at runtime.
+
+
+Calling the doctor directly
++++++++++++++++++++++++++++
+
+The doctor can also be reached via Python, naturally:
+
+   >>> import grok
+   >>> from grokadmin import docgrok
+   >>> doctor = docgrok.DocGrok('grokadmin.docgrok')
+
+This doctor has immediatly a patient, which is denoted by the dotted
+path `grokadmin.docgrok`. The dotted path might reference any thing
+which lives in the Python environment: a package, a module, a class, a
+function or even a file or some interface attribute:
+
+   >>> doctor.getPath()
+   'grokadmin.docgrok'
+
+We can also get a filepath, using the `getFilePath()` method. Objects,
+which have no filepath always return `None`.
+
+There is not much more information to get from Doc Grok. This is,
+because a `DocGrok` only knows very little about the objects. The base
+doc is not a specialist, but cares for all objects and elements, which
+can not be handled by other specialists.
+
+If we like to get more detailed information, we have to call a
+specialist. For example a package doctor, who happens to be called
+`DocGrokPackage` :
+
+    >>> from grokadmin.docgrok import DocGrokPackage
+    >>> doctor = DocGrokPackage('grok')
+    >>> doctor
+    <grokadmin.docgrok.DocGrokPackage ...>
+
+Using ``getPath()`` we get the dotted path of the thing, the doctor
+cares for:
+
+    >>> doctor.getPath()
+    'grok'
+
+Fine. Obviously DocGrokPackages know as much as DocGroks. That's
+little. But a ``DocGrokPackage`` knows also about package-things:
+
+    >>> info = doctor.getSubPackageInfos()
+
+will generate infos subpackages contained in the package we are
+examining. The ``info`` here is basically a list of dictionaries,
+where each dictionary contains a the keys ``url``, ``name`` and
+``dotted_name``:
+
+    >>> info
+    [{'url': '...', 'dotted_name': '...', 'name': '...'}...]
+
+The ``url`` element is meant as the path 'inside' the
+``docgrok``-browser, when ``docgrok`` documentation is watched in a
+browser. ``dotted_name`` and ``name`` give the dotted path of a
+subpackage and last element of this path respectively.
+
+As said, ``DocGrokPackage`` is only one possible type of 'documenter'
+already included in the docgrok module. The different ``DocGroks``
+included are documented below in section `the specialists`_ in detail.
+
+So, this is allright if we already know, what kind of thing (a module,
+package, etc.) the doctor should examine. Then we can create an
+appropriate doctor and retrieve all information we like.
+
+But how can we find out, what kind of thing we examine? Shouldn't this
+be a doctors job as well? Right! This is possible and you should *use*
+the possibility to determine the most appropriate doctor
+automatically.
+
+
+Getting the right specialist directly
++++++++++++++++++++++++++++++++++++++
+
+Often we don't want to visit the base doctor, but a specialist
+directly. But how can we tell, what specialist we need? Easy. We use
+the function ``docgrok_handle()`` which delivers us a doctor, who
+can tell us more:
+
+    >>> from grokadmin.docgrok import docgrok_handle
+    >>> thedoc = docgrok_handle('grokadmin.docgrok')
+    >>> thedoc
+    <grokadmin.docgrok.DocGrokModule ...>
+
+This is correct. `docgrok` of course *is* a python module, so the best
+specialist we can get is a `DocGrokModule`. The mentioned function
+therefore is some kind of factory, which always gives us a doctor most
+appropriate for the kind of thing specified by a dotted path.
+
+We can, for example ask for a different doc like this:
+
+    >>> thedoc = docgrok_handle('grokadmin.docgrok.DocGrok')
+    >>> thedoc
+    <grokadmin.docgrok.DocGrokClass ...>
+
+and get a class-specific doctor. Because
+``grokadmin.docgrok.DocGrok`` *is* a class, this is again the most
+appropriate doc we could get.
+
+
+How to create your own specialist docgrok
+-----------------------------------------
+
+At times you might want to create your own sort of DocGrok. This might
+be due to the fact, that you have written a special kind of 'thing' (a
+special class, module, whatever), whose special attributes, methods,
+etc. should be made available in documentation in a special way.
+
+Thanks to Martijn Faassens ``martian`` package, this can be done very
+easily. All you need is a handler function, which is able to determine
+from a dotted path, whether some 'thing' is of the type you want
+special documentation for, and, of course, a special docgrok, which is
+able to deliver the special information about your new 'thing'.
+
+Let's see how this works.
+
+Writing new DocGroks
+++++++++++++++++++++
+
+Consider we want to document ``grok.View`` classes. Since
+``grok.View`` is a class, we choose the DocGrokClass as base for our
+new DocGrok:
+
+    >>> from grokadmin.docgrok import DocGrokClass
+
+    >>> class DocGrokGrokView(DocGrokClass):
+    ...     """"This doctor cares for grok.Views."""
+    ...     pass
+
+Now we create a doctor:
+
+    >>> doctor = DocGrokGrokView()
+    Traceback (most recent call last):
+        [...]
+    TypeError: __init__() takes exactly 2 arguments (1 given)
+
+Oops! Well, we didn't specify, what kind of DocGrokView we want to
+examine. We can do that, giving a dotted name:
+
+    >>> doctor = DocGrokGrokView('grok.View')
+    >>> doctor
+    <DocGrokGrokView ...>
+
+The doctor is in. Fine. So let's see, what he can tell us about
+``grok.View``.
+
+    >>> doctor.getPath()
+    'grok.View'
+
+Ah, yes, very interesting. We got the dotted path of the examined
+class. But, where can we find the definition of it in file system? Ask
+the doc:
+
+    >>> pnorm(doctor.getFilePath())
+    '.../grok/__init__.py'
+
+This is not exactly, what we wanted to know, is it? We got the package
+location instead of the module. So the path is wrong. Really? When you
+have a look at the specified ``__init__.py``, you will discover, that
+there is indeed an assignment, which says, that ``grok.View`` is
+another name for ``grok.components.View``. It is simply a shortcut. So,
+as we asked for ``grok.View`` the answer was correct.
+
+To check it, let's see, what happens, if we give the real dotted path:
+
+    >>> doctor = DocGrokGrokView('grok.components.View')
+    >>> pnorm(doctor.getFilePath())
+    '.../grok/components.py'
+
+Ah, right. This is, what we wanted. Now we can use some of the derived
+methods to gather some more information about this class:
+
+    >>> methods = doctor.getMethods()
+
+delivers us a list of all public methods defined in this class. Each
+list entry being a dictionary with keys ``name``, ``signature``,
+``doc``, ``interface``, ``attr``, ``read_perm`` and `` write_perm``.
+
+    >>> entry = [x for x in methods if x['name'] == 'url'][0]
+    >>> entry['name']
+    'url'
+    >>> entry['doc']
+    ''
+    >>> entry['signature']
+    '(obj=None, name=None)'
+
+The other methods work as described in the ``DocGrokClass``
+documentation.
+
+This is all very well, but not too impressive. We could gather the
+information delivered relatively easy writing some simple
+snippets. So, what is it all about?
+
+One main reason is, that ``DocGroks`` are used by the Grok admin
+interface to provide easy accessible API documentation. Those
+documentation is basically able, to give some information about
+everything, which is describable as a dotted path. But some of the
+information is not very descriptive. That's it, why ``docgrok`` uses a
+set of helpers, to give more detailed information. If you, for
+instance, want that instead of the standard class-related docgrok your
+own docgrok is displayed, whenever a user wants to know something
+about ``grok.View`` and related classes, then you can register your
+docgrok and let it display documentation for ``grok.View``.
+
+The selection to register a docgrok for ``grok.View`` was very
+arbitrary. You can also register a docgrok, that handles all elements,
+whose name starts with the letter 'a' or those elements, which are
+classes and implement at least three different interfaces. It's
+completely up to you.
+
+To choose, which API elements your docgrok is able to handle, you have
+to define a handler class. This is what we want to do next.
+
+
+Create a handler and register your new docgrok
+++++++++++++++++++++++++++++++++++++++++++++++
+
+Those steps are described in the ftests for the docgrok module in
+grokadmin.ftests.docgrok. There you can also find examples, how to
+create own docgrok documentation (see the lower parts of the file).
+
+
+The Specialists
+---------------
+
+``DocGrokPackage`` - The package doctor
++++++++++++++++++++++++++++++++++++++++
+
+XXX: to be written.
+
+
+``DocGrokModule`` - The module doctor
++++++++++++++++++++++++++++++++++++++
+
+XXX: to be written.
+
+
+``DocGrokClass`` - The class doctor
++++++++++++++++++++++++++++++++++++
+
+XXX: to be written.

Modified: Sandbox/ulif/grokadmin/trunk/src/grokadmin/tests/test_grokadmin.py
===================================================================
--- Sandbox/ulif/grokadmin/trunk/src/grokadmin/tests/test_grokadmin.py	2007-10-07 23:14:19 UTC (rev 80699)
+++ Sandbox/ulif/grokadmin/trunk/src/grokadmin/tests/test_grokadmin.py	2007-10-07 23:35:00 UTC (rev 80700)
@@ -60,7 +60,7 @@
 
     for name in []:
         suite.addTest(suiteFromPackage(name))
-    for name in ['app.py']:
+    for name in ['app.py', 'docgrok.txt']:
         suite.addTest(doctest.DocFileSuite(name,
                                            package='grokadmin',
                                            globs=globs,



More information about the Checkins mailing list