Hi,

I really like the fact that controllers are objects and URL are
translatable into a python path.
I'm also keen of pydoc and the docstrings stuff.
So I developed a small controller and a command line client to
automatically expose documentation from a controller.
Please give me feedback.

Controller Usage:
-----------------
    an selfdocumentor for controllers

    present the prototypes and documentation of all exposed members of a
controller
    use it this way:

    >>> import selfdoc
    >>> class Foo(selfdoc.SelfDoc):
    ...       '''
    ...       Foo bar controller
    ...       '''
    ...
    ...       @turbogears.expose()
    ...       def method(self, foo, bar=None):
    ...           '''
    ...           foo bar method  
    ...           '''
    ...           return ''
    >>>

    or alternatively:

    >>> import selfdoc
    >>> class Foo(turbogears.controllers.Controller):
    ...       '''
    ...       Foo bar controller
    ...       '''
    ...
    ...       def __init__(self):
    ...           self.doc = selfdoc.SelfDoc(self)
    >>>

    then access your doc at
    http://yoursite/path/to/foo/ or
http://yoursite/path/to/foo/__selfdoc__

Command line client usage:
--------------------------
   
    Command-line client tool for SelfDoc.

    $ tg-pydoc http://localhost:8080/some/path
    [...]

    $ tg-pydoc http://localhost:8080 -m some.path
    [...]

    $ tg-pydoc http://localhost:8080/some/path --html
    <p>[...]

    $ echo "http://localhost:8080"; > .selfdoc
    $ tg-pydoc -m some.path
    [...]




--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"TurboGears" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/turbogears
-~----------~----~----~----~------~----~------~--~---
#!/usr/bin/env python
# -*- coding: utf-8 -*-
## SelfDoc client
## 
## Copyright (C) 2006 Bader Ladjemi <[EMAIL PROTECTED]>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; see the file COPYING. If not, write to the
## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
## USA.
"""
Command-line client tool for SelfDoc.

$ tg-pydoc http://localhost:8080/some/path
[...]

$ tg-pydoc http://localhost:8080 -m some.path
[...]

$ tg-pydoc http://localhost:8080/some/path --html
<p>[...]

$ echo "http://localhost:8080"; > .selfdoc
$ tg-pydoc -m some.path
[...]

"""
__author__ = "Bader Ladjemi <[EMAIL PROTECTED]>"
__date__ = "8 July 2006"
__version__ = "0.1"
__copyright__ = "GNU GPLv2"

import pydoc
import urllib
import urllib2
import sys
import os
import logging
from optparse import OptionParser
import simplejson

HTTP_PROXY = os.getenv('http_proxy', None)
SELFDOC_FILE = '.selfdoc'
SELFDOC_USER_FILE = os.path.join(os.getenv('HOME'), SELFDOC_FILE)

if HTTP_PROXY is not None:
    proxy_handler = urllib2.ProxyHandler({'http': HTTP_PROXY})
    urlopen = urllib2.build_opener(proxy_handler)
    urllib2.install_opener(urlopen)

class Doc:
    """
    Fetch doc from an url
    """
    output_format = 'plain'
    methods = {
        'doc' : "__selfdoc__",
        'doc_member': "__selfdoc__member__",
        'dir' : "__dir__",
        'controllers' : "__dir__controllers__",
        'exposed' : "__dir__exposed__",
        }
    ignored = ('tg_flash', )
    
    def __init__(self, url):
        """
        Fetch doc from an url
        """             
        self.name = None

        if url[-1] != '/':
            #name or an incomplete url
            parent, name = url_parent(url)
            if parent is not None:
                doc = Doc(parent)
                if name in doc.exposed():
                    self.name = name
                    url = parent
                else:
                    url += '/'
            else:
                url += '/'

        self.url = url
        self.text = None
        self.resource = None

        
    def fetch(self, name=None):
        """
        Fetch documentation
        """
        headers = {'Accept': "text/%s" % self.output_format}

        name = name or self.name
        
        if name is None:
            url = '%s%s' % (self.url, self.methods['doc'])
        else:
            params = urllib.urlencode({'name': name})
            url = '%s%s?%s' % (self.url, self.methods['doc_member'], params)
            

        req = urllib2.Request(url=url, headers=headers)
        try:
            self.resource = urllib2.urlopen(req)
        except urllib2.HTTPError, err:
            logging.fatal(err)
            sys.exit(1)
        except urllib2.URLError, err:
            logging.fatal(err.reason[1])
            sys.exit(2)
        
        self.text = self.resource.read()

    def exposed(self):
        """
        Tuple of exposed methods
        """
        return self.fetch_object('exposed')

    def controllers(self):
        """
        Tuple of controllers
        """
        return self.fetch_object('controllers')

    def fetch_object(self, method):
        """
        Fetch object by method
        """
        try:
            res = urllib2.urlopen('%s%s' % (self.url, self.methods[method]))
            raw_data = simplejson.loads(res.read()).keys()
            exposed = tuple(key for key in raw_data if key not in self.ignored)
        except urllib2.URLError:
            exposed = ()
        return exposed
        
    def __str__(self):
        """
        Returns documentation
        """
        if self.text is None:
            self.fetch()
        return self.text

def url_parent(url):
    """
    tuple of its parent url and last
    """
    items = url.rsplit('/', 1)
    parent = items[0]+'/'
    last = items[-1]
    if parent == 'http:/':
        parent = None
        last = None
        
    return parent, last

def ns_to_path(url, namespace):
    """
    translate a namespace into a path string
    """
    if url[-1] != '/':
        url += '/'
    return url+namespace.replace('.', '/')


def command_line_interface():
    """
    Command Line Interface for selfdoc client
    """
    usage = "usage: %prog [options] url"
    parser = OptionParser(usage, version=__version__)
    
    parser.add_option("-m", "--module",
                      dest="module",
                      help="fetch documentation for this module")

    parser.add_option("--html", action="store_true", dest="html",
                      help="fetch html output")

    (options, args) = parser.parse_args()

    if len(args) != 1:
        if os.path.exists(SELFDOC_FILE):
            url = file(SELFDOC_FILE).read()
        elif os.path.exists(SELFDOC_USER_FILE):
            url = file(SELFDOC_USER_FILE).read()
        else:
            parser.error("Please provide an url\
            or write it in .selfdoc or ~/.selfdoc")
    else:
        url = args[0]

    if options.module:
        url = ns_to_path(url, options.module)

    if options.html:
        Doc.output_format = 'html'

    doc = Doc(url)

    output = str(doc)
    if not options.html:
        pydoc.pager(output)
    else:
        sys.stdout.write(output)

if __name__ == '__main__':
    command_line_interface()
## Selfdoc
## Copyright (C) 2006 <[EMAIL PROTECTED]>
## 
## This library is free software; you can redistribute it and/or
## modify it under the terms of the GNU Lesser General Public
## License as published by the Free Software Foundation; either
## version 2.1 of the License, or (at your option) any later version.
## 
## This library is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## Lesser General Public License for more details.
## 
## You should have received a copy of the GNU Lesser General Public
## License along with this library; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
## 02110-1301  USA
"""
SelfDoc, documentation controller for TurboGears

REST compliant.
"""
import pydoc
import inspect
import turbogears
import cherrypy

__author__ = "Bader Ladjemi <[EMAIL PROTECTED]>"
__date__ = "17 June 2006"
__version__ = "0.1"
__copyright__ = "GNU LGPL"


class _HTMLDoc(pydoc.HTMLDoc):
    """
    SelfDoc HTMLDoc

    prevents htmldoc from making links
    """
    
    def namelink(self, name, *dicts):
        """Make a link for an identifier, given name-to-URL mappings."""
        return name

    def modulelink(self, object):
        """Make a link for a module."""
        return object.__name__

    def modpkglink(self, (name, path, ispackage, shadowed)):
        """Make a link for a module or package to display in an index."""
        if shadowed:
            return self.grey(name)
        return name

    def classlink(self, object, modname):
        """Make a link for a class."""
        name, module = object.__name__, pydoc.sys.modules.get(object.__module__)
        return pydoc.classname(object, modname)
    
helper = {'html':  _HTMLDoc(), 'plain': pydoc.text}

def isexposed(member):
    """
    Returns True if a method is exposed else False
    """
    return getattr(member, 'exposed', False)

def iscontroller(member):
    """
    Returns True if an object is a controller else False
    """
    return issubclass(member.__class__, turbogears.controllers.Controller)

class SelfDoc(turbogears.controllers.Controller):
    """
    an selfdocumentor for controllers

    present the prototypes and documentation of all exposed members of a controller
    use it this way:

    >>> import selfdoc
    >>> class Foo(selfdoc.SelfDoc):
    ...       '''
    ...       Foo bar controller
    ...       '''
    ...
    ...       @turbogears.expose()
    ...       def method(self, foo, bar=None):
    ...           '''
    ...           foo bar method  
    ...           '''
    ...           return ''
    >>>

    or alternatively:

    >>> import selfdoc
    >>> class Foo(turbogears.controllers.Controller):
    ...       '''
    ...       Foo bar controller
    ...       '''
    ...
    ...       def __init__(self):
    ...           self.doc = selfdoc.SelfDoc(self)
    >>>

    then access your doc at
    http://yoursite/path/to/foo/ or http://yoursite/path/to/foo/__selfdoc__
    """
    selfdoc_methods = ('__selfdoc__',
                       '__selfdoc__member__',
                       '__dir__controllers__',
                       '__dir__exposed__',
                       '__dir__'
                       )
    
    def __init__(self, klass, selfdoc=True):
        """
        @param klass class to document
        @param selfdoc authorize documentation of the selfdoc class
        """
        if not inspect.isclass(klass):
            klass = klass.__class__
        self.selfdoc_klass = klass
        self.selfdoc_methods = self.selfdoc_methods+('__dir__',)
        self.selfdoc = selfdoc

    @turbogears.expose()
    def __selfdoc__(self, output_format='plain', selfdoc=False):
        """
        Document automically this class

        present the prototypes and documentation of all exposed members of this controller
        """
        ignored_members = getattr(self, 'selfdoc_methods', ())

        if cherrypy.request.headers.has_key('Accept'):
            if 'text/html' in cherrypy.request.headers['Accept'].split(';'):
                output_format = 'html'

        if selfdoc and getattr(self, 'selfdoc', False):
            klass = self.__class__
        elif hasattr(self, 'selfdoc_klass'):
            klass = self.selfdoc_klass
        else:
            klass = self.__class__

        class Klass:
            pass
        
        for member_name in dir(klass):
            member = getattr(klass, member_name)

            if member_name not in ignored_members:
                if isexposed(member) and member_name:
                    setattr(Klass, member_name, member.__composition__[0])  
                elif iscontroller(member) and member_name:
                    setattr(Klass, member_name, member)
                
        Klass.__name__ = klass.__name__
        Klass.__doc__ = klass.__doc__

        cherrypy.response.headers['Content-type'] = 'text/%s' % output_format

        return helper[output_format].docclass(Klass)

    @turbogears.expose()
    def __selfdoc__member__(self, name, output_format='plain'):
        """
        Returns documentation for a specific member
        """
        if cherrypy.request.headers.has_key('Accept'):
            if 'text/html' in cherrypy.request.headers['Accept'].split(';'):
                output_format = 'html'

        if hasattr(self, 'selfdoc_klass'):
            klass = self.selfdoc_klass
        else:
            klass = self.__class__

        member = getattr(klass, name).__composition__[0]
        
        cherrypy.response.headers['Content-type'] = 'text/%s' % output_format

        return helper[output_format].docroutine(member)

    @turbogears.expose(format='json')
    def __dir__controllers__(self):
        """
        Returns a dictionary of controllers indexed by their name,
        values are their representation.
        """
        ignored_members = self.selfdoc_methods
        controllers = inspect.getmembers(self, (lambda member: iscontroller(member) and member.__name__ not in ignored_members))
        return dict([(member_name, str(member)) for member_name, method in controllers])
    
    @turbogears.expose(format='json')
    def __dir__exposed__(self):
        """
        Returns a dictionary of exposed methods indexed by their name,
        values are their representation.
        """
        ignored_members = self.selfdoc_methods
        exposed =  inspect.getmembers(self, (lambda method: isexposed(method) and method.__name__ not in ignored_members))
        return dict([(method_name, str(method)) for method_name, method in exposed])

    @turbogears.expose(format='json')
    def __dir__(self):
        """
        Returns a dictionary of controllers and exposed methods indexed by their name,
        values are their representation.
        Same as using both __dir__controllers__() and ___dir__exposed__()
        """
        ignored_members = self.selfdoc_methods
        is_member = (lambda member: (iscontroller(member) or isexposed(member)) and member.__name__ not in ignored_members)
        members = inspect.getmembers(self, is_member)
        return dict([(member_name, str(member)) for member_name, member in members])
                                     
        
    @turbogears.expose()
    def index(self):
        return self.__selfdoc__()

Reply via email to