+++ /dev/null
-"""Self documenting XML-RPC Server.\r
-\r
-This module can be used to create XML-RPC servers that\r
-serve pydoc-style documentation in response to HTTP\r
-GET requests. This documentation is dynamically generated\r
-based on the functions and methods registered with the\r
-server.\r
-\r
-This module is built upon the pydoc and SimpleXMLRPCServer\r
-modules.\r
-"""\r
-\r
-import pydoc\r
-import inspect\r
-import re\r
-import sys\r
-\r
-from SimpleXMLRPCServer import (SimpleXMLRPCServer,\r
- SimpleXMLRPCRequestHandler,\r
- CGIXMLRPCRequestHandler,\r
- resolve_dotted_attribute)\r
-\r
-class ServerHTMLDoc(pydoc.HTMLDoc):\r
- """Class used to generate pydoc HTML document for a server"""\r
-\r
- def markup(self, text, escape=None, funcs={}, classes={}, methods={}):\r
- """Mark up some plain text, given a context of symbols to look for.\r
- Each context dictionary maps object names to anchor names."""\r
- escape = escape or self.escape\r
- results = []\r
- here = 0\r
-\r
- # XXX Note that this regular expression does not allow for the\r
- # hyperlinking of arbitrary strings being used as method\r
- # names. Only methods with names consisting of word characters\r
- # and '.'s are hyperlinked.\r
- pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'\r
- r'RFC[- ]?(\d+)|'\r
- r'PEP[- ]?(\d+)|'\r
- r'(self\.)?((?:\w|\.)+))\b')\r
- while 1:\r
- match = pattern.search(text, here)\r
- if not match: break\r
- start, end = match.span()\r
- results.append(escape(text[here:start]))\r
-\r
- all, scheme, rfc, pep, selfdot, name = match.groups()\r
- if scheme:\r
- url = escape(all).replace('"', '"')\r
- results.append('<a href="%s">%s</a>' % (url, url))\r
- elif rfc:\r
- url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)\r
- results.append('<a href="%s">%s</a>' % (url, escape(all)))\r
- elif pep:\r
- url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)\r
- results.append('<a href="%s">%s</a>' % (url, escape(all)))\r
- elif text[end:end+1] == '(':\r
- results.append(self.namelink(name, methods, funcs, classes))\r
- elif selfdot:\r
- results.append('self.<strong>%s</strong>' % name)\r
- else:\r
- results.append(self.namelink(name, classes))\r
- here = end\r
- results.append(escape(text[here:]))\r
- return ''.join(results)\r
-\r
- def docroutine(self, object, name, mod=None,\r
- funcs={}, classes={}, methods={}, cl=None):\r
- """Produce HTML documentation for a function or method object."""\r
-\r
- anchor = (cl and cl.__name__ or '') + '-' + name\r
- note = ''\r
-\r
- title = '<a name="%s"><strong>%s</strong></a>' % (\r
- self.escape(anchor), self.escape(name))\r
-\r
- if inspect.ismethod(object):\r
- args, varargs, varkw, defaults = inspect.getargspec(object.im_func)\r
- # exclude the argument bound to the instance, it will be\r
- # confusing to the non-Python user\r
- argspec = inspect.formatargspec (\r
- args[1:],\r
- varargs,\r
- varkw,\r
- defaults,\r
- formatvalue=self.formatvalue\r
- )\r
- elif inspect.isfunction(object):\r
- args, varargs, varkw, defaults = inspect.getargspec(object)\r
- argspec = inspect.formatargspec(\r
- args, varargs, varkw, defaults, formatvalue=self.formatvalue)\r
- else:\r
- argspec = '(...)'\r
-\r
- if isinstance(object, tuple):\r
- argspec = object[0] or argspec\r
- docstring = object[1] or ""\r
- else:\r
- docstring = pydoc.getdoc(object)\r
-\r
- decl = title + argspec + (note and self.grey(\r
- '<font face="helvetica, arial">%s</font>' % note))\r
-\r
- doc = self.markup(\r
- docstring, self.preformat, funcs, classes, methods)\r
- doc = doc and '<dd><tt>%s</tt></dd>' % doc\r
- return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)\r
-\r
- def docserver(self, server_name, package_documentation, methods):\r
- """Produce HTML documentation for an XML-RPC server."""\r
-\r
- fdict = {}\r
- for key, value in methods.items():\r
- fdict[key] = '#-' + key\r
- fdict[value] = fdict[key]\r
-\r
- server_name = self.escape(server_name)\r
- head = '<big><big><strong>%s</strong></big></big>' % server_name\r
- result = self.heading(head, '#ffffff', '#7799ee')\r
-\r
- doc = self.markup(package_documentation, self.preformat, fdict)\r
- doc = doc and '<tt>%s</tt>' % doc\r
- result = result + '<p>%s</p>\n' % doc\r
-\r
- contents = []\r
- method_items = sorted(methods.items())\r
- for key, value in method_items:\r
- contents.append(self.docroutine(value, key, funcs=fdict))\r
- result = result + self.bigsection(\r
- 'Methods', '#ffffff', '#eeaa77', pydoc.join(contents))\r
-\r
- return result\r
-\r
-class XMLRPCDocGenerator:\r
- """Generates documentation for an XML-RPC server.\r
-\r
- This class is designed as mix-in and should not\r
- be constructed directly.\r
- """\r
-\r
- def __init__(self):\r
- # setup variables used for HTML documentation\r
- self.server_name = 'XML-RPC Server Documentation'\r
- self.server_documentation = \\r
- "This server exports the following methods through the XML-RPC "\\r
- "protocol."\r
- self.server_title = 'XML-RPC Server Documentation'\r
-\r
- def set_server_title(self, server_title):\r
- """Set the HTML title of the generated server documentation"""\r
-\r
- self.server_title = server_title\r
-\r
- def set_server_name(self, server_name):\r
- """Set the name of the generated HTML server documentation"""\r
-\r
- self.server_name = server_name\r
-\r
- def set_server_documentation(self, server_documentation):\r
- """Set the documentation string for the entire server."""\r
-\r
- self.server_documentation = server_documentation\r
-\r
- def generate_html_documentation(self):\r
- """generate_html_documentation() => html documentation for the server\r
-\r
- Generates HTML documentation for the server using introspection for\r
- installed functions and instances that do not implement the\r
- _dispatch method. Alternatively, instances can choose to implement\r
- the _get_method_argstring(method_name) method to provide the\r
- argument string used in the documentation and the\r
- _methodHelp(method_name) method to provide the help text used\r
- in the documentation."""\r
-\r
- methods = {}\r
-\r
- for method_name in self.system_listMethods():\r
- if method_name in self.funcs:\r
- method = self.funcs[method_name]\r
- elif self.instance is not None:\r
- method_info = [None, None] # argspec, documentation\r
- if hasattr(self.instance, '_get_method_argstring'):\r
- method_info[0] = self.instance._get_method_argstring(method_name)\r
- if hasattr(self.instance, '_methodHelp'):\r
- method_info[1] = self.instance._methodHelp(method_name)\r
-\r
- method_info = tuple(method_info)\r
- if method_info != (None, None):\r
- method = method_info\r
- elif not hasattr(self.instance, '_dispatch'):\r
- try:\r
- method = resolve_dotted_attribute(\r
- self.instance,\r
- method_name\r
- )\r
- except AttributeError:\r
- method = method_info\r
- else:\r
- method = method_info\r
- else:\r
- assert 0, "Could not find method in self.functions and no "\\r
- "instance installed"\r
-\r
- methods[method_name] = method\r
-\r
- documenter = ServerHTMLDoc()\r
- documentation = documenter.docserver(\r
- self.server_name,\r
- self.server_documentation,\r
- methods\r
- )\r
-\r
- return documenter.page(self.server_title, documentation)\r
-\r
-class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):\r
- """XML-RPC and documentation request handler class.\r
-\r
- Handles all HTTP POST requests and attempts to decode them as\r
- XML-RPC requests.\r
-\r
- Handles all HTTP GET requests and interprets them as requests\r
- for documentation.\r
- """\r
-\r
- def do_GET(self):\r
- """Handles the HTTP GET request.\r
-\r
- Interpret all HTTP GET requests as requests for server\r
- documentation.\r
- """\r
- # Check that the path is legal\r
- if not self.is_rpc_path_valid():\r
- self.report_404()\r
- return\r
-\r
- response = self.server.generate_html_documentation()\r
- self.send_response(200)\r
- self.send_header("Content-type", "text/html")\r
- self.send_header("Content-length", str(len(response)))\r
- self.end_headers()\r
- self.wfile.write(response)\r
-\r
-class DocXMLRPCServer( SimpleXMLRPCServer,\r
- XMLRPCDocGenerator):\r
- """XML-RPC and HTML documentation server.\r
-\r
- Adds the ability to serve server documentation to the capabilities\r
- of SimpleXMLRPCServer.\r
- """\r
-\r
- def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,\r
- logRequests=1, allow_none=False, encoding=None,\r
- bind_and_activate=True):\r
- SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,\r
- allow_none, encoding, bind_and_activate)\r
- XMLRPCDocGenerator.__init__(self)\r
-\r
-class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,\r
- XMLRPCDocGenerator):\r
- """Handler for XML-RPC data and documentation requests passed through\r
- CGI"""\r
-\r
- def handle_get(self):\r
- """Handles the HTTP GET request.\r
-\r
- Interpret all HTTP GET requests as requests for server\r
- documentation.\r
- """\r
-\r
- response = self.generate_html_documentation()\r
-\r
- print 'Content-Type: text/html'\r
- print 'Content-Length: %d' % len(response)\r
- print\r
- sys.stdout.write(response)\r
-\r
- def __init__(self):\r
- CGIXMLRPCRequestHandler.__init__(self)\r
- XMLRPCDocGenerator.__init__(self)\r