[Zope3-checkins] CVS: Zope3/src/zope/products/apidoc - MAINTAINER.txt:1.1 README.txt:1.1 TODO.txt:1.1 __init__.py:1.1 configure.zcml:1.1 interfaces.py:1.1 tests.py:1.1 utilities.py:1.1 version.txt:1.1

Stephan Richter srichter at cosmos.phy.tufts.edu
Thu Jan 29 12:51:13 EST 2004


Update of /cvs-repository/Zope3/src/zope/products/apidoc
In directory cvs.zope.org:/tmp/cvs-serv11915/apidoc

Added Files:
	MAINTAINER.txt README.txt TODO.txt __init__.py configure.zcml 
	interfaces.py tests.py utilities.py version.txt 
Log Message:
Here comes the new Zope 3 API Documentation tool. You can access it via

  http://localhost:8080/++apidoc++/

There is really not much more to say here. Check it out and let me know what
you think.


=== Added File Zope3/src/zope/products/apidoc/MAINTAINER.txt ===
Stephan Richter

  Email: stephan.richter at tufts.edu

  IRC nick: srichter

=== Added File Zope3/src/zope/products/apidoc/README.txt ===
Zope 3 API Documentation
========================

This Zope 3 product provides a fully dynamic API documentation of Zope 3 and
registered add-on components. The product is very extensible and can be easily
extended by implementing new modules.


Installation
------------

  1. In your 'products.zcml' file make sure that

     (a) the static tree product is registered using::

         <include package="zope.products.apidoc"/>

     (b) you register this product using::

         <include package="zope.products.apidoc"/>

  2. Restart Zope 3.

  3. You can now access the documentation via the new namespace "++apidoc++",
     like in::

     http://localhost:8080/++apidoc++/


Developing a Module
-------------------

  1. Implement a class that realizes the 'IDocumentationModule' interface.

  2. Register this class as a utility using something like this::

    <utility
        provides="zope.products.apidoc.interfaces.IDocumentationModule"
        factory=".examplemodule.ExampleModule"
        name="Example" />

  3. Take care of security by allowing at least 'IDocumentationModule':

    <class class=".ExampleModule">
      <allow interface="zope.products.apidoc.interfaces.IDocumentationModule" />
    </class>

  4. Provide a browser view called 'menu.html'.

  5. Provide another view, usually 'index.html', that can show the details for
     the various menu items.

  Note: There are several modules that come with the product. Just look in
  them for some guidance.

=== Added File Zope3/src/zope/products/apidoc/TODO.txt ===
TO DO
=====
  
  - better README.txt

  - Refactor some of the views, so that templates can be reused

  - move out StructuredText (???)

=== Added File Zope3/src/zope/products/apidoc/__init__.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Zope 3 API Documentation

$Id: __init__.py,v 1.1 2004/01/29 17:51:12 srichter Exp $
"""
from interfaces import IDocumentationModule
from zope.app import zapi
from zope.app.interfaces.container import IReadContainer
from zope.app.location import locate
from zope.app.interfaces.location import ILocation
from zope.interface import implements
from zope.products.apidoc.utilities import ReadContainerBase

class APIDocumentation(ReadContainerBase):
    r"""Represent the complete API Documentation.

    This documentation is implemented using a simply 'IReadContainer'. The
    items of the container are all registered utilities for
    IDocumentationModule.

    Demonstration::

      >>> from zope.products.apidoc import tests
      >>> tests.setUp()

      >>> doc = APIDocumentation(None, '++apidoc++')
      >>> doc.get('ZCML').title
      'ZCML Reference'

      >>> doc.get('Documentation') is None
      True

      >>> print '\n'.join([id for id, mod in doc.items()])
      Interface
      ZCML
      
      >>> tests.tearDown()
    """

    implements(ILocation)

    def __init__(self, parent, name):
        self.__parent__ = parent
        self.__name__ = name
    
    def get(self, key, default=None):
        """See zope.app.interfaces.container.IReadContainer"""
        utility = zapi.queryUtility(self, IDocumentationModule, default, key)
        if utility != default:
            locate(utility, self, key)
        return utility

    def items(self):
        """See zope.app.interfaces.container.IReadContainer"""
        items = zapi.getUtilitiesFor(self, IDocumentationModule)
        items.sort()
        utils = []
        for key, value in items:
            locate(value, self, key)
            utils.append((key, value))
        return utils
        

def handleNamespace(name, parameters, pname, ob, request):
    """Used to traverse to an API Documentation."""
    return APIDocumentation(ob, pname)


=== Added File Zope3/src/zope/products/apidoc/configure.zcml ===
<configure
  xmlns="http://namespaces.zope.org/zope"
  i18n_domain="api_doc">

  <class class=".APIDocumentation">
    <allow interface="zope.app.interfaces.container.IReadContainer" />
  </class>

  <traversalNamespace
      name="apidoc"
      handler=".handleNamespace" />

  <include package=".browser" />

  <!-- API Documentation Modules -->
  <include package=".classmodule" />
  <include package=".ifacemodule" />
  <include package=".servicemodule" />
  <include package=".utilitymodule" />
  <include package=".viewmodule" />
  <include package=".zcmlmodule" />

</configure>


=== Added File Zope3/src/zope/products/apidoc/interfaces.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Generic API Documentation Interfaces

$Id: interfaces.py,v 1.1 2004/01/29 17:51:12 srichter Exp $
"""
from zope.interface import Interface
from zope.schema import TextLine, Text

class IDocumentationModule(Interface):
    """Zope 3 API Documentation Module

    A documentation module contains the documentation for one specific aspect
    of the framework, such as ZCML directives or interfaces.

    The interface is used to register module as utilitites.
    """

    title = TextLine(
        title=u"Title",
        description=u"The title of the documentation module.",
        required=True)

    description = Text(
        title=u"Module Description",
        description=u"This text describes the functionality of the module.",
        required=True)


=== Added File Zope3/src/zope/products/apidoc/tests.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Tests for the Interface Documentation Module

$Id: tests.py,v 1.1 2004/01/29 17:51:12 srichter Exp $
"""
import unittest
from zope.app import zapi
from zope.app.interfaces.traversing import IContainmentRoot
from zope.app.location import LocationProxy
from zope.app.tests import placelesssetup
from zope.interface import implements
from zope.products.apidoc.interfaces import IDocumentationModule
from zope.products.apidoc.ifacemodule import InterfaceModule
from zope.products.apidoc.zcmlmodule import ZCMLModule
from zope.testing.doctestunit import DocTestSuite


def setUp():
    placelesssetup.setUp()
    service = zapi.getService(None, 'Utilities')
    service.provideUtility(IDocumentationModule, InterfaceModule(), 'Interface')
    service.provideUtility(IDocumentationModule, ZCMLModule(), 'ZCML')

def tearDown():
    placelesssetup.tearDown()


class Root:
    implements(IContainmentRoot)

    __parent__ = None
    __name__ = ''

def rootLocation(obj, name):
    return LocationProxy(obj, Root(), name)
    
def test_suite():
    return unittest.TestSuite((
        DocTestSuite('zope.products.apidoc'),
        DocTestSuite('zope.products.apidoc.utilities'),
        ))

if __name__ == '__main__':
    unittest.main()


=== Added File Zope3/src/zope/products/apidoc/utilities.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Utilties to make the life of Documentation Modules easier.

$Id: utilities.py,v 1.1 2004/01/29 17:51:12 srichter Exp $
"""
import re
import types
import inspect
import docutils
from zope.app.interfaces.container import IReadContainer
from zope.app.renderer.rest import Writer
from zope.interface import implements, implementedBy
from zope.products.apidoc.StructuredText import HTML
from zope.security.checker import getCheckerForInstancesOf
from zope.security.interfaces import INameBasedChecker

_remove_html_overhead = re.compile(
    r'(?sm)^<html.*<body.*?>\n(.*)</body>\n</html>\n')

_marker = object()

class ReadContainerBase(object):
    """Base for IReadContainer objects.

    This is a base class that minimizes the implementation of IReadContainers
    to two methods, get() and items(), since the other methods can be
    implemented using these two.

    Demonstration::

      Make a sample implementation first

      >>> class Container(ReadContainerBase):
      ...     def get(self, key, default=None):
      ...         return {'a': 1, 'b': 2}.get(key, default)
      ...     def items(self):
      ...         return [('a', 1), ('b', 2)]

      >>> container = Container()

      Now we can use the methods

      >>> container.get('a')
      1
      >>> container.get('c') is None
      True
      >>> container['b']
      2

      >>> container.items()
      [('a', 1), ('b', 2)]
      >>> container.keys()
      ['a', 'b']
      >>> container.values()
      [1, 2]

      >>> 1 in container
      True
      >>> len(container)
      2
    """

    implements(IReadContainer)

    def get(self, key, default=None):
        raise NotImplemented
    
    def items(self):
        raise NotImplemented

    def __getitem__(self, key):
        default = object()
        obj = self.get(key, default)
        if obj is default:
            raise KeyError, key
        return obj

    def __contains__(self, key):
        return self.get(key) is None

    def keys(self):
        return map(lambda x: x[0], self.items())

    def __iter__(self):
        return self.values().__iter__()
        
    def values(self):
        return map(lambda x: x[1], self.items())

    def __len__(self):
        return len(self.items())


def getPythonPath(obj):
    """Return the path of the object in standard Python notation.

    This method makes only sense for classes and interfaces. Instances do not
    have a '__name__' attribute, so we would expect them to fail.

    Example::

      >>> from zope.interface import Interface
      >>> class ISample(Interface):
      ...     pass
      >>> class Sample(object):
      ...     pass

      >>> getPythonPath(ISample)
      'zope.products.apidoc.utilities.ISample'

      >>> getPythonPath(Sample)
      'zope.products.apidoc.utilities.Sample'

      >>> try:
      ...   getPythonPath(Sample())
      ... except AttributeError:
      ...   print 'failed'
      failed
    """
    if obj is None:
        return None
    module = obj.__module__
    return '%s.%s' %(module, obj.__name__)


def stx2html(text, level=1):
    r"""Convert STX text to HTML.

    Example::

      >>> text = 'Header\n\n  Normal text goes here.'

      >>> stx2html(text)
      '<h1>Header</h1>\n<p>  Normal text goes here.</p>\n'

      >>> stx2html(text, level=3)
      '<h3>Header</h3>\n<p>  Normal text goes here.</p>\n'

      >>> stx2html(text, level=6)
      '<h6>Header</h6>\n<p>  Normal text goes here.</p>\n'
    """
    html = HTML(text, level)
    html = _remove_html_overhead.sub(r'\1', html)
    return html


def getPermissionIds(name, checker=_marker, klass=_marker):
    """Get the permissions of an attribute.

    Either the klass or the checker must be specified. If the class is
    specified, then the checker for it is looked up. Furthermore, this
    function only works with 'INameBasedChecker' checkers. If another checker
    is found, 'None' is returned for the permissions.

    Example::

      We first define the class and then the checker for it

      >>> from zope.security.checker import Checker, defineChecker
      
      >>> class Sample(object):
      ...     attr = 'value'

      >>> class Sample2(object):
      ...      pass

      >>> checker = Checker({'attr': 'zope.Read'}.get,
      ...                   {'attr': 'zope.Write'}.get) 
      >>> defineChecker(Sample, checker)

      Now let's see how this function works

      >>> entries = getPermissionIds('attr', klass=Sample)
      >>> entries['read_perm']
      'zope.Read'
      >>> entries['write_perm']
      'zope.Write'

      >>> entries = getPermissionIds('attr', getCheckerForInstancesOf(Sample))
      >>> entries['read_perm']
      'zope.Read'
      >>> entries['write_perm']
      'zope.Write'

      >>> entries = getPermissionIds('attr2', klass=Sample)
      >>> print entries['read_perm']
      N/A
      >>> print entries['write_perm']
      N/A

      >>> entries = getPermissionIds('attr', klass=Sample2)
      >>> entries['read_perm'] is None
      True
      >>> print entries['write_perm'] is None
      True
    """
    assert (klass is _marker) != (checker is _marker)
    entry = {}
    
    if klass is not _marker:
        checker = getCheckerForInstancesOf(klass)
    
    if checker is not None and \
           INameBasedChecker.isImplementedBy(checker):
        entry['read_perm'] = checker.permission_id(name) or 'N/A'
        entry['write_perm'] = checker.setattr_permission_id(name) or 'N/A'
    else:
        entry['read_perm'] = entry['write_perm'] = None 

    return entry


def getFunctionSignature(func):
    """Return the signature of a function or method.

    The 'func' argument *must* be a generic function or a method of a class. 

    Examples::

      >>> def func(attr, attr2=None):
      ...     pass
      >>> print getFunctionSignature(func)
      (attr, attr2=None)

      >>> def func(attr, **kw):
      ...     pass
      >>> print getFunctionSignature(func)
      (attr, **kw)

      >>> def func(attr, attr2=None, **kw):
      ...     pass      
      >>> print getFunctionSignature(func)
      (attr, attr2=None, **kw)

      >>> def func(*args, **kw):
      ...     pass
      >>> print getFunctionSignature(func)
      (*args, **kw)

      >>> def func(**kw):
      ...     pass
      >>> print getFunctionSignature(func)
      (**kw)

      >>> class Klass(object):
      ...     def func(self, attr):
      ...         pass
      
      >>> print getFunctionSignature(Klass.func)
      (attr)

      >>> class Klass(object):
      ...     def func(self, attr, *args, **kw):
      ...         pass
      
      >>> print getFunctionSignature(Klass.func)
      (attr, *args, **kw)
      
      >>> try:
      ...     getFunctionSignature('func')
      ... except AssertionError:
      ...     print 'Argument not a function or method.'
      Argument not a function or method.
    """
    assert type(func) in (types.FunctionType, types.MethodType)
    
    args, varargs, varkw, default = inspect.getargspec(func)
    placeholder = object()
    sig = '('
    # By filling up the default tuple, we now have equal indeces for args and
    # default.
    if default is not None:
        default = (placeholder,)*(len(args)-len(default)) + default
    else:
        default = (placeholder,)*len(args)

    str_args = []

    for i in range(len(args)):
        # Neglect self, since it is always there and not part of the signature.
        # This way the implementation and interface signatures should match.
        if args[i] == 'self' and type(func) == types.MethodType:
            continue
        if default[i] is placeholder:
            str_args.append(args[i])
        else:
            str_args.append(args[i] + '=' + default[i].__repr__())

    if varargs:
        str_args.append('*'+varargs)
    if varkw:
        str_args.append('**'+varkw)

    sig += ', '.join(str_args)
    return sig + ')'


def getPublicAttributes(obj):
    """Return a list of public attribute names.

    This excludes any attribute starting with '_'. The 'obj' argument can be
    either a classic class, type or instance of the previous two. Note that
    the term "attributes" here includes methods and properties.

    Examples::

      >>> class Sample(object):
      ...     attr = None
      ...     def __str__(self):
      ...         return ''
      ...     def func(self):
      ...         pass
      ...     def _getAttr(self):
      ...         return self.attr
      ...     attr2 = property(_getAttr)
      >>> class Sample2:
      ...     attr = None
      >>> class Sample3(Sample):
      ...     attr3 = None
      
      >>> attrs = getPublicAttributes(Sample)
      >>> attrs.sort()
      >>> print attrs
      ['attr', 'attr2', 'func']
      
      >>> attrs = getPublicAttributes(Sample())
      >>> attrs.sort()
      >>> print attrs
      ['attr', 'attr2', 'func']
      
      >>> attrs = getPublicAttributes(Sample2)
      >>> attrs.sort()
      >>> print attrs
      ['attr']
      
      >>> attrs = getPublicAttributes(Sample3)
      >>> attrs.sort()
      >>> print attrs
      ['attr', 'attr2', 'attr3', 'func']
    """
    attrs = []
    for attr in dir(obj):
        if attr.startswith('_'):
            continue
        else:
            attrs.append(attr)
    return attrs

def getInterfaceForAttribute(name, interfaces=_marker, klass=_marker,
                             asPath=True):
    """Determine the interface in which an attribute is defined.

    This function is nice, if you have an attribute name which you retrieved
    from a class and want to know which interface requires it to be there.

    Either 'interfaces' or 'klass' must be specified. If 'interfaces' is not
    specified, the 'klass' is used to retrieve a list of
    interfaces. 'interfaces' must be iteratable.

    'asPath' specifies whether the dotted name of the interface or the
    interface object is returned.

    If no match is found, 'None' is returned.

    Example::

      >>> from zope.interface import Interface, Attribute
      >>> class I1(Interface):
      ...     attr = Attribute('attr')
      >>> class I2(I1):
      ...     def getAttr():
      ...         '''get attr'''
      >>> class Sample(object):
      ...     implements(I2)

      >>> getInterfaceForAttribute('attr', (I1, I2), asPath=False).getName()
      'I1'
      >>> getInterfaceForAttribute('getAttr', (I1, I2), asPath=False).getName()
      'I2'
      >>> getInterfaceForAttribute('attr', klass=Sample, asPath=False).getName()
      'I1'
      >>> getInterfaceForAttribute(
      ...     'getAttr', klass=Sample, asPath=False).getName()
      'I2'

      >>> getInterfaceForAttribute('attr', (I1, I2))
      'zope.products.apidoc.utilities.I1'

      >>> getInterfaceForAttribute('attr2', (I1, I2)) is None
      True
      >>> getInterfaceForAttribute('attr2', klass=Sample) is None
      True

      >>> try:
      ...     getInterfaceForAttribute('getAttr')
      ... except AssertionError:
      ...     print 'need to specify the interfaces or a klass'
      need to specify the interfaces or a klass

    """
    assert (interfaces is _marker) != (klass is _marker)

    if interfaces is _marker:
        direct_interfaces = list(implementedBy(klass))
        interfaces = {}
        for interface in direct_interfaces:
            interfaces[interface] = 1
            for base in interface.getBases():
                interfaces[base] = 1
        interfaces = interfaces.keys()
        
    for interface in interfaces:
        if name in interface.names():                
            if asPath:
                return getPythonPath(interface)
            return interface

    return None


=== Added File Zope3/src/zope/products/apidoc/version.txt ===
apidoc 0.1



More information about the Zope3-Checkins mailing list