[Checkins] SVN: grok/trunk/ Refactored docgrok not to use function
grokker but a class grokker instead. Extended documentation
and tests.
Uli Fouquet
uli at gnufix.de
Tue Sep 11 20:47:29 EDT 2007
Log message for revision 79579:
Refactored docgrok not to use function grokker but a class grokker instead. Extended documentation and tests.
Changed:
U grok/trunk/CHANGES.txt
U grok/trunk/src/grok/admin/docgrok.py
U grok/trunk/src/grok/admin/docgrok.txt
U grok/trunk/src/grok/admin/tests/test_grokadmin.py
U grok/trunk/src/grok/admin/view.py
U grok/trunk/src/grok/ftests/admin/docgrok.py
-=-
Modified: grok/trunk/CHANGES.txt
===================================================================
--- grok/trunk/CHANGES.txt 2007-09-11 20:44:56 UTC (rev 79578)
+++ grok/trunk/CHANGES.txt 2007-09-12 00:47:27 UTC (rev 79579)
@@ -19,6 +19,8 @@
* The admin UI now shows interfaces in modules.
+* `handle...` is not a special function name any more.
+
Restructuring
-------------
Modified: grok/trunk/src/grok/admin/docgrok.py
===================================================================
--- grok/trunk/src/grok/admin/docgrok.py 2007-09-11 20:44:56 UTC (rev 79578)
+++ grok/trunk/src/grok/admin/docgrok.py 2007-09-12 00:47:27 UTC (rev 79579)
@@ -47,7 +47,7 @@
import grok.interfaces
from grok.interfaces import IApplication
from martian.scan import is_package, ModuleInfo
-from martian import InstanceGrokker, ModuleGrokker
+from martian import ClassGrokker, ModuleGrokker
from grok.admin.objectinfo import ZopeObjectInfo
# This is the name under which the docgrok object-browser can be
@@ -95,111 +95,127 @@
return filepath_to_check
return None
+class DocGrokHandler(object):
+ """A handler for DocGrok objects.
-def handle_module(dotted_path, ob=None):
- """Determine, whether the given path/obj references a Python module.
- """
- if ob is None:
- try:
- ob = resolve(dotted_path)
- except ImportError:
- return None
- if not hasattr(ob, '__file__'):
- return None
- if not is_package(os.path.dirname(ob.__file__)):
- return None
- if os.path.basename(ob.__file__) in ['__init__.py',
- '__init__.pyc',
- '__init__.pyo']:
- return None
- return DocGrokModule(dotted_path)
+ 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.
-def handle_package(dotted_path, ob=None):
- """Determine, whether the given path/obj references a Python package.
+ 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``.
"""
- if ob is None:
- try:
- ob = resolve(dotted_path)
- except ImportError:
- return None
- if not hasattr(ob, '__file__'):
- return None
- if not is_package(os.path.dirname(ob.__file__)):
- return None
- if os.path.basename(ob.__file__) not in ['__init__.py',
+ 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 None
- return DocGrokPackage(dotted_path)
+ return
+ return DocGrokModule(dotted_path)
-def handle_interface(dotted_path, ob=None):
- """Determine, whether the given path/obj references an interface.
- """
- if ob is None:
- try:
- ob = resolve(dotted_path)
- except ImportError:
- return None
- if not isinstance(
- removeAllProxies(ob), InterfaceClass):
- return None
- return DocGrokInterface(dotted_path)
-def handle_class(dotted_path, ob=None):
- """Determine, whether the given path/obj references a Python class.
- """
- if ob is None:
- try:
- ob = resolve(dotted_path)
- except ImportError:
- return None
- if not isinstance(ob, (types.ClassType, type)):
- return None
- return DocGrokClass(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)
-def handle_grokapplication(dotted_path, ob=None):
- """Determine, whether the given path/obj references a Grok application.
- """
- if ob is None:
+
+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:
- ob = resolve(dotted_path)
- except ImportError:
- None
- try:
- if not IApplication.implementedBy(ob):
- return None
- except TypeError:
- return None
- return DocGrokGrokApplication(dotted_path)
+ if not IApplication.implementedBy(obj):
+ return
+ except TypeError:
+ return
+ return DocGrokGrokApplication(dotted_path)
-def handle_textfile(dotted_path, ob=None):
- if ob is not None:
- # Textfiles that are objects, are not text files.
- return None
- if os.path.splitext(dotted_path)[1] != u'.txt':
- return None
- return DocGrokTextFile(dotted_path)
-# The docgroks registry.
-#
-# We register 'manually', because the handlers
-# are defined in the same module.
-docgrok_handlers = [
- { 'name' : 'module',
- 'handler' : handle_module },
- { 'name' : 'package',
- 'handler' : handle_package },
- { 'name' : 'interface',
- 'handler' : handle_interface },
- { 'name' : 'grokapplication',
- 'handler' : handle_grokapplication },
- { 'name' : 'class',
- 'handler' : handle_class },
- { 'name' : 'textfile',
- 'handler' : handle_textfile}]
+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 handle(dotted_path):
+def docgrok_handle(dotted_path):
"""Find a doctor specialized for certain things.
"""
try:
@@ -208,28 +224,35 @@
# 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 None
+ return
ob = None
except:
- return None
+ return
for handler in docgrok_handlers:
- spec_handler = handler['handler']
- doc_grok = spec_handler(dotted_path, ob)
+ 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 None
+ return
path = getPythonPath(iface)
return {'path': path,
'url': isReferencable(path) and path or None}
-class DocGrokGrokker(InstanceGrokker):
+class DocGrokGrokker(ClassGrokker):
"""A grokker that groks DocGroks.
This grokker can help to 'plugin' different docgroks in an easy
@@ -249,8 +272,8 @@
>>> from grok.admin import docgrok
- Then we get create an (empty) 'ModuleGrokker'. 'ModuleGrokkers'
- can grok whole modules. ::
+ Then we create an (empty) 'ModuleGrokker'. 'ModuleGrokkers' can
+ grok whole modules. ::
>>> from martian import ModuleGrokker
>>> module_grokker = ModuleGrokker()
@@ -258,75 +281,26 @@
Then we register the 'docgrok_grokker', which should contain some
base handlers for modules, classes, etc. by default::
- >>> module_grokker.register(docgrok.docgrok_grokker)
+ >>> module_grokker.register(docgrok.docgrok_handler_grokker)
- The 'docgrok_grokker' is an instance of 'DocGrokGrokker'::
+ The 'docgrok_handler_grokker' is an instance of 'DocGrokGrokker'::
>>> from grok.admin.docgrok import DocGrokGrokker
- >>> isinstance(docgrok.docgrok_grokker, DocGrokGrokker)
+ >>> isinstance(docgrok.docgrok_handler_grokker, DocGrokGrokker)
True
- Now imagine, you have your own DocGroks for special things, for
- example for a class 'Mammoth'. You might have derived this class
- from DocGrok (or a subclass thereof), but this is not a
- requirement. Note however, that other programmers might expect
- your DocGroks to be compatible in a certain manner, so it surely
- is a good idea to derive your GrokDocs from the original one.
-
- Let's assume, your DocGrokMammoth is defined in a module called
- 'mammoth'::
-
- >>> from grok.admin.docgrok import DocGrok
- >>> class mammoth(FakeModule):
- ... class Mammoth(object):
- ... pass
- ...
- ... class MammothDocGrok(DocGrok):
- ... def isMammoth(self):
- ... return True
- ...
- ... def handle_mammoths(dotted_path,ob=None):
- ... if not isinstance(ob, Mammoth):
- ... return None
- ... return MammothDocGrok(dotted_path)
-
- This is a simple DocGrok ('MammothDocGrok') accompanied by a
- thing, it is representing (class 'Mammoth') and a handler
- function, which decides, whether a given dotted path denotes a
- Mammoth or not. The FakeModule class is a workaround to emulate
- modules in doctests. Just think of watching a module, when you see
- a FakeModule class.
-
- Now we want to register this new DocGrok with the 'global
- machinery'. Easy::
-
- >>> module_grokker.grok('mammoth_grokker', mammoth)
- True
-
- Now the 'handle_mammoths' function is considered to deliver a
- valid DocGrok, whenever it is asked. Every time, someone asks the
- docgroks 'handle()' function for a suitable docgrok for things
- that happen to be Mammoths, a DocGrokMammoth will be served.
-
- Even the default docgrok viewer that comes with the grok package
- in the admin interface, now will deliver your special views for
- mammoths (if you defined one; otherwise the default 'DocGrok'
- template will be used to show mammoth information).
-
- TODO: Show how to make a docgrok view.
-
That's it.
"""
- component_class = types.FunctionType
+ component_class = DocGrokHandler
def grok(self, name, obj, **kw):
- if not name.startswith('handle_'):
- return False
- if name in [x['name'] for x in docgrok_handlers]:
- return False
+ if not issubclass(obj, DocGrokHandler):
+ return
+ if not hasattr(obj, 'getDoctor'):
+ return
docgrok_handlers.insert(0, {'name':name,
- 'handler':obj})
+ 'docgrok_handler':obj})
return True
@@ -365,9 +339,9 @@
self.apidoc, "getDocString"):
text = self.apidoc.getDocString()
else:
- return None
+ return
if text is None:
- return None
+ return
lines = text.strip().split('\n')
if len(lines) and heading_only:
# Find first empty line to separate heading from trailing text.
@@ -398,12 +372,12 @@
else:
newpath = '.'.join([self.path, patient])
- doctor = handle(newpath)
+ 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 None
+ return
doctor.__parent__ = self
doctor.__name__ = patient
doctor._traversal_root = self._traversal_root
@@ -461,7 +435,7 @@
doctor.__name__ = 'docgrok'
doctor._traversal_root = doctor
return doctor
- return None
+ return
class DocGrokPackage(DocGrok):
@@ -566,7 +540,7 @@
def getFilePath(self):
if not hasattr(self.module, "__file__"):
- return None
+ return
filename = self.module.__file__
if filename.endswith('o') or filename.endswith('c'):
filename = filename[:-1]
@@ -629,7 +603,7 @@
def getFilePath(self):
if not hasattr(self.module, "__file__"):
- return None
+ return
filename = self.module.__file__
if filename.endswith('o') or filename.endswith('c'):
filename = filename[:-1]
@@ -664,3 +638,21 @@
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)
+
Modified: grok/trunk/src/grok/admin/docgrok.txt
===================================================================
--- grok/trunk/src/grok/admin/docgrok.txt 2007-09-11 20:44:56 UTC (rev 79578)
+++ grok/trunk/src/grok/admin/docgrok.txt 2007-09-12 00:47:27 UTC (rev 79579)
@@ -124,11 +124,11 @@
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 ``handle()`` which delivers us a doctor, who
+the function ``docgrok_handle()`` which delivers us a doctor, who
can tell us more:
- >>> from grok.admin.docgrok import handle
- >>> thedoc = handle('grok.admin.docgrok')
+ >>> from grok.admin.docgrok import docgrok_handle
+ >>> thedoc = docgrok_handle('grok.admin.docgrok')
>>> thedoc
<grok.admin.docgrok.DocGrokModule ...>
@@ -139,7 +139,7 @@
We can, for example ask for a different doc like this:
- >>> thedoc = handle('grok.admin.docgrok.DocGrok')
+ >>> thedoc = docgrok_handle('grok.admin.docgrok.DocGrok')
>>> thedoc
<grok.admin.docgrok.DocGrokClass ...>
@@ -259,21 +259,17 @@
completely up to you.
To choose, which API elements your docgrok is able to handle, you have
-to define a handler function. This is what we want to do next.
+to define a handler class. This is what we want to do next.
-Create a handler
-++++++++++++++++
+Create a handler and register your new docgrok
+++++++++++++++++++++++++++++++++++++++++++++++
-XXX: to be written.
+Those steps are described in the ftests for the docgrok module in
+grok.ftests.admin.docgrok. There you can also find examples, how to
+create own docgrok documentation (see the lower parts of the file).
-Register your new DocGrok
-+++++++++++++++++++++++++
-
-XXX: to be written.
-
-
The Specialists
---------------
Modified: grok/trunk/src/grok/admin/tests/test_grokadmin.py
===================================================================
--- grok/trunk/src/grok/admin/tests/test_grokadmin.py 2007-09-11 20:44:56 UTC (rev 79578)
+++ grok/trunk/src/grok/admin/tests/test_grokadmin.py 2007-09-12 00:47:27 UTC (rev 79579)
@@ -60,7 +60,7 @@
for name in []:
suite.addTest(suiteFromPackage(name))
- for name in ['docgrok.txt','objectinfo.txt', 'utilities.py']:
+ for name in ['docgrok.txt', 'docgrok.py', 'objectinfo.txt', 'utilities.py']:
suite.addTest(doctest.DocFileSuite(name,
package='grok.admin',
globs=globs,
Modified: grok/trunk/src/grok/admin/view.py
===================================================================
--- grok/trunk/src/grok/admin/view.py 2007-09-11 20:44:56 UTC (rev 79578)
+++ grok/trunk/src/grok/admin/view.py 2007-09-12 00:47:27 UTC (rev 79579)
@@ -319,7 +319,7 @@
grok.require('grok.ManageApplications')
def getDocOfApp(self, apppath, headonly = True):
- doctor = docgrok.handle(apppath)
+ doctor = docgrok.docgrok_handle(apppath)
result = doctor.getDoc(headonly)
if result is None:
result = ""
Modified: grok/trunk/src/grok/ftests/admin/docgrok.py
===================================================================
--- grok/trunk/src/grok/ftests/admin/docgrok.py 2007-09-11 20:44:56 UTC (rev 79578)
+++ grok/trunk/src/grok/ftests/admin/docgrok.py 2007-09-12 00:47:27 UTC (rev 79579)
@@ -100,6 +100,134 @@
DocGrok for classes
-------------------
+Custom DocGroks
+---------------
+It is relatively easy to provide own DocGroks, that are displayed when
+browsing. You have to provide three parts:
+* a DocGrok class
+
+* a DocGrokHandlerClass
+
+* a view for the DocGrok class
+
+The latter is optional, but required, if you want to see your docgrok
+documentation in the docgrok browser.
+
+Basically, the DocGrok should provide and extract all information
+available for the kind of thing (a class, a function or whatever), you
+want to document. The handler class determines, whether a special
+dotted path denotes one of the things you want to document while the
+docgrok view finally renders all this (hopefully) nicer than it is
+done below.
+
+The *DocGrok* classes should be derived from
+grok.admin.docgrok.DocGrok, so that they automatically provide some
+useful information like the Python path of an object and similar.
+
+The *DocGrokHandlers* should be derived from
+grok.admin.docgrok.DocGrokHandler to be registered automatically on
+startup. It must provide a method ``getDoctor(dotted_path,
+obj=None)``, which returns a DocGrok object iff the given dotted_path
+denotes a thing of the appropriate type. In the example below we
+check, whether a given dotted path denotes a Mammoth class and in
+this case return a ``DocGrokForMammoths`` instance.
+
+The *DocGrokView* finally renders the information of the DocGrok
+instance, which is created, when a user requests information about a
+certain MammothManager.
+
+Example:
+--------
+
+We created the wonderfull Mammoth as below. Normally, when we watch
+the docgrok documentation of that class, we would be served the usual
+class documentation. But we want to give hints, that mammoths are
+really _large_, so we have to provide a special docgrok.
+
+All three of the above mentioned parts are defined below: a docgrok
+only for the Mammoth class (and its subclasses), a docgrok handler,
+which finds out, whether something is a Mammoth class and a view to
+display some valuable information about mammoths.
+
+Both, the handler and the view are registered automatically on
+startup, because they are subclassing DocGrokHandler (the handler) and
+grok.View (the view). There is nothing else, we have to do.
+
+To check this, we have a look at the docgrok class browser. First we
+have a look at the module, the class is contained in::
+
+ >>> browser.open("http://localhost/docgrok/grok/ftests/admin/docgrok")
+
+In this page, there should be a link available to our Mammoth class,
+as defined below::
+
+ >>> link = browser.getLink(url='/Mammoth')
+ >>> link.text
+ 'Mammoth'
+
+ >>> link.url
+ 'http://localhost/docgrok/grok/ftests/admin/docgrok/Mammoth'
+
+If we click on this link, we normally would get a usual class
+documentation page as generated by the docgrok for classes. But, while
+we have registered a special docgrok for Mammoth-things, we get::
+
+ >>> link.click()
+ >>> print browser.contents
+ An enormous beast.
+ Mammoths are really tall.
+ The size is: remarkable
+
+
"""
+import grok
+from grok.admin.docgrok import DocGrok, DocGrokHandler
+from grok.admin.view import DocGrokView
+
+class Mammoth(object):
+ """A (large) thing, we want to document later on.
+ """
+ pass
+
+class DocGrokForMammoths(DocGrok):
+ """Documentation for Mammoths.
+ """
+ def getSize(self):
+ return u"remarkable"
+
+class DocGrokForMammothsHandler(DocGrokHandler):
+ """A class for determining, whether a dotted path denotes a
+ Mammoth.
+ """
+ def getDoctor(self, dotted_path, ob=None):
+ """The only method required from a docgrok handler.
+ """
+ from zope.dottedname.resolve import resolve
+ try:
+ ob = resolve(dotted_path)
+ except ImportError:
+ return
+ try:
+ if not issubclass(ob, Mammoth):
+ return
+ except TypeError:
+ return
+ return DocGrokForMammoths(dotted_path)
+
+class DocGrokViewForMammoths(grok.View):
+ """A view, that should fit into the other docgrok documentation.
+ """
+ # We bind to the docgrok which provides us with information about
+ # the thing, we want to document.
+ grok.context(DocGrokForMammoths)
+ grok.name('index')
+
+ def render(self):
+ """To avoid an extra template, we provide a render method.
+ """
+ return (u"An enormous beast.\n"
+ u"Mammoths are really tall.\n"
+ u"The size is: %s " % self.context.getSize())
+
More information about the Checkins
mailing list