]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/DocXMLRPCServer.py
hgwebfs: remove prefixing // from file path
[plan9front.git] / sys / lib / python / DocXMLRPCServer.py
1 """Self documenting XML-RPC Server.
2
3 This module can be used to create XML-RPC servers that
4 serve pydoc-style documentation in response to HTTP
5 GET requests. This documentation is dynamically generated
6 based on the functions and methods registered with the
7 server.
8
9 This module is built upon the pydoc and SimpleXMLRPCServer
10 modules.
11 """
12
13 import pydoc
14 import inspect
15 import re
16 import sys
17
18 from SimpleXMLRPCServer import (SimpleXMLRPCServer,
19             SimpleXMLRPCRequestHandler,
20             CGIXMLRPCRequestHandler,
21             resolve_dotted_attribute)
22
23 class ServerHTMLDoc(pydoc.HTMLDoc):
24     """Class used to generate pydoc HTML document for a server"""
25
26     def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
27         """Mark up some plain text, given a context of symbols to look for.
28         Each context dictionary maps object names to anchor names."""
29         escape = escape or self.escape
30         results = []
31         here = 0
32
33         # XXX Note that this regular expressions does not allow for the
34         # hyperlinking of arbitrary strings being used as method
35         # names. Only methods with names consisting of word characters
36         # and '.'s are hyperlinked.
37         pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
38                                 r'RFC[- ]?(\d+)|'
39                                 r'PEP[- ]?(\d+)|'
40                                 r'(self\.)?((?:\w|\.)+))\b')
41         while 1:
42             match = pattern.search(text, here)
43             if not match: break
44             start, end = match.span()
45             results.append(escape(text[here:start]))
46
47             all, scheme, rfc, pep, selfdot, name = match.groups()
48             if scheme:
49                 url = escape(all).replace('"', '"')
50                 results.append('<a href="%s">%s</a>' % (url, url))
51             elif rfc:
52                 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
53                 results.append('<a href="%s">%s</a>' % (url, escape(all)))
54             elif pep:
55                 url = 'http://www.python.org/peps/pep-%04d.html' % int(pep)
56                 results.append('<a href="%s">%s</a>' % (url, escape(all)))
57             elif text[end:end+1] == '(':
58                 results.append(self.namelink(name, methods, funcs, classes))
59             elif selfdot:
60                 results.append('self.<strong>%s</strong>' % name)
61             else:
62                 results.append(self.namelink(name, classes))
63             here = end
64         results.append(escape(text[here:]))
65         return ''.join(results)
66
67     def docroutine(self, object, name=None, mod=None,
68                    funcs={}, classes={}, methods={}, cl=None):
69         """Produce HTML documentation for a function or method object."""
70
71         anchor = (cl and cl.__name__ or '') + '-' + name
72         note = ''
73
74         title = '<a name="%s"><strong>%s</strong></a>' % (anchor, name)
75
76         if inspect.ismethod(object):
77             args, varargs, varkw, defaults = inspect.getargspec(object.im_func)
78             # exclude the argument bound to the instance, it will be
79             # confusing to the non-Python user
80             argspec = inspect.formatargspec (
81                     args[1:],
82                     varargs,
83                     varkw,
84                     defaults,
85                     formatvalue=self.formatvalue
86                 )
87         elif inspect.isfunction(object):
88             args, varargs, varkw, defaults = inspect.getargspec(object)
89             argspec = inspect.formatargspec(
90                 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
91         else:
92             argspec = '(...)'
93
94         if isinstance(object, tuple):
95             argspec = object[0] or argspec
96             docstring = object[1] or ""
97         else:
98             docstring = pydoc.getdoc(object)
99
100         decl = title + argspec + (note and self.grey(
101                '<font face="helvetica, arial">%s</font>' % note))
102
103         doc = self.markup(
104             docstring, self.preformat, funcs, classes, methods)
105         doc = doc and '<dd><tt>%s</tt></dd>' % doc
106         return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
107
108     def docserver(self, server_name, package_documentation, methods):
109         """Produce HTML documentation for an XML-RPC server."""
110
111         fdict = {}
112         for key, value in methods.items():
113             fdict[key] = '#-' + key
114             fdict[value] = fdict[key]
115
116         head = '<big><big><strong>%s</strong></big></big>' % server_name
117         result = self.heading(head, '#ffffff', '#7799ee')
118
119         doc = self.markup(package_documentation, self.preformat, fdict)
120         doc = doc and '<tt>%s</tt>' % doc
121         result = result + '<p>%s</p>\n' % doc
122
123         contents = []
124         method_items = methods.items()
125         method_items.sort()
126         for key, value in method_items:
127             contents.append(self.docroutine(value, key, funcs=fdict))
128         result = result + self.bigsection(
129             'Methods', '#ffffff', '#eeaa77', pydoc.join(contents))
130
131         return result
132
133 class XMLRPCDocGenerator:
134     """Generates documentation for an XML-RPC server.
135
136     This class is designed as mix-in and should not
137     be constructed directly.
138     """
139
140     def __init__(self):
141         # setup variables used for HTML documentation
142         self.server_name = 'XML-RPC Server Documentation'
143         self.server_documentation = \
144             "This server exports the following methods through the XML-RPC "\
145             "protocol."
146         self.server_title = 'XML-RPC Server Documentation'
147
148     def set_server_title(self, server_title):
149         """Set the HTML title of the generated server documentation"""
150
151         self.server_title = server_title
152
153     def set_server_name(self, server_name):
154         """Set the name of the generated HTML server documentation"""
155
156         self.server_name = server_name
157
158     def set_server_documentation(self, server_documentation):
159         """Set the documentation string for the entire server."""
160
161         self.server_documentation = server_documentation
162
163     def generate_html_documentation(self):
164         """generate_html_documentation() => html documentation for the server
165
166         Generates HTML documentation for the server using introspection for
167         installed functions and instances that do not implement the
168         _dispatch method. Alternatively, instances can choose to implement
169         the _get_method_argstring(method_name) method to provide the
170         argument string used in the documentation and the
171         _methodHelp(method_name) method to provide the help text used
172         in the documentation."""
173
174         methods = {}
175
176         for method_name in self.system_listMethods():
177             if self.funcs.has_key(method_name):
178                 method = self.funcs[method_name]
179             elif self.instance is not None:
180                 method_info = [None, None] # argspec, documentation
181                 if hasattr(self.instance, '_get_method_argstring'):
182                     method_info[0] = self.instance._get_method_argstring(method_name)
183                 if hasattr(self.instance, '_methodHelp'):
184                     method_info[1] = self.instance._methodHelp(method_name)
185
186                 method_info = tuple(method_info)
187                 if method_info != (None, None):
188                     method = method_info
189                 elif not hasattr(self.instance, '_dispatch'):
190                     try:
191                         method = resolve_dotted_attribute(
192                                     self.instance,
193                                     method_name
194                                     )
195                     except AttributeError:
196                         method = method_info
197                 else:
198                     method = method_info
199             else:
200                 assert 0, "Could not find method in self.functions and no "\
201                           "instance installed"
202
203             methods[method_name] = method
204
205         documenter = ServerHTMLDoc()
206         documentation = documenter.docserver(
207                                 self.server_name,
208                                 self.server_documentation,
209                                 methods
210                             )
211
212         return documenter.page(self.server_title, documentation)
213
214 class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
215     """XML-RPC and documentation request handler class.
216
217     Handles all HTTP POST requests and attempts to decode them as
218     XML-RPC requests.
219
220     Handles all HTTP GET requests and interprets them as requests
221     for documentation.
222     """
223
224     def do_GET(self):
225         """Handles the HTTP GET request.
226
227         Interpret all HTTP GET requests as requests for server
228         documentation.
229         """
230         # Check that the path is legal
231         if not self.is_rpc_path_valid():
232             self.report_404()
233             return
234
235         response = self.server.generate_html_documentation()
236         self.send_response(200)
237         self.send_header("Content-type", "text/html")
238         self.send_header("Content-length", str(len(response)))
239         self.end_headers()
240         self.wfile.write(response)
241
242         # shut down the connection
243         self.wfile.flush()
244         self.connection.shutdown(1)
245
246 class DocXMLRPCServer(  SimpleXMLRPCServer,
247                         XMLRPCDocGenerator):
248     """XML-RPC and HTML documentation server.
249
250     Adds the ability to serve server documentation to the capabilities
251     of SimpleXMLRPCServer.
252     """
253
254     def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
255                  logRequests=1):
256         SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests)
257         XMLRPCDocGenerator.__init__(self)
258
259 class DocCGIXMLRPCRequestHandler(   CGIXMLRPCRequestHandler,
260                                     XMLRPCDocGenerator):
261     """Handler for XML-RPC data and documentation requests passed through
262     CGI"""
263
264     def handle_get(self):
265         """Handles the HTTP GET request.
266
267         Interpret all HTTP GET requests as requests for server
268         documentation.
269         """
270
271         response = self.generate_html_documentation()
272
273         print 'Content-Type: text/html'
274         print 'Content-Length: %d' % len(response)
275         print
276         sys.stdout.write(response)
277
278     def __init__(self):
279         CGIXMLRPCRequestHandler.__init__(self)
280         XMLRPCDocGenerator.__init__(self)
281
282 if __name__ == '__main__':
283     def deg_to_rad(deg):
284         """deg_to_rad(90) => 1.5707963267948966
285
286         Converts an angle in degrees to an angle in radians"""
287         import math
288         return deg * math.pi / 180
289
290     server = DocXMLRPCServer(("localhost", 8000))
291
292     server.set_server_title("Math Server")
293     server.set_server_name("Math XML-RPC Server")
294     server.set_server_documentation("""This server supports various mathematical functions.
295
296 You can use it from Python as follows:
297
298 >>> from xmlrpclib import ServerProxy
299 >>> s = ServerProxy("http://localhost:8000")
300 >>> s.deg_to_rad(90.0)
301 1.5707963267948966""")
302
303     server.register_function(deg_to_rad)
304     server.register_introspection_functions()
305
306     server.serve_forever()