]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | #!/usr/bin/env python\r |
2 | # -*- coding: latin-1 -*-\r | |
3 | """Generate Python documentation in HTML or text for interactive use.\r | |
4 | \r | |
5 | In the Python interpreter, do "from pydoc import help" to provide online\r | |
6 | help. Calling help(thing) on a Python object documents the object.\r | |
7 | \r | |
8 | Or, at the shell command line outside of Python:\r | |
9 | \r | |
10 | Run "pydoc <name>" to show documentation on something. <name> may be\r | |
11 | the name of a function, module, package, or a dotted reference to a\r | |
12 | class or function within a module or module in a package. If the\r | |
13 | argument contains a path segment delimiter (e.g. slash on Unix,\r | |
14 | backslash on Windows) it is treated as the path to a Python source file.\r | |
15 | \r | |
16 | Run "pydoc -k <keyword>" to search for a keyword in the synopsis lines\r | |
17 | of all available modules.\r | |
18 | \r | |
19 | Run "pydoc -p <port>" to start an HTTP server on a given port on the\r | |
20 | local machine to generate documentation web pages.\r | |
21 | \r | |
22 | For platforms without a command line, "pydoc -g" starts the HTTP server\r | |
23 | and also pops up a little window for controlling it.\r | |
24 | \r | |
25 | Run "pydoc -w <name>" to write out the HTML documentation for a module\r | |
26 | to a file named "<name>.html".\r | |
27 | \r | |
28 | Module docs for core modules are assumed to be in\r | |
29 | \r | |
30 | http://docs.python.org/library/\r | |
31 | \r | |
32 | This can be overridden by setting the PYTHONDOCS environment variable\r | |
33 | to a different URL or to a local directory containing the Library\r | |
34 | Reference Manual pages.\r | |
35 | """\r | |
36 | \r | |
37 | __author__ = "Ka-Ping Yee <ping@lfw.org>"\r | |
38 | __date__ = "26 February 2001"\r | |
39 | \r | |
40 | __version__ = "$Revision$"\r | |
41 | __credits__ = """Guido van Rossum, for an excellent programming language.\r | |
42 | Tommy Burnette, the original creator of manpy.\r | |
43 | Paul Prescod, for all his work on onlinehelp.\r | |
44 | Richard Chamberlain, for the first implementation of textdoc.\r | |
45 | """\r | |
46 | \r | |
47 | # Known bugs that can't be fixed here:\r | |
48 | # - imp.load_module() cannot be prevented from clobbering existing\r | |
49 | # loaded modules, so calling synopsis() on a binary module file\r | |
50 | # changes the contents of any existing module with the same name.\r | |
51 | # - If the __file__ attribute on a module is a relative path and\r | |
52 | # the current directory is changed with os.chdir(), an incorrect\r | |
53 | # path will be displayed.\r | |
54 | \r | |
55 | import sys, imp, os, re, types, inspect, __builtin__, pkgutil\r | |
56 | from repr import Repr\r | |
57 | from string import expandtabs, find, join, lower, split, strip, rfind, rstrip\r | |
58 | from traceback import extract_tb\r | |
59 | try:\r | |
60 | from collections import deque\r | |
61 | except ImportError:\r | |
62 | # Python 2.3 compatibility\r | |
63 | class deque(list):\r | |
64 | def popleft(self):\r | |
65 | return self.pop(0)\r | |
66 | \r | |
67 | # --------------------------------------------------------- common routines\r | |
68 | \r | |
69 | def pathdirs():\r | |
70 | """Convert sys.path into a list of absolute, existing, unique paths."""\r | |
71 | dirs = []\r | |
72 | normdirs = []\r | |
73 | for dir in sys.path:\r | |
74 | dir = os.path.abspath(dir or '.')\r | |
75 | normdir = os.path.normcase(dir)\r | |
76 | if normdir not in normdirs and os.path.isdir(dir):\r | |
77 | dirs.append(dir)\r | |
78 | normdirs.append(normdir)\r | |
79 | return dirs\r | |
80 | \r | |
81 | def getdoc(object):\r | |
82 | """Get the doc string or comments for an object."""\r | |
83 | result = inspect.getdoc(object) or inspect.getcomments(object)\r | |
84 | return result and re.sub('^ *\n', '', rstrip(result)) or ''\r | |
85 | \r | |
86 | def splitdoc(doc):\r | |
87 | """Split a doc string into a synopsis line (if any) and the rest."""\r | |
88 | lines = split(strip(doc), '\n')\r | |
89 | if len(lines) == 1:\r | |
90 | return lines[0], ''\r | |
91 | elif len(lines) >= 2 and not rstrip(lines[1]):\r | |
92 | return lines[0], join(lines[2:], '\n')\r | |
93 | return '', join(lines, '\n')\r | |
94 | \r | |
95 | def classname(object, modname):\r | |
96 | """Get a class name and qualify it with a module name if necessary."""\r | |
97 | name = object.__name__\r | |
98 | if object.__module__ != modname:\r | |
99 | name = object.__module__ + '.' + name\r | |
100 | return name\r | |
101 | \r | |
102 | def isdata(object):\r | |
103 | """Check if an object is of a type that probably means it's data."""\r | |
104 | return not (inspect.ismodule(object) or inspect.isclass(object) or\r | |
105 | inspect.isroutine(object) or inspect.isframe(object) or\r | |
106 | inspect.istraceback(object) or inspect.iscode(object))\r | |
107 | \r | |
108 | def replace(text, *pairs):\r | |
109 | """Do a series of global replacements on a string."""\r | |
110 | while pairs:\r | |
111 | text = join(split(text, pairs[0]), pairs[1])\r | |
112 | pairs = pairs[2:]\r | |
113 | return text\r | |
114 | \r | |
115 | def cram(text, maxlen):\r | |
116 | """Omit part of a string if needed to make it fit in a maximum length."""\r | |
117 | if len(text) > maxlen:\r | |
118 | pre = max(0, (maxlen-3)//2)\r | |
119 | post = max(0, maxlen-3-pre)\r | |
120 | return text[:pre] + '...' + text[len(text)-post:]\r | |
121 | return text\r | |
122 | \r | |
123 | _re_stripid = re.compile(r' at 0x[0-9a-f]{6,16}(>+)$', re.IGNORECASE)\r | |
124 | def stripid(text):\r | |
125 | """Remove the hexadecimal id from a Python object representation."""\r | |
126 | # The behaviour of %p is implementation-dependent in terms of case.\r | |
127 | return _re_stripid.sub(r'\1', text)\r | |
128 | \r | |
129 | def _is_some_method(obj):\r | |
130 | return inspect.ismethod(obj) or inspect.ismethoddescriptor(obj)\r | |
131 | \r | |
132 | def allmethods(cl):\r | |
133 | methods = {}\r | |
134 | for key, value in inspect.getmembers(cl, _is_some_method):\r | |
135 | methods[key] = 1\r | |
136 | for base in cl.__bases__:\r | |
137 | methods.update(allmethods(base)) # all your base are belong to us\r | |
138 | for key in methods.keys():\r | |
139 | methods[key] = getattr(cl, key)\r | |
140 | return methods\r | |
141 | \r | |
142 | def _split_list(s, predicate):\r | |
143 | """Split sequence s via predicate, and return pair ([true], [false]).\r | |
144 | \r | |
145 | The return value is a 2-tuple of lists,\r | |
146 | ([x for x in s if predicate(x)],\r | |
147 | [x for x in s if not predicate(x)])\r | |
148 | """\r | |
149 | \r | |
150 | yes = []\r | |
151 | no = []\r | |
152 | for x in s:\r | |
153 | if predicate(x):\r | |
154 | yes.append(x)\r | |
155 | else:\r | |
156 | no.append(x)\r | |
157 | return yes, no\r | |
158 | \r | |
159 | def visiblename(name, all=None, obj=None):\r | |
160 | """Decide whether to show documentation on a variable."""\r | |
161 | # Certain special names are redundant.\r | |
162 | _hidden_names = ('__builtins__', '__doc__', '__file__', '__path__',\r | |
163 | '__module__', '__name__', '__slots__', '__package__')\r | |
164 | if name in _hidden_names: return 0\r | |
165 | # Private names are hidden, but special names are displayed.\r | |
166 | if name.startswith('__') and name.endswith('__'): return 1\r | |
167 | # Namedtuples have public fields and methods with a single leading underscore\r | |
168 | if name.startswith('_') and hasattr(obj, '_fields'):\r | |
169 | return 1\r | |
170 | if all is not None:\r | |
171 | # only document that which the programmer exported in __all__\r | |
172 | return name in all\r | |
173 | else:\r | |
174 | return not name.startswith('_')\r | |
175 | \r | |
176 | def classify_class_attrs(object):\r | |
177 | """Wrap inspect.classify_class_attrs, with fixup for data descriptors."""\r | |
178 | def fixup(data):\r | |
179 | name, kind, cls, value = data\r | |
180 | if inspect.isdatadescriptor(value):\r | |
181 | kind = 'data descriptor'\r | |
182 | return name, kind, cls, value\r | |
183 | return map(fixup, inspect.classify_class_attrs(object))\r | |
184 | \r | |
185 | # ----------------------------------------------------- module manipulation\r | |
186 | \r | |
187 | def ispackage(path):\r | |
188 | """Guess whether a path refers to a package directory."""\r | |
189 | if os.path.isdir(path):\r | |
190 | for ext in ('.py', '.pyc', '.pyo'):\r | |
191 | if os.path.isfile(os.path.join(path, '__init__' + ext)):\r | |
192 | return True\r | |
193 | return False\r | |
194 | \r | |
195 | def source_synopsis(file):\r | |
196 | line = file.readline()\r | |
197 | while line[:1] == '#' or not strip(line):\r | |
198 | line = file.readline()\r | |
199 | if not line: break\r | |
200 | line = strip(line)\r | |
201 | if line[:4] == 'r"""': line = line[1:]\r | |
202 | if line[:3] == '"""':\r | |
203 | line = line[3:]\r | |
204 | if line[-1:] == '\\': line = line[:-1]\r | |
205 | while not strip(line):\r | |
206 | line = file.readline()\r | |
207 | if not line: break\r | |
208 | result = strip(split(line, '"""')[0])\r | |
209 | else: result = None\r | |
210 | return result\r | |
211 | \r | |
212 | def synopsis(filename, cache={}):\r | |
213 | """Get the one-line summary out of a module file."""\r | |
214 | mtime = os.stat(filename).st_mtime\r | |
215 | lastupdate, result = cache.get(filename, (0, None))\r | |
216 | if lastupdate < mtime:\r | |
217 | info = inspect.getmoduleinfo(filename)\r | |
218 | try:\r | |
219 | file = open(filename)\r | |
220 | except IOError:\r | |
221 | # module can't be opened, so skip it\r | |
222 | return None\r | |
223 | if info and 'b' in info[2]: # binary modules have to be imported\r | |
224 | try: module = imp.load_module('__temp__', file, filename, info[1:])\r | |
225 | except: return None\r | |
226 | result = (module.__doc__ or '').splitlines()[0]\r | |
227 | del sys.modules['__temp__']\r | |
228 | else: # text modules can be directly examined\r | |
229 | result = source_synopsis(file)\r | |
230 | file.close()\r | |
231 | cache[filename] = (mtime, result)\r | |
232 | return result\r | |
233 | \r | |
234 | class ErrorDuringImport(Exception):\r | |
235 | """Errors that occurred while trying to import something to document it."""\r | |
236 | def __init__(self, filename, exc_info):\r | |
237 | exc, value, tb = exc_info\r | |
238 | self.filename = filename\r | |
239 | self.exc = exc\r | |
240 | self.value = value\r | |
241 | self.tb = tb\r | |
242 | \r | |
243 | def __str__(self):\r | |
244 | exc = self.exc\r | |
245 | if type(exc) is types.ClassType:\r | |
246 | exc = exc.__name__\r | |
247 | return 'problem in %s - %s: %s' % (self.filename, exc, self.value)\r | |
248 | \r | |
249 | def importfile(path):\r | |
250 | """Import a Python source file or compiled file given its path."""\r | |
251 | magic = imp.get_magic()\r | |
252 | file = open(path, 'r')\r | |
253 | if file.read(len(magic)) == magic:\r | |
254 | kind = imp.PY_COMPILED\r | |
255 | else:\r | |
256 | kind = imp.PY_SOURCE\r | |
257 | file.close()\r | |
258 | filename = os.path.basename(path)\r | |
259 | name, ext = os.path.splitext(filename)\r | |
260 | file = open(path, 'r')\r | |
261 | try:\r | |
262 | module = imp.load_module(name, file, path, (ext, 'r', kind))\r | |
263 | except:\r | |
264 | raise ErrorDuringImport(path, sys.exc_info())\r | |
265 | file.close()\r | |
266 | return module\r | |
267 | \r | |
268 | def safeimport(path, forceload=0, cache={}):\r | |
269 | """Import a module; handle errors; return None if the module isn't found.\r | |
270 | \r | |
271 | If the module *is* found but an exception occurs, it's wrapped in an\r | |
272 | ErrorDuringImport exception and reraised. Unlike __import__, if a\r | |
273 | package path is specified, the module at the end of the path is returned,\r | |
274 | not the package at the beginning. If the optional 'forceload' argument\r | |
275 | is 1, we reload the module from disk (unless it's a dynamic extension)."""\r | |
276 | try:\r | |
277 | # If forceload is 1 and the module has been previously loaded from\r | |
278 | # disk, we always have to reload the module. Checking the file's\r | |
279 | # mtime isn't good enough (e.g. the module could contain a class\r | |
280 | # that inherits from another module that has changed).\r | |
281 | if forceload and path in sys.modules:\r | |
282 | if path not in sys.builtin_module_names:\r | |
283 | # Avoid simply calling reload() because it leaves names in\r | |
284 | # the currently loaded module lying around if they're not\r | |
285 | # defined in the new source file. Instead, remove the\r | |
286 | # module from sys.modules and re-import. Also remove any\r | |
287 | # submodules because they won't appear in the newly loaded\r | |
288 | # module's namespace if they're already in sys.modules.\r | |
289 | subs = [m for m in sys.modules if m.startswith(path + '.')]\r | |
290 | for key in [path] + subs:\r | |
291 | # Prevent garbage collection.\r | |
292 | cache[key] = sys.modules[key]\r | |
293 | del sys.modules[key]\r | |
294 | module = __import__(path)\r | |
295 | except:\r | |
296 | # Did the error occur before or after the module was found?\r | |
297 | (exc, value, tb) = info = sys.exc_info()\r | |
298 | if path in sys.modules:\r | |
299 | # An error occurred while executing the imported module.\r | |
300 | raise ErrorDuringImport(sys.modules[path].__file__, info)\r | |
301 | elif exc is SyntaxError:\r | |
302 | # A SyntaxError occurred before we could execute the module.\r | |
303 | raise ErrorDuringImport(value.filename, info)\r | |
304 | elif exc is ImportError and extract_tb(tb)[-1][2]=='safeimport':\r | |
305 | # The import error occurred directly in this function,\r | |
306 | # which means there is no such module in the path.\r | |
307 | return None\r | |
308 | else:\r | |
309 | # Some other error occurred during the importing process.\r | |
310 | raise ErrorDuringImport(path, sys.exc_info())\r | |
311 | for part in split(path, '.')[1:]:\r | |
312 | try: module = getattr(module, part)\r | |
313 | except AttributeError: return None\r | |
314 | return module\r | |
315 | \r | |
316 | # ---------------------------------------------------- formatter base class\r | |
317 | \r | |
318 | class Doc:\r | |
319 | def document(self, object, name=None, *args):\r | |
320 | """Generate documentation for an object."""\r | |
321 | args = (object, name) + args\r | |
322 | # 'try' clause is to attempt to handle the possibility that inspect\r | |
323 | # identifies something in a way that pydoc itself has issues handling;\r | |
324 | # think 'super' and how it is a descriptor (which raises the exception\r | |
325 | # by lacking a __name__ attribute) and an instance.\r | |
326 | if inspect.isgetsetdescriptor(object): return self.docdata(*args)\r | |
327 | if inspect.ismemberdescriptor(object): return self.docdata(*args)\r | |
328 | try:\r | |
329 | if inspect.ismodule(object): return self.docmodule(*args)\r | |
330 | if inspect.isclass(object): return self.docclass(*args)\r | |
331 | if inspect.isroutine(object): return self.docroutine(*args)\r | |
332 | except AttributeError:\r | |
333 | pass\r | |
334 | if isinstance(object, property): return self.docproperty(*args)\r | |
335 | return self.docother(*args)\r | |
336 | \r | |
337 | def fail(self, object, name=None, *args):\r | |
338 | """Raise an exception for unimplemented types."""\r | |
339 | message = "don't know how to document object%s of type %s" % (\r | |
340 | name and ' ' + repr(name), type(object).__name__)\r | |
341 | raise TypeError, message\r | |
342 | \r | |
343 | docmodule = docclass = docroutine = docother = docproperty = docdata = fail\r | |
344 | \r | |
345 | def getdocloc(self, object):\r | |
346 | """Return the location of module docs or None"""\r | |
347 | \r | |
348 | try:\r | |
349 | file = inspect.getabsfile(object)\r | |
350 | except TypeError:\r | |
351 | file = '(built-in)'\r | |
352 | \r | |
353 | docloc = os.environ.get("PYTHONDOCS",\r | |
354 | "http://docs.python.org/library")\r | |
355 | basedir = os.path.join(sys.exec_prefix, "lib",\r | |
356 | "python"+sys.version[0:3])\r | |
357 | if (isinstance(object, type(os)) and\r | |
358 | (object.__name__ in ('errno', 'exceptions', 'gc', 'imp',\r | |
359 | 'marshal', 'posix', 'signal', 'sys',\r | |
360 | 'thread', 'zipimport') or\r | |
361 | (file.startswith(basedir) and\r | |
362 | not file.startswith(os.path.join(basedir, 'site-packages')))) and\r | |
363 | object.__name__ not in ('xml.etree', 'test.pydoc_mod')):\r | |
364 | if docloc.startswith("http://"):\r | |
365 | docloc = "%s/%s" % (docloc.rstrip("/"), object.__name__)\r | |
366 | else:\r | |
367 | docloc = os.path.join(docloc, object.__name__ + ".html")\r | |
368 | else:\r | |
369 | docloc = None\r | |
370 | return docloc\r | |
371 | \r | |
372 | # -------------------------------------------- HTML documentation generator\r | |
373 | \r | |
374 | class HTMLRepr(Repr):\r | |
375 | """Class for safely making an HTML representation of a Python object."""\r | |
376 | def __init__(self):\r | |
377 | Repr.__init__(self)\r | |
378 | self.maxlist = self.maxtuple = 20\r | |
379 | self.maxdict = 10\r | |
380 | self.maxstring = self.maxother = 100\r | |
381 | \r | |
382 | def escape(self, text):\r | |
383 | return replace(text, '&', '&', '<', '<', '>', '>')\r | |
384 | \r | |
385 | def repr(self, object):\r | |
386 | return Repr.repr(self, object)\r | |
387 | \r | |
388 | def repr1(self, x, level):\r | |
389 | if hasattr(type(x), '__name__'):\r | |
390 | methodname = 'repr_' + join(split(type(x).__name__), '_')\r | |
391 | if hasattr(self, methodname):\r | |
392 | return getattr(self, methodname)(x, level)\r | |
393 | return self.escape(cram(stripid(repr(x)), self.maxother))\r | |
394 | \r | |
395 | def repr_string(self, x, level):\r | |
396 | test = cram(x, self.maxstring)\r | |
397 | testrepr = repr(test)\r | |
398 | if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):\r | |
399 | # Backslashes are only literal in the string and are never\r | |
400 | # needed to make any special characters, so show a raw string.\r | |
401 | return 'r' + testrepr[0] + self.escape(test) + testrepr[0]\r | |
402 | return re.sub(r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',\r | |
403 | r'<font color="#c040c0">\1</font>',\r | |
404 | self.escape(testrepr))\r | |
405 | \r | |
406 | repr_str = repr_string\r | |
407 | \r | |
408 | def repr_instance(self, x, level):\r | |
409 | try:\r | |
410 | return self.escape(cram(stripid(repr(x)), self.maxstring))\r | |
411 | except:\r | |
412 | return self.escape('<%s instance>' % x.__class__.__name__)\r | |
413 | \r | |
414 | repr_unicode = repr_string\r | |
415 | \r | |
416 | class HTMLDoc(Doc):\r | |
417 | """Formatter class for HTML documentation."""\r | |
418 | \r | |
419 | # ------------------------------------------- HTML formatting utilities\r | |
420 | \r | |
421 | _repr_instance = HTMLRepr()\r | |
422 | repr = _repr_instance.repr\r | |
423 | escape = _repr_instance.escape\r | |
424 | \r | |
425 | def page(self, title, contents):\r | |
426 | """Format an HTML page."""\r | |
427 | return '''\r | |
428 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r | |
429 | <html><head><title>Python: %s</title>\r | |
430 | </head><body bgcolor="#f0f0f8">\r | |
431 | %s\r | |
432 | </body></html>''' % (title, contents)\r | |
433 | \r | |
434 | def heading(self, title, fgcol, bgcol, extras=''):\r | |
435 | """Format a page heading."""\r | |
436 | return '''\r | |
437 | <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="heading">\r | |
438 | <tr bgcolor="%s">\r | |
439 | <td valign=bottom> <br>\r | |
440 | <font color="%s" face="helvetica, arial"> <br>%s</font></td\r | |
441 | ><td align=right valign=bottom\r | |
442 | ><font color="%s" face="helvetica, arial">%s</font></td></tr></table>\r | |
443 | ''' % (bgcol, fgcol, title, fgcol, extras or ' ')\r | |
444 | \r | |
445 | def section(self, title, fgcol, bgcol, contents, width=6,\r | |
446 | prelude='', marginalia=None, gap=' '):\r | |
447 | """Format a section with a heading."""\r | |
448 | if marginalia is None:\r | |
449 | marginalia = '<tt>' + ' ' * width + '</tt>'\r | |
450 | result = '''<p>\r | |
451 | <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">\r | |
452 | <tr bgcolor="%s">\r | |
453 | <td colspan=3 valign=bottom> <br>\r | |
454 | <font color="%s" face="helvetica, arial">%s</font></td></tr>\r | |
455 | ''' % (bgcol, fgcol, title)\r | |
456 | if prelude:\r | |
457 | result = result + '''\r | |
458 | <tr bgcolor="%s"><td rowspan=2>%s</td>\r | |
459 | <td colspan=2>%s</td></tr>\r | |
460 | <tr><td>%s</td>''' % (bgcol, marginalia, prelude, gap)\r | |
461 | else:\r | |
462 | result = result + '''\r | |
463 | <tr><td bgcolor="%s">%s</td><td>%s</td>''' % (bgcol, marginalia, gap)\r | |
464 | \r | |
465 | return result + '\n<td width="100%%">%s</td></tr></table>' % contents\r | |
466 | \r | |
467 | def bigsection(self, title, *args):\r | |
468 | """Format a section with a big heading."""\r | |
469 | title = '<big><strong>%s</strong></big>' % title\r | |
470 | return self.section(title, *args)\r | |
471 | \r | |
472 | def preformat(self, text):\r | |
473 | """Format literal preformatted text."""\r | |
474 | text = self.escape(expandtabs(text))\r | |
475 | return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',\r | |
476 | ' ', ' ', '\n', '<br>\n')\r | |
477 | \r | |
478 | def multicolumn(self, list, format, cols=4):\r | |
479 | """Format a list of items into a multi-column list."""\r | |
480 | result = ''\r | |
481 | rows = (len(list)+cols-1)//cols\r | |
482 | for col in range(cols):\r | |
483 | result = result + '<td width="%d%%" valign=top>' % (100//cols)\r | |
484 | for i in range(rows*col, rows*col+rows):\r | |
485 | if i < len(list):\r | |
486 | result = result + format(list[i]) + '<br>\n'\r | |
487 | result = result + '</td>'\r | |
488 | return '<table width="100%%" summary="list"><tr>%s</tr></table>' % result\r | |
489 | \r | |
490 | def grey(self, text): return '<font color="#909090">%s</font>' % text\r | |
491 | \r | |
492 | def namelink(self, name, *dicts):\r | |
493 | """Make a link for an identifier, given name-to-URL mappings."""\r | |
494 | for dict in dicts:\r | |
495 | if name in dict:\r | |
496 | return '<a href="%s">%s</a>' % (dict[name], name)\r | |
497 | return name\r | |
498 | \r | |
499 | def classlink(self, object, modname):\r | |
500 | """Make a link for a class."""\r | |
501 | name, module = object.__name__, sys.modules.get(object.__module__)\r | |
502 | if hasattr(module, name) and getattr(module, name) is object:\r | |
503 | return '<a href="%s.html#%s">%s</a>' % (\r | |
504 | module.__name__, name, classname(object, modname))\r | |
505 | return classname(object, modname)\r | |
506 | \r | |
507 | def modulelink(self, object):\r | |
508 | """Make a link for a module."""\r | |
509 | return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)\r | |
510 | \r | |
511 | def modpkglink(self, data):\r | |
512 | """Make a link for a module or package to display in an index."""\r | |
513 | name, path, ispackage, shadowed = data\r | |
514 | if shadowed:\r | |
515 | return self.grey(name)\r | |
516 | if path:\r | |
517 | url = '%s.%s.html' % (path, name)\r | |
518 | else:\r | |
519 | url = '%s.html' % name\r | |
520 | if ispackage:\r | |
521 | text = '<strong>%s</strong> (package)' % name\r | |
522 | else:\r | |
523 | text = name\r | |
524 | return '<a href="%s">%s</a>' % (url, text)\r | |
525 | \r | |
526 | def markup(self, text, escape=None, funcs={}, classes={}, methods={}):\r | |
527 | """Mark up some plain text, given a context of symbols to look for.\r | |
528 | Each context dictionary maps object names to anchor names."""\r | |
529 | escape = escape or self.escape\r | |
530 | results = []\r | |
531 | here = 0\r | |
532 | pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'\r | |
533 | r'RFC[- ]?(\d+)|'\r | |
534 | r'PEP[- ]?(\d+)|'\r | |
535 | r'(self\.)?(\w+))')\r | |
536 | while True:\r | |
537 | match = pattern.search(text, here)\r | |
538 | if not match: break\r | |
539 | start, end = match.span()\r | |
540 | results.append(escape(text[here:start]))\r | |
541 | \r | |
542 | all, scheme, rfc, pep, selfdot, name = match.groups()\r | |
543 | if scheme:\r | |
544 | url = escape(all).replace('"', '"')\r | |
545 | results.append('<a href="%s">%s</a>' % (url, url))\r | |
546 | elif rfc:\r | |
547 | url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)\r | |
548 | results.append('<a href="%s">%s</a>' % (url, escape(all)))\r | |
549 | elif pep:\r | |
550 | url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)\r | |
551 | results.append('<a href="%s">%s</a>' % (url, escape(all)))\r | |
552 | elif text[end:end+1] == '(':\r | |
553 | results.append(self.namelink(name, methods, funcs, classes))\r | |
554 | elif selfdot:\r | |
555 | results.append('self.<strong>%s</strong>' % name)\r | |
556 | else:\r | |
557 | results.append(self.namelink(name, classes))\r | |
558 | here = end\r | |
559 | results.append(escape(text[here:]))\r | |
560 | return join(results, '')\r | |
561 | \r | |
562 | # ---------------------------------------------- type-specific routines\r | |
563 | \r | |
564 | def formattree(self, tree, modname, parent=None):\r | |
565 | """Produce HTML for a class tree as given by inspect.getclasstree()."""\r | |
566 | result = ''\r | |
567 | for entry in tree:\r | |
568 | if type(entry) is type(()):\r | |
569 | c, bases = entry\r | |
570 | result = result + '<dt><font face="helvetica, arial">'\r | |
571 | result = result + self.classlink(c, modname)\r | |
572 | if bases and bases != (parent,):\r | |
573 | parents = []\r | |
574 | for base in bases:\r | |
575 | parents.append(self.classlink(base, modname))\r | |
576 | result = result + '(' + join(parents, ', ') + ')'\r | |
577 | result = result + '\n</font></dt>'\r | |
578 | elif type(entry) is type([]):\r | |
579 | result = result + '<dd>\n%s</dd>\n' % self.formattree(\r | |
580 | entry, modname, c)\r | |
581 | return '<dl>\n%s</dl>\n' % result\r | |
582 | \r | |
583 | def docmodule(self, object, name=None, mod=None, *ignored):\r | |
584 | """Produce HTML documentation for a module object."""\r | |
585 | name = object.__name__ # ignore the passed-in name\r | |
586 | try:\r | |
587 | all = object.__all__\r | |
588 | except AttributeError:\r | |
589 | all = None\r | |
590 | parts = split(name, '.')\r | |
591 | links = []\r | |
592 | for i in range(len(parts)-1):\r | |
593 | links.append(\r | |
594 | '<a href="%s.html"><font color="#ffffff">%s</font></a>' %\r | |
595 | (join(parts[:i+1], '.'), parts[i]))\r | |
596 | linkedname = join(links + parts[-1:], '.')\r | |
597 | head = '<big><big><strong>%s</strong></big></big>' % linkedname\r | |
598 | try:\r | |
599 | path = inspect.getabsfile(object)\r | |
600 | url = path\r | |
601 | if sys.platform == 'win32':\r | |
602 | import nturl2path\r | |
603 | url = nturl2path.pathname2url(path)\r | |
604 | filelink = '<a href="file:%s">%s</a>' % (url, path)\r | |
605 | except TypeError:\r | |
606 | filelink = '(built-in)'\r | |
607 | info = []\r | |
608 | if hasattr(object, '__version__'):\r | |
609 | version = str(object.__version__)\r | |
610 | if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':\r | |
611 | version = strip(version[11:-1])\r | |
612 | info.append('version %s' % self.escape(version))\r | |
613 | if hasattr(object, '__date__'):\r | |
614 | info.append(self.escape(str(object.__date__)))\r | |
615 | if info:\r | |
616 | head = head + ' (%s)' % join(info, ', ')\r | |
617 | docloc = self.getdocloc(object)\r | |
618 | if docloc is not None:\r | |
619 | docloc = '<br><a href="%(docloc)s">Module Docs</a>' % locals()\r | |
620 | else:\r | |
621 | docloc = ''\r | |
622 | result = self.heading(\r | |
623 | head, '#ffffff', '#7799ee',\r | |
624 | '<a href=".">index</a><br>' + filelink + docloc)\r | |
625 | \r | |
626 | modules = inspect.getmembers(object, inspect.ismodule)\r | |
627 | \r | |
628 | classes, cdict = [], {}\r | |
629 | for key, value in inspect.getmembers(object, inspect.isclass):\r | |
630 | # if __all__ exists, believe it. Otherwise use old heuristic.\r | |
631 | if (all is not None or\r | |
632 | (inspect.getmodule(value) or object) is object):\r | |
633 | if visiblename(key, all, object):\r | |
634 | classes.append((key, value))\r | |
635 | cdict[key] = cdict[value] = '#' + key\r | |
636 | for key, value in classes:\r | |
637 | for base in value.__bases__:\r | |
638 | key, modname = base.__name__, base.__module__\r | |
639 | module = sys.modules.get(modname)\r | |
640 | if modname != name and module and hasattr(module, key):\r | |
641 | if getattr(module, key) is base:\r | |
642 | if not key in cdict:\r | |
643 | cdict[key] = cdict[base] = modname + '.html#' + key\r | |
644 | funcs, fdict = [], {}\r | |
645 | for key, value in inspect.getmembers(object, inspect.isroutine):\r | |
646 | # if __all__ exists, believe it. Otherwise use old heuristic.\r | |
647 | if (all is not None or\r | |
648 | inspect.isbuiltin(value) or inspect.getmodule(value) is object):\r | |
649 | if visiblename(key, all, object):\r | |
650 | funcs.append((key, value))\r | |
651 | fdict[key] = '#-' + key\r | |
652 | if inspect.isfunction(value): fdict[value] = fdict[key]\r | |
653 | data = []\r | |
654 | for key, value in inspect.getmembers(object, isdata):\r | |
655 | if visiblename(key, all, object):\r | |
656 | data.append((key, value))\r | |
657 | \r | |
658 | doc = self.markup(getdoc(object), self.preformat, fdict, cdict)\r | |
659 | doc = doc and '<tt>%s</tt>' % doc\r | |
660 | result = result + '<p>%s</p>\n' % doc\r | |
661 | \r | |
662 | if hasattr(object, '__path__'):\r | |
663 | modpkgs = []\r | |
664 | for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):\r | |
665 | modpkgs.append((modname, name, ispkg, 0))\r | |
666 | modpkgs.sort()\r | |
667 | contents = self.multicolumn(modpkgs, self.modpkglink)\r | |
668 | result = result + self.bigsection(\r | |
669 | 'Package Contents', '#ffffff', '#aa55cc', contents)\r | |
670 | elif modules:\r | |
671 | contents = self.multicolumn(\r | |
672 | modules, lambda key_value, s=self: s.modulelink(key_value[1]))\r | |
673 | result = result + self.bigsection(\r | |
674 | 'Modules', '#ffffff', '#aa55cc', contents)\r | |
675 | \r | |
676 | if classes:\r | |
677 | classlist = map(lambda key_value: key_value[1], classes)\r | |
678 | contents = [\r | |
679 | self.formattree(inspect.getclasstree(classlist, 1), name)]\r | |
680 | for key, value in classes:\r | |
681 | contents.append(self.document(value, key, name, fdict, cdict))\r | |
682 | result = result + self.bigsection(\r | |
683 | 'Classes', '#ffffff', '#ee77aa', join(contents))\r | |
684 | if funcs:\r | |
685 | contents = []\r | |
686 | for key, value in funcs:\r | |
687 | contents.append(self.document(value, key, name, fdict, cdict))\r | |
688 | result = result + self.bigsection(\r | |
689 | 'Functions', '#ffffff', '#eeaa77', join(contents))\r | |
690 | if data:\r | |
691 | contents = []\r | |
692 | for key, value in data:\r | |
693 | contents.append(self.document(value, key))\r | |
694 | result = result + self.bigsection(\r | |
695 | 'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))\r | |
696 | if hasattr(object, '__author__'):\r | |
697 | contents = self.markup(str(object.__author__), self.preformat)\r | |
698 | result = result + self.bigsection(\r | |
699 | 'Author', '#ffffff', '#7799ee', contents)\r | |
700 | if hasattr(object, '__credits__'):\r | |
701 | contents = self.markup(str(object.__credits__), self.preformat)\r | |
702 | result = result + self.bigsection(\r | |
703 | 'Credits', '#ffffff', '#7799ee', contents)\r | |
704 | \r | |
705 | return result\r | |
706 | \r | |
707 | def docclass(self, object, name=None, mod=None, funcs={}, classes={},\r | |
708 | *ignored):\r | |
709 | """Produce HTML documentation for a class object."""\r | |
710 | realname = object.__name__\r | |
711 | name = name or realname\r | |
712 | bases = object.__bases__\r | |
713 | \r | |
714 | contents = []\r | |
715 | push = contents.append\r | |
716 | \r | |
717 | # Cute little class to pump out a horizontal rule between sections.\r | |
718 | class HorizontalRule:\r | |
719 | def __init__(self):\r | |
720 | self.needone = 0\r | |
721 | def maybe(self):\r | |
722 | if self.needone:\r | |
723 | push('<hr>\n')\r | |
724 | self.needone = 1\r | |
725 | hr = HorizontalRule()\r | |
726 | \r | |
727 | # List the mro, if non-trivial.\r | |
728 | mro = deque(inspect.getmro(object))\r | |
729 | if len(mro) > 2:\r | |
730 | hr.maybe()\r | |
731 | push('<dl><dt>Method resolution order:</dt>\n')\r | |
732 | for base in mro:\r | |
733 | push('<dd>%s</dd>\n' % self.classlink(base,\r | |
734 | object.__module__))\r | |
735 | push('</dl>\n')\r | |
736 | \r | |
737 | def spill(msg, attrs, predicate):\r | |
738 | ok, attrs = _split_list(attrs, predicate)\r | |
739 | if ok:\r | |
740 | hr.maybe()\r | |
741 | push(msg)\r | |
742 | for name, kind, homecls, value in ok:\r | |
743 | push(self.document(getattr(object, name), name, mod,\r | |
744 | funcs, classes, mdict, object))\r | |
745 | push('\n')\r | |
746 | return attrs\r | |
747 | \r | |
748 | def spilldescriptors(msg, attrs, predicate):\r | |
749 | ok, attrs = _split_list(attrs, predicate)\r | |
750 | if ok:\r | |
751 | hr.maybe()\r | |
752 | push(msg)\r | |
753 | for name, kind, homecls, value in ok:\r | |
754 | push(self._docdescriptor(name, value, mod))\r | |
755 | return attrs\r | |
756 | \r | |
757 | def spilldata(msg, attrs, predicate):\r | |
758 | ok, attrs = _split_list(attrs, predicate)\r | |
759 | if ok:\r | |
760 | hr.maybe()\r | |
761 | push(msg)\r | |
762 | for name, kind, homecls, value in ok:\r | |
763 | base = self.docother(getattr(object, name), name, mod)\r | |
764 | if (hasattr(value, '__call__') or\r | |
765 | inspect.isdatadescriptor(value)):\r | |
766 | doc = getattr(value, "__doc__", None)\r | |
767 | else:\r | |
768 | doc = None\r | |
769 | if doc is None:\r | |
770 | push('<dl><dt>%s</dl>\n' % base)\r | |
771 | else:\r | |
772 | doc = self.markup(getdoc(value), self.preformat,\r | |
773 | funcs, classes, mdict)\r | |
774 | doc = '<dd><tt>%s</tt>' % doc\r | |
775 | push('<dl><dt>%s%s</dl>\n' % (base, doc))\r | |
776 | push('\n')\r | |
777 | return attrs\r | |
778 | \r | |
779 | attrs = filter(lambda data: visiblename(data[0], obj=object),\r | |
780 | classify_class_attrs(object))\r | |
781 | mdict = {}\r | |
782 | for key, kind, homecls, value in attrs:\r | |
783 | mdict[key] = anchor = '#' + name + '-' + key\r | |
784 | value = getattr(object, key)\r | |
785 | try:\r | |
786 | # The value may not be hashable (e.g., a data attr with\r | |
787 | # a dict or list value).\r | |
788 | mdict[value] = anchor\r | |
789 | except TypeError:\r | |
790 | pass\r | |
791 | \r | |
792 | while attrs:\r | |
793 | if mro:\r | |
794 | thisclass = mro.popleft()\r | |
795 | else:\r | |
796 | thisclass = attrs[0][2]\r | |
797 | attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)\r | |
798 | \r | |
799 | if thisclass is __builtin__.object:\r | |
800 | attrs = inherited\r | |
801 | continue\r | |
802 | elif thisclass is object:\r | |
803 | tag = 'defined here'\r | |
804 | else:\r | |
805 | tag = 'inherited from %s' % self.classlink(thisclass,\r | |
806 | object.__module__)\r | |
807 | tag += ':<br>\n'\r | |
808 | \r | |
809 | # Sort attrs by name.\r | |
810 | try:\r | |
811 | attrs.sort(key=lambda t: t[0])\r | |
812 | except TypeError:\r | |
813 | attrs.sort(lambda t1, t2: cmp(t1[0], t2[0])) # 2.3 compat\r | |
814 | \r | |
815 | # Pump out the attrs, segregated by kind.\r | |
816 | attrs = spill('Methods %s' % tag, attrs,\r | |
817 | lambda t: t[1] == 'method')\r | |
818 | attrs = spill('Class methods %s' % tag, attrs,\r | |
819 | lambda t: t[1] == 'class method')\r | |
820 | attrs = spill('Static methods %s' % tag, attrs,\r | |
821 | lambda t: t[1] == 'static method')\r | |
822 | attrs = spilldescriptors('Data descriptors %s' % tag, attrs,\r | |
823 | lambda t: t[1] == 'data descriptor')\r | |
824 | attrs = spilldata('Data and other attributes %s' % tag, attrs,\r | |
825 | lambda t: t[1] == 'data')\r | |
826 | assert attrs == []\r | |
827 | attrs = inherited\r | |
828 | \r | |
829 | contents = ''.join(contents)\r | |
830 | \r | |
831 | if name == realname:\r | |
832 | title = '<a name="%s">class <strong>%s</strong></a>' % (\r | |
833 | name, realname)\r | |
834 | else:\r | |
835 | title = '<strong>%s</strong> = <a name="%s">class %s</a>' % (\r | |
836 | name, name, realname)\r | |
837 | if bases:\r | |
838 | parents = []\r | |
839 | for base in bases:\r | |
840 | parents.append(self.classlink(base, object.__module__))\r | |
841 | title = title + '(%s)' % join(parents, ', ')\r | |
842 | doc = self.markup(getdoc(object), self.preformat, funcs, classes, mdict)\r | |
843 | doc = doc and '<tt>%s<br> </tt>' % doc\r | |
844 | \r | |
845 | return self.section(title, '#000000', '#ffc8d8', contents, 3, doc)\r | |
846 | \r | |
847 | def formatvalue(self, object):\r | |
848 | """Format an argument default value as text."""\r | |
849 | return self.grey('=' + self.repr(object))\r | |
850 | \r | |
851 | def docroutine(self, object, name=None, mod=None,\r | |
852 | funcs={}, classes={}, methods={}, cl=None):\r | |
853 | """Produce HTML documentation for a function or method object."""\r | |
854 | realname = object.__name__\r | |
855 | name = name or realname\r | |
856 | anchor = (cl and cl.__name__ or '') + '-' + name\r | |
857 | note = ''\r | |
858 | skipdocs = 0\r | |
859 | if inspect.ismethod(object):\r | |
860 | imclass = object.im_class\r | |
861 | if cl:\r | |
862 | if imclass is not cl:\r | |
863 | note = ' from ' + self.classlink(imclass, mod)\r | |
864 | else:\r | |
865 | if object.im_self is not None:\r | |
866 | note = ' method of %s instance' % self.classlink(\r | |
867 | object.im_self.__class__, mod)\r | |
868 | else:\r | |
869 | note = ' unbound %s method' % self.classlink(imclass,mod)\r | |
870 | object = object.im_func\r | |
871 | \r | |
872 | if name == realname:\r | |
873 | title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)\r | |
874 | else:\r | |
875 | if (cl and realname in cl.__dict__ and\r | |
876 | cl.__dict__[realname] is object):\r | |
877 | reallink = '<a href="#%s">%s</a>' % (\r | |
878 | cl.__name__ + '-' + realname, realname)\r | |
879 | skipdocs = 1\r | |
880 | else:\r | |
881 | reallink = realname\r | |
882 | title = '<a name="%s"><strong>%s</strong></a> = %s' % (\r | |
883 | anchor, name, reallink)\r | |
884 | if inspect.isfunction(object):\r | |
885 | args, varargs, varkw, defaults = inspect.getargspec(object)\r | |
886 | argspec = inspect.formatargspec(\r | |
887 | args, varargs, varkw, defaults, formatvalue=self.formatvalue)\r | |
888 | if realname == '<lambda>':\r | |
889 | title = '<strong>%s</strong> <em>lambda</em> ' % name\r | |
890 | argspec = argspec[1:-1] # remove parentheses\r | |
891 | else:\r | |
892 | argspec = '(...)'\r | |
893 | \r | |
894 | decl = title + argspec + (note and self.grey(\r | |
895 | '<font face="helvetica, arial">%s</font>' % note))\r | |
896 | \r | |
897 | if skipdocs:\r | |
898 | return '<dl><dt>%s</dt></dl>\n' % decl\r | |
899 | else:\r | |
900 | doc = self.markup(\r | |
901 | getdoc(object), self.preformat, funcs, classes, methods)\r | |
902 | doc = doc and '<dd><tt>%s</tt></dd>' % doc\r | |
903 | return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)\r | |
904 | \r | |
905 | def _docdescriptor(self, name, value, mod):\r | |
906 | results = []\r | |
907 | push = results.append\r | |
908 | \r | |
909 | if name:\r | |
910 | push('<dl><dt><strong>%s</strong></dt>\n' % name)\r | |
911 | if value.__doc__ is not None:\r | |
912 | doc = self.markup(getdoc(value), self.preformat)\r | |
913 | push('<dd><tt>%s</tt></dd>\n' % doc)\r | |
914 | push('</dl>\n')\r | |
915 | \r | |
916 | return ''.join(results)\r | |
917 | \r | |
918 | def docproperty(self, object, name=None, mod=None, cl=None):\r | |
919 | """Produce html documentation for a property."""\r | |
920 | return self._docdescriptor(name, object, mod)\r | |
921 | \r | |
922 | def docother(self, object, name=None, mod=None, *ignored):\r | |
923 | """Produce HTML documentation for a data object."""\r | |
924 | lhs = name and '<strong>%s</strong> = ' % name or ''\r | |
925 | return lhs + self.repr(object)\r | |
926 | \r | |
927 | def docdata(self, object, name=None, mod=None, cl=None):\r | |
928 | """Produce html documentation for a data descriptor."""\r | |
929 | return self._docdescriptor(name, object, mod)\r | |
930 | \r | |
931 | def index(self, dir, shadowed=None):\r | |
932 | """Generate an HTML index for a directory of modules."""\r | |
933 | modpkgs = []\r | |
934 | if shadowed is None: shadowed = {}\r | |
935 | for importer, name, ispkg in pkgutil.iter_modules([dir]):\r | |
936 | modpkgs.append((name, '', ispkg, name in shadowed))\r | |
937 | shadowed[name] = 1\r | |
938 | \r | |
939 | modpkgs.sort()\r | |
940 | contents = self.multicolumn(modpkgs, self.modpkglink)\r | |
941 | return self.bigsection(dir, '#ffffff', '#ee77aa', contents)\r | |
942 | \r | |
943 | # -------------------------------------------- text documentation generator\r | |
944 | \r | |
945 | class TextRepr(Repr):\r | |
946 | """Class for safely making a text representation of a Python object."""\r | |
947 | def __init__(self):\r | |
948 | Repr.__init__(self)\r | |
949 | self.maxlist = self.maxtuple = 20\r | |
950 | self.maxdict = 10\r | |
951 | self.maxstring = self.maxother = 100\r | |
952 | \r | |
953 | def repr1(self, x, level):\r | |
954 | if hasattr(type(x), '__name__'):\r | |
955 | methodname = 'repr_' + join(split(type(x).__name__), '_')\r | |
956 | if hasattr(self, methodname):\r | |
957 | return getattr(self, methodname)(x, level)\r | |
958 | return cram(stripid(repr(x)), self.maxother)\r | |
959 | \r | |
960 | def repr_string(self, x, level):\r | |
961 | test = cram(x, self.maxstring)\r | |
962 | testrepr = repr(test)\r | |
963 | if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):\r | |
964 | # Backslashes are only literal in the string and are never\r | |
965 | # needed to make any special characters, so show a raw string.\r | |
966 | return 'r' + testrepr[0] + test + testrepr[0]\r | |
967 | return testrepr\r | |
968 | \r | |
969 | repr_str = repr_string\r | |
970 | \r | |
971 | def repr_instance(self, x, level):\r | |
972 | try:\r | |
973 | return cram(stripid(repr(x)), self.maxstring)\r | |
974 | except:\r | |
975 | return '<%s instance>' % x.__class__.__name__\r | |
976 | \r | |
977 | class TextDoc(Doc):\r | |
978 | """Formatter class for text documentation."""\r | |
979 | \r | |
980 | # ------------------------------------------- text formatting utilities\r | |
981 | \r | |
982 | _repr_instance = TextRepr()\r | |
983 | repr = _repr_instance.repr\r | |
984 | \r | |
985 | def bold(self, text):\r | |
986 | """Format a string in bold by overstriking."""\r | |
987 | return join(map(lambda ch: ch + '\b' + ch, text), '')\r | |
988 | \r | |
989 | def indent(self, text, prefix=' '):\r | |
990 | """Indent text by prepending a given prefix to each line."""\r | |
991 | if not text: return ''\r | |
992 | lines = split(text, '\n')\r | |
993 | lines = map(lambda line, prefix=prefix: prefix + line, lines)\r | |
994 | if lines: lines[-1] = rstrip(lines[-1])\r | |
995 | return join(lines, '\n')\r | |
996 | \r | |
997 | def section(self, title, contents):\r | |
998 | """Format a section with a given heading."""\r | |
999 | return self.bold(title) + '\n' + rstrip(self.indent(contents)) + '\n\n'\r | |
1000 | \r | |
1001 | # ---------------------------------------------- type-specific routines\r | |
1002 | \r | |
1003 | def formattree(self, tree, modname, parent=None, prefix=''):\r | |
1004 | """Render in text a class tree as returned by inspect.getclasstree()."""\r | |
1005 | result = ''\r | |
1006 | for entry in tree:\r | |
1007 | if type(entry) is type(()):\r | |
1008 | c, bases = entry\r | |
1009 | result = result + prefix + classname(c, modname)\r | |
1010 | if bases and bases != (parent,):\r | |
1011 | parents = map(lambda c, m=modname: classname(c, m), bases)\r | |
1012 | result = result + '(%s)' % join(parents, ', ')\r | |
1013 | result = result + '\n'\r | |
1014 | elif type(entry) is type([]):\r | |
1015 | result = result + self.formattree(\r | |
1016 | entry, modname, c, prefix + ' ')\r | |
1017 | return result\r | |
1018 | \r | |
1019 | def docmodule(self, object, name=None, mod=None):\r | |
1020 | """Produce text documentation for a given module object."""\r | |
1021 | name = object.__name__ # ignore the passed-in name\r | |
1022 | synop, desc = splitdoc(getdoc(object))\r | |
1023 | result = self.section('NAME', name + (synop and ' - ' + synop))\r | |
1024 | \r | |
1025 | try:\r | |
1026 | all = object.__all__\r | |
1027 | except AttributeError:\r | |
1028 | all = None\r | |
1029 | \r | |
1030 | try:\r | |
1031 | file = inspect.getabsfile(object)\r | |
1032 | except TypeError:\r | |
1033 | file = '(built-in)'\r | |
1034 | result = result + self.section('FILE', file)\r | |
1035 | \r | |
1036 | docloc = self.getdocloc(object)\r | |
1037 | if docloc is not None:\r | |
1038 | result = result + self.section('MODULE DOCS', docloc)\r | |
1039 | \r | |
1040 | if desc:\r | |
1041 | result = result + self.section('DESCRIPTION', desc)\r | |
1042 | \r | |
1043 | classes = []\r | |
1044 | for key, value in inspect.getmembers(object, inspect.isclass):\r | |
1045 | # if __all__ exists, believe it. Otherwise use old heuristic.\r | |
1046 | if (all is not None\r | |
1047 | or (inspect.getmodule(value) or object) is object):\r | |
1048 | if visiblename(key, all, object):\r | |
1049 | classes.append((key, value))\r | |
1050 | funcs = []\r | |
1051 | for key, value in inspect.getmembers(object, inspect.isroutine):\r | |
1052 | # if __all__ exists, believe it. Otherwise use old heuristic.\r | |
1053 | if (all is not None or\r | |
1054 | inspect.isbuiltin(value) or inspect.getmodule(value) is object):\r | |
1055 | if visiblename(key, all, object):\r | |
1056 | funcs.append((key, value))\r | |
1057 | data = []\r | |
1058 | for key, value in inspect.getmembers(object, isdata):\r | |
1059 | if visiblename(key, all, object):\r | |
1060 | data.append((key, value))\r | |
1061 | \r | |
1062 | modpkgs = []\r | |
1063 | modpkgs_names = set()\r | |
1064 | if hasattr(object, '__path__'):\r | |
1065 | for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):\r | |
1066 | modpkgs_names.add(modname)\r | |
1067 | if ispkg:\r | |
1068 | modpkgs.append(modname + ' (package)')\r | |
1069 | else:\r | |
1070 | modpkgs.append(modname)\r | |
1071 | \r | |
1072 | modpkgs.sort()\r | |
1073 | result = result + self.section(\r | |
1074 | 'PACKAGE CONTENTS', join(modpkgs, '\n'))\r | |
1075 | \r | |
1076 | # Detect submodules as sometimes created by C extensions\r | |
1077 | submodules = []\r | |
1078 | for key, value in inspect.getmembers(object, inspect.ismodule):\r | |
1079 | if value.__name__.startswith(name + '.') and key not in modpkgs_names:\r | |
1080 | submodules.append(key)\r | |
1081 | if submodules:\r | |
1082 | submodules.sort()\r | |
1083 | result = result + self.section(\r | |
1084 | 'SUBMODULES', join(submodules, '\n'))\r | |
1085 | \r | |
1086 | if classes:\r | |
1087 | classlist = map(lambda key_value: key_value[1], classes)\r | |
1088 | contents = [self.formattree(\r | |
1089 | inspect.getclasstree(classlist, 1), name)]\r | |
1090 | for key, value in classes:\r | |
1091 | contents.append(self.document(value, key, name))\r | |
1092 | result = result + self.section('CLASSES', join(contents, '\n'))\r | |
1093 | \r | |
1094 | if funcs:\r | |
1095 | contents = []\r | |
1096 | for key, value in funcs:\r | |
1097 | contents.append(self.document(value, key, name))\r | |
1098 | result = result + self.section('FUNCTIONS', join(contents, '\n'))\r | |
1099 | \r | |
1100 | if data:\r | |
1101 | contents = []\r | |
1102 | for key, value in data:\r | |
1103 | contents.append(self.docother(value, key, name, maxlen=70))\r | |
1104 | result = result + self.section('DATA', join(contents, '\n'))\r | |
1105 | \r | |
1106 | if hasattr(object, '__version__'):\r | |
1107 | version = str(object.__version__)\r | |
1108 | if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':\r | |
1109 | version = strip(version[11:-1])\r | |
1110 | result = result + self.section('VERSION', version)\r | |
1111 | if hasattr(object, '__date__'):\r | |
1112 | result = result + self.section('DATE', str(object.__date__))\r | |
1113 | if hasattr(object, '__author__'):\r | |
1114 | result = result + self.section('AUTHOR', str(object.__author__))\r | |
1115 | if hasattr(object, '__credits__'):\r | |
1116 | result = result + self.section('CREDITS', str(object.__credits__))\r | |
1117 | return result\r | |
1118 | \r | |
1119 | def docclass(self, object, name=None, mod=None, *ignored):\r | |
1120 | """Produce text documentation for a given class object."""\r | |
1121 | realname = object.__name__\r | |
1122 | name = name or realname\r | |
1123 | bases = object.__bases__\r | |
1124 | \r | |
1125 | def makename(c, m=object.__module__):\r | |
1126 | return classname(c, m)\r | |
1127 | \r | |
1128 | if name == realname:\r | |
1129 | title = 'class ' + self.bold(realname)\r | |
1130 | else:\r | |
1131 | title = self.bold(name) + ' = class ' + realname\r | |
1132 | if bases:\r | |
1133 | parents = map(makename, bases)\r | |
1134 | title = title + '(%s)' % join(parents, ', ')\r | |
1135 | \r | |
1136 | doc = getdoc(object)\r | |
1137 | contents = doc and [doc + '\n'] or []\r | |
1138 | push = contents.append\r | |
1139 | \r | |
1140 | # List the mro, if non-trivial.\r | |
1141 | mro = deque(inspect.getmro(object))\r | |
1142 | if len(mro) > 2:\r | |
1143 | push("Method resolution order:")\r | |
1144 | for base in mro:\r | |
1145 | push(' ' + makename(base))\r | |
1146 | push('')\r | |
1147 | \r | |
1148 | # Cute little class to pump out a horizontal rule between sections.\r | |
1149 | class HorizontalRule:\r | |
1150 | def __init__(self):\r | |
1151 | self.needone = 0\r | |
1152 | def maybe(self):\r | |
1153 | if self.needone:\r | |
1154 | push('-' * 70)\r | |
1155 | self.needone = 1\r | |
1156 | hr = HorizontalRule()\r | |
1157 | \r | |
1158 | def spill(msg, attrs, predicate):\r | |
1159 | ok, attrs = _split_list(attrs, predicate)\r | |
1160 | if ok:\r | |
1161 | hr.maybe()\r | |
1162 | push(msg)\r | |
1163 | for name, kind, homecls, value in ok:\r | |
1164 | push(self.document(getattr(object, name),\r | |
1165 | name, mod, object))\r | |
1166 | return attrs\r | |
1167 | \r | |
1168 | def spilldescriptors(msg, attrs, predicate):\r | |
1169 | ok, attrs = _split_list(attrs, predicate)\r | |
1170 | if ok:\r | |
1171 | hr.maybe()\r | |
1172 | push(msg)\r | |
1173 | for name, kind, homecls, value in ok:\r | |
1174 | push(self._docdescriptor(name, value, mod))\r | |
1175 | return attrs\r | |
1176 | \r | |
1177 | def spilldata(msg, attrs, predicate):\r | |
1178 | ok, attrs = _split_list(attrs, predicate)\r | |
1179 | if ok:\r | |
1180 | hr.maybe()\r | |
1181 | push(msg)\r | |
1182 | for name, kind, homecls, value in ok:\r | |
1183 | if (hasattr(value, '__call__') or\r | |
1184 | inspect.isdatadescriptor(value)):\r | |
1185 | doc = getdoc(value)\r | |
1186 | else:\r | |
1187 | doc = None\r | |
1188 | push(self.docother(getattr(object, name),\r | |
1189 | name, mod, maxlen=70, doc=doc) + '\n')\r | |
1190 | return attrs\r | |
1191 | \r | |
1192 | attrs = filter(lambda data: visiblename(data[0], obj=object),\r | |
1193 | classify_class_attrs(object))\r | |
1194 | while attrs:\r | |
1195 | if mro:\r | |
1196 | thisclass = mro.popleft()\r | |
1197 | else:\r | |
1198 | thisclass = attrs[0][2]\r | |
1199 | attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)\r | |
1200 | \r | |
1201 | if thisclass is __builtin__.object:\r | |
1202 | attrs = inherited\r | |
1203 | continue\r | |
1204 | elif thisclass is object:\r | |
1205 | tag = "defined here"\r | |
1206 | else:\r | |
1207 | tag = "inherited from %s" % classname(thisclass,\r | |
1208 | object.__module__)\r | |
1209 | \r | |
1210 | # Sort attrs by name.\r | |
1211 | attrs.sort()\r | |
1212 | \r | |
1213 | # Pump out the attrs, segregated by kind.\r | |
1214 | attrs = spill("Methods %s:\n" % tag, attrs,\r | |
1215 | lambda t: t[1] == 'method')\r | |
1216 | attrs = spill("Class methods %s:\n" % tag, attrs,\r | |
1217 | lambda t: t[1] == 'class method')\r | |
1218 | attrs = spill("Static methods %s:\n" % tag, attrs,\r | |
1219 | lambda t: t[1] == 'static method')\r | |
1220 | attrs = spilldescriptors("Data descriptors %s:\n" % tag, attrs,\r | |
1221 | lambda t: t[1] == 'data descriptor')\r | |
1222 | attrs = spilldata("Data and other attributes %s:\n" % tag, attrs,\r | |
1223 | lambda t: t[1] == 'data')\r | |
1224 | assert attrs == []\r | |
1225 | attrs = inherited\r | |
1226 | \r | |
1227 | contents = '\n'.join(contents)\r | |
1228 | if not contents:\r | |
1229 | return title + '\n'\r | |
1230 | return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'\r | |
1231 | \r | |
1232 | def formatvalue(self, object):\r | |
1233 | """Format an argument default value as text."""\r | |
1234 | return '=' + self.repr(object)\r | |
1235 | \r | |
1236 | def docroutine(self, object, name=None, mod=None, cl=None):\r | |
1237 | """Produce text documentation for a function or method object."""\r | |
1238 | realname = object.__name__\r | |
1239 | name = name or realname\r | |
1240 | note = ''\r | |
1241 | skipdocs = 0\r | |
1242 | if inspect.ismethod(object):\r | |
1243 | imclass = object.im_class\r | |
1244 | if cl:\r | |
1245 | if imclass is not cl:\r | |
1246 | note = ' from ' + classname(imclass, mod)\r | |
1247 | else:\r | |
1248 | if object.im_self is not None:\r | |
1249 | note = ' method of %s instance' % classname(\r | |
1250 | object.im_self.__class__, mod)\r | |
1251 | else:\r | |
1252 | note = ' unbound %s method' % classname(imclass,mod)\r | |
1253 | object = object.im_func\r | |
1254 | \r | |
1255 | if name == realname:\r | |
1256 | title = self.bold(realname)\r | |
1257 | else:\r | |
1258 | if (cl and realname in cl.__dict__ and\r | |
1259 | cl.__dict__[realname] is object):\r | |
1260 | skipdocs = 1\r | |
1261 | title = self.bold(name) + ' = ' + realname\r | |
1262 | if inspect.isfunction(object):\r | |
1263 | args, varargs, varkw, defaults = inspect.getargspec(object)\r | |
1264 | argspec = inspect.formatargspec(\r | |
1265 | args, varargs, varkw, defaults, formatvalue=self.formatvalue)\r | |
1266 | if realname == '<lambda>':\r | |
1267 | title = self.bold(name) + ' lambda '\r | |
1268 | argspec = argspec[1:-1] # remove parentheses\r | |
1269 | else:\r | |
1270 | argspec = '(...)'\r | |
1271 | decl = title + argspec + note\r | |
1272 | \r | |
1273 | if skipdocs:\r | |
1274 | return decl + '\n'\r | |
1275 | else:\r | |
1276 | doc = getdoc(object) or ''\r | |
1277 | return decl + '\n' + (doc and rstrip(self.indent(doc)) + '\n')\r | |
1278 | \r | |
1279 | def _docdescriptor(self, name, value, mod):\r | |
1280 | results = []\r | |
1281 | push = results.append\r | |
1282 | \r | |
1283 | if name:\r | |
1284 | push(self.bold(name))\r | |
1285 | push('\n')\r | |
1286 | doc = getdoc(value) or ''\r | |
1287 | if doc:\r | |
1288 | push(self.indent(doc))\r | |
1289 | push('\n')\r | |
1290 | return ''.join(results)\r | |
1291 | \r | |
1292 | def docproperty(self, object, name=None, mod=None, cl=None):\r | |
1293 | """Produce text documentation for a property."""\r | |
1294 | return self._docdescriptor(name, object, mod)\r | |
1295 | \r | |
1296 | def docdata(self, object, name=None, mod=None, cl=None):\r | |
1297 | """Produce text documentation for a data descriptor."""\r | |
1298 | return self._docdescriptor(name, object, mod)\r | |
1299 | \r | |
1300 | def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):\r | |
1301 | """Produce text documentation for a data object."""\r | |
1302 | repr = self.repr(object)\r | |
1303 | if maxlen:\r | |
1304 | line = (name and name + ' = ' or '') + repr\r | |
1305 | chop = maxlen - len(line)\r | |
1306 | if chop < 0: repr = repr[:chop] + '...'\r | |
1307 | line = (name and self.bold(name) + ' = ' or '') + repr\r | |
1308 | if doc is not None:\r | |
1309 | line += '\n' + self.indent(str(doc))\r | |
1310 | return line\r | |
1311 | \r | |
1312 | # --------------------------------------------------------- user interfaces\r | |
1313 | \r | |
1314 | def pager(text):\r | |
1315 | """The first time this is called, determine what kind of pager to use."""\r | |
1316 | global pager\r | |
1317 | pager = getpager()\r | |
1318 | pager(text)\r | |
1319 | \r | |
1320 | def getpager():\r | |
1321 | """Decide what method to use for paging through text."""\r | |
1322 | if type(sys.stdout) is not types.FileType:\r | |
1323 | return plainpager\r | |
1324 | if not sys.stdin.isatty() or not sys.stdout.isatty():\r | |
1325 | return plainpager\r | |
1326 | if 'PAGER' in os.environ:\r | |
1327 | if sys.platform == 'win32': # pipes completely broken in Windows\r | |
1328 | return lambda text: tempfilepager(plain(text), os.environ['PAGER'])\r | |
1329 | elif os.environ.get('TERM') in ('dumb', 'emacs'):\r | |
1330 | return lambda text: pipepager(plain(text), os.environ['PAGER'])\r | |
1331 | else:\r | |
1332 | return lambda text: pipepager(text, os.environ['PAGER'])\r | |
1333 | if os.environ.get('TERM') in ('dumb', 'emacs'):\r | |
1334 | return plainpager\r | |
1335 | if sys.platform == 'win32' or sys.platform.startswith('os2'):\r | |
1336 | return lambda text: tempfilepager(plain(text), 'more <')\r | |
1337 | if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:\r | |
1338 | return lambda text: pipepager(text, 'less')\r | |
1339 | \r | |
1340 | import tempfile\r | |
1341 | (fd, filename) = tempfile.mkstemp()\r | |
1342 | os.close(fd)\r | |
1343 | try:\r | |
1344 | if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:\r | |
1345 | return lambda text: pipepager(text, 'more')\r | |
1346 | else:\r | |
1347 | return ttypager\r | |
1348 | finally:\r | |
1349 | os.unlink(filename)\r | |
1350 | \r | |
1351 | def plain(text):\r | |
1352 | """Remove boldface formatting from text."""\r | |
1353 | return re.sub('.\b', '', text)\r | |
1354 | \r | |
1355 | def pipepager(text, cmd):\r | |
1356 | """Page through text by feeding it to another program."""\r | |
1357 | pipe = os.popen(cmd, 'w')\r | |
1358 | try:\r | |
1359 | pipe.write(text)\r | |
1360 | pipe.close()\r | |
1361 | except IOError:\r | |
1362 | pass # Ignore broken pipes caused by quitting the pager program.\r | |
1363 | \r | |
1364 | def tempfilepager(text, cmd):\r | |
1365 | """Page through text by invoking a program on a temporary file."""\r | |
1366 | import tempfile\r | |
1367 | filename = tempfile.mktemp()\r | |
1368 | file = open(filename, 'w')\r | |
1369 | file.write(text)\r | |
1370 | file.close()\r | |
1371 | try:\r | |
1372 | os.system(cmd + ' "' + filename + '"')\r | |
1373 | finally:\r | |
1374 | os.unlink(filename)\r | |
1375 | \r | |
1376 | def ttypager(text):\r | |
1377 | """Page through text on a text terminal."""\r | |
1378 | lines = split(plain(text), '\n')\r | |
1379 | try:\r | |
1380 | import tty\r | |
1381 | fd = sys.stdin.fileno()\r | |
1382 | old = tty.tcgetattr(fd)\r | |
1383 | tty.setcbreak(fd)\r | |
1384 | getchar = lambda: sys.stdin.read(1)\r | |
1385 | except (ImportError, AttributeError):\r | |
1386 | tty = None\r | |
1387 | getchar = lambda: sys.stdin.readline()[:-1][:1]\r | |
1388 | \r | |
1389 | try:\r | |
1390 | r = inc = os.environ.get('LINES', 25) - 1\r | |
1391 | sys.stdout.write(join(lines[:inc], '\n') + '\n')\r | |
1392 | while lines[r:]:\r | |
1393 | sys.stdout.write('-- more --')\r | |
1394 | sys.stdout.flush()\r | |
1395 | c = getchar()\r | |
1396 | \r | |
1397 | if c in ('q', 'Q'):\r | |
1398 | sys.stdout.write('\r \r')\r | |
1399 | break\r | |
1400 | elif c in ('\r', '\n'):\r | |
1401 | sys.stdout.write('\r \r' + lines[r] + '\n')\r | |
1402 | r = r + 1\r | |
1403 | continue\r | |
1404 | if c in ('b', 'B', '\x1b'):\r | |
1405 | r = r - inc - inc\r | |
1406 | if r < 0: r = 0\r | |
1407 | sys.stdout.write('\n' + join(lines[r:r+inc], '\n') + '\n')\r | |
1408 | r = r + inc\r | |
1409 | \r | |
1410 | finally:\r | |
1411 | if tty:\r | |
1412 | tty.tcsetattr(fd, tty.TCSAFLUSH, old)\r | |
1413 | \r | |
1414 | def plainpager(text):\r | |
1415 | """Simply print unformatted text. This is the ultimate fallback."""\r | |
1416 | sys.stdout.write(plain(text))\r | |
1417 | \r | |
1418 | def describe(thing):\r | |
1419 | """Produce a short description of the given thing."""\r | |
1420 | if inspect.ismodule(thing):\r | |
1421 | if thing.__name__ in sys.builtin_module_names:\r | |
1422 | return 'built-in module ' + thing.__name__\r | |
1423 | if hasattr(thing, '__path__'):\r | |
1424 | return 'package ' + thing.__name__\r | |
1425 | else:\r | |
1426 | return 'module ' + thing.__name__\r | |
1427 | if inspect.isbuiltin(thing):\r | |
1428 | return 'built-in function ' + thing.__name__\r | |
1429 | if inspect.isgetsetdescriptor(thing):\r | |
1430 | return 'getset descriptor %s.%s.%s' % (\r | |
1431 | thing.__objclass__.__module__, thing.__objclass__.__name__,\r | |
1432 | thing.__name__)\r | |
1433 | if inspect.ismemberdescriptor(thing):\r | |
1434 | return 'member descriptor %s.%s.%s' % (\r | |
1435 | thing.__objclass__.__module__, thing.__objclass__.__name__,\r | |
1436 | thing.__name__)\r | |
1437 | if inspect.isclass(thing):\r | |
1438 | return 'class ' + thing.__name__\r | |
1439 | if inspect.isfunction(thing):\r | |
1440 | return 'function ' + thing.__name__\r | |
1441 | if inspect.ismethod(thing):\r | |
1442 | return 'method ' + thing.__name__\r | |
1443 | if type(thing) is types.InstanceType:\r | |
1444 | return 'instance of ' + thing.__class__.__name__\r | |
1445 | return type(thing).__name__\r | |
1446 | \r | |
1447 | def locate(path, forceload=0):\r | |
1448 | """Locate an object by name or dotted path, importing as necessary."""\r | |
1449 | parts = [part for part in split(path, '.') if part]\r | |
1450 | module, n = None, 0\r | |
1451 | while n < len(parts):\r | |
1452 | nextmodule = safeimport(join(parts[:n+1], '.'), forceload)\r | |
1453 | if nextmodule: module, n = nextmodule, n + 1\r | |
1454 | else: break\r | |
1455 | if module:\r | |
1456 | object = module\r | |
1457 | for part in parts[n:]:\r | |
1458 | try: object = getattr(object, part)\r | |
1459 | except AttributeError: return None\r | |
1460 | return object\r | |
1461 | else:\r | |
1462 | if hasattr(__builtin__, path):\r | |
1463 | return getattr(__builtin__, path)\r | |
1464 | \r | |
1465 | # --------------------------------------- interactive interpreter interface\r | |
1466 | \r | |
1467 | text = TextDoc()\r | |
1468 | html = HTMLDoc()\r | |
1469 | \r | |
1470 | class _OldStyleClass: pass\r | |
1471 | _OLD_INSTANCE_TYPE = type(_OldStyleClass())\r | |
1472 | \r | |
1473 | def resolve(thing, forceload=0):\r | |
1474 | """Given an object or a path to an object, get the object and its name."""\r | |
1475 | if isinstance(thing, str):\r | |
1476 | object = locate(thing, forceload)\r | |
1477 | if not object:\r | |
1478 | raise ImportError, 'no Python documentation found for %r' % thing\r | |
1479 | return object, thing\r | |
1480 | else:\r | |
1481 | return thing, getattr(thing, '__name__', None)\r | |
1482 | \r | |
1483 | def render_doc(thing, title='Python Library Documentation: %s', forceload=0):\r | |
1484 | """Render text documentation, given an object or a path to an object."""\r | |
1485 | object, name = resolve(thing, forceload)\r | |
1486 | desc = describe(object)\r | |
1487 | module = inspect.getmodule(object)\r | |
1488 | if name and '.' in name:\r | |
1489 | desc += ' in ' + name[:name.rfind('.')]\r | |
1490 | elif module and module is not object:\r | |
1491 | desc += ' in module ' + module.__name__\r | |
1492 | if type(object) is _OLD_INSTANCE_TYPE:\r | |
1493 | # If the passed object is an instance of an old-style class,\r | |
1494 | # document its available methods instead of its value.\r | |
1495 | object = object.__class__\r | |
1496 | elif not (inspect.ismodule(object) or\r | |
1497 | inspect.isclass(object) or\r | |
1498 | inspect.isroutine(object) or\r | |
1499 | inspect.isgetsetdescriptor(object) or\r | |
1500 | inspect.ismemberdescriptor(object) or\r | |
1501 | isinstance(object, property)):\r | |
1502 | # If the passed object is a piece of data or an instance,\r | |
1503 | # document its available methods instead of its value.\r | |
1504 | object = type(object)\r | |
1505 | desc += ' object'\r | |
1506 | return title % desc + '\n\n' + text.document(object, name)\r | |
1507 | \r | |
1508 | def doc(thing, title='Python Library Documentation: %s', forceload=0):\r | |
1509 | """Display text documentation, given an object or a path to an object."""\r | |
1510 | try:\r | |
1511 | pager(render_doc(thing, title, forceload))\r | |
1512 | except (ImportError, ErrorDuringImport), value:\r | |
1513 | print value\r | |
1514 | \r | |
1515 | def writedoc(thing, forceload=0):\r | |
1516 | """Write HTML documentation to a file in the current directory."""\r | |
1517 | try:\r | |
1518 | object, name = resolve(thing, forceload)\r | |
1519 | page = html.page(describe(object), html.document(object, name))\r | |
1520 | file = open(name + '.html', 'w')\r | |
1521 | file.write(page)\r | |
1522 | file.close()\r | |
1523 | print 'wrote', name + '.html'\r | |
1524 | except (ImportError, ErrorDuringImport), value:\r | |
1525 | print value\r | |
1526 | \r | |
1527 | def writedocs(dir, pkgpath='', done=None):\r | |
1528 | """Write out HTML documentation for all modules in a directory tree."""\r | |
1529 | if done is None: done = {}\r | |
1530 | for importer, modname, ispkg in pkgutil.walk_packages([dir], pkgpath):\r | |
1531 | writedoc(modname)\r | |
1532 | return\r | |
1533 | \r | |
1534 | class Helper:\r | |
1535 | \r | |
1536 | # These dictionaries map a topic name to either an alias, or a tuple\r | |
1537 | # (label, seealso-items). The "label" is the label of the corresponding\r | |
1538 | # section in the .rst file under Doc/ and an index into the dictionary\r | |
1539 | # in pydoc_data/topics.py.\r | |
1540 | #\r | |
1541 | # CAUTION: if you change one of these dictionaries, be sure to adapt the\r | |
1542 | # list of needed labels in Doc/tools/sphinxext/pyspecific.py and\r | |
1543 | # regenerate the pydoc_data/topics.py file by running\r | |
1544 | # make pydoc-topics\r | |
1545 | # in Doc/ and copying the output file into the Lib/ directory.\r | |
1546 | \r | |
1547 | keywords = {\r | |
1548 | 'and': 'BOOLEAN',\r | |
1549 | 'as': 'with',\r | |
1550 | 'assert': ('assert', ''),\r | |
1551 | 'break': ('break', 'while for'),\r | |
1552 | 'class': ('class', 'CLASSES SPECIALMETHODS'),\r | |
1553 | 'continue': ('continue', 'while for'),\r | |
1554 | 'def': ('function', ''),\r | |
1555 | 'del': ('del', 'BASICMETHODS'),\r | |
1556 | 'elif': 'if',\r | |
1557 | 'else': ('else', 'while for'),\r | |
1558 | 'except': 'try',\r | |
1559 | 'exec': ('exec', ''),\r | |
1560 | 'finally': 'try',\r | |
1561 | 'for': ('for', 'break continue while'),\r | |
1562 | 'from': 'import',\r | |
1563 | 'global': ('global', 'NAMESPACES'),\r | |
1564 | 'if': ('if', 'TRUTHVALUE'),\r | |
1565 | 'import': ('import', 'MODULES'),\r | |
1566 | 'in': ('in', 'SEQUENCEMETHODS2'),\r | |
1567 | 'is': 'COMPARISON',\r | |
1568 | 'lambda': ('lambda', 'FUNCTIONS'),\r | |
1569 | 'not': 'BOOLEAN',\r | |
1570 | 'or': 'BOOLEAN',\r | |
1571 | 'pass': ('pass', ''),\r | |
1572 | 'print': ('print', ''),\r | |
1573 | 'raise': ('raise', 'EXCEPTIONS'),\r | |
1574 | 'return': ('return', 'FUNCTIONS'),\r | |
1575 | 'try': ('try', 'EXCEPTIONS'),\r | |
1576 | 'while': ('while', 'break continue if TRUTHVALUE'),\r | |
1577 | 'with': ('with', 'CONTEXTMANAGERS EXCEPTIONS yield'),\r | |
1578 | 'yield': ('yield', ''),\r | |
1579 | }\r | |
1580 | # Either add symbols to this dictionary or to the symbols dictionary\r | |
1581 | # directly: Whichever is easier. They are merged later.\r | |
1582 | _symbols_inverse = {\r | |
1583 | 'STRINGS' : ("'", "'''", "r'", "u'", '"""', '"', 'r"', 'u"'),\r | |
1584 | 'OPERATORS' : ('+', '-', '*', '**', '/', '//', '%', '<<', '>>', '&',\r | |
1585 | '|', '^', '~', '<', '>', '<=', '>=', '==', '!=', '<>'),\r | |
1586 | 'COMPARISON' : ('<', '>', '<=', '>=', '==', '!=', '<>'),\r | |
1587 | 'UNARY' : ('-', '~'),\r | |
1588 | 'AUGMENTEDASSIGNMENT' : ('+=', '-=', '*=', '/=', '%=', '&=', '|=',\r | |
1589 | '^=', '<<=', '>>=', '**=', '//='),\r | |
1590 | 'BITWISE' : ('<<', '>>', '&', '|', '^', '~'),\r | |
1591 | 'COMPLEX' : ('j', 'J')\r | |
1592 | }\r | |
1593 | symbols = {\r | |
1594 | '%': 'OPERATORS FORMATTING',\r | |
1595 | '**': 'POWER',\r | |
1596 | ',': 'TUPLES LISTS FUNCTIONS',\r | |
1597 | '.': 'ATTRIBUTES FLOAT MODULES OBJECTS',\r | |
1598 | '...': 'ELLIPSIS',\r | |
1599 | ':': 'SLICINGS DICTIONARYLITERALS',\r | |
1600 | '@': 'def class',\r | |
1601 | '\\': 'STRINGS',\r | |
1602 | '_': 'PRIVATENAMES',\r | |
1603 | '__': 'PRIVATENAMES SPECIALMETHODS',\r | |
1604 | '`': 'BACKQUOTES',\r | |
1605 | '(': 'TUPLES FUNCTIONS CALLS',\r | |
1606 | ')': 'TUPLES FUNCTIONS CALLS',\r | |
1607 | '[': 'LISTS SUBSCRIPTS SLICINGS',\r | |
1608 | ']': 'LISTS SUBSCRIPTS SLICINGS'\r | |
1609 | }\r | |
1610 | for topic, symbols_ in _symbols_inverse.iteritems():\r | |
1611 | for symbol in symbols_:\r | |
1612 | topics = symbols.get(symbol, topic)\r | |
1613 | if topic not in topics:\r | |
1614 | topics = topics + ' ' + topic\r | |
1615 | symbols[symbol] = topics\r | |
1616 | \r | |
1617 | topics = {\r | |
1618 | 'TYPES': ('types', 'STRINGS UNICODE NUMBERS SEQUENCES MAPPINGS '\r | |
1619 | 'FUNCTIONS CLASSES MODULES FILES inspect'),\r | |
1620 | 'STRINGS': ('strings', 'str UNICODE SEQUENCES STRINGMETHODS FORMATTING '\r | |
1621 | 'TYPES'),\r | |
1622 | 'STRINGMETHODS': ('string-methods', 'STRINGS FORMATTING'),\r | |
1623 | 'FORMATTING': ('formatstrings', 'OPERATORS'),\r | |
1624 | 'UNICODE': ('strings', 'encodings unicode SEQUENCES STRINGMETHODS '\r | |
1625 | 'FORMATTING TYPES'),\r | |
1626 | 'NUMBERS': ('numbers', 'INTEGER FLOAT COMPLEX TYPES'),\r | |
1627 | 'INTEGER': ('integers', 'int range'),\r | |
1628 | 'FLOAT': ('floating', 'float math'),\r | |
1629 | 'COMPLEX': ('imaginary', 'complex cmath'),\r | |
1630 | 'SEQUENCES': ('typesseq', 'STRINGMETHODS FORMATTING xrange LISTS'),\r | |
1631 | 'MAPPINGS': 'DICTIONARIES',\r | |
1632 | 'FUNCTIONS': ('typesfunctions', 'def TYPES'),\r | |
1633 | 'METHODS': ('typesmethods', 'class def CLASSES TYPES'),\r | |
1634 | 'CODEOBJECTS': ('bltin-code-objects', 'compile FUNCTIONS TYPES'),\r | |
1635 | 'TYPEOBJECTS': ('bltin-type-objects', 'types TYPES'),\r | |
1636 | 'FRAMEOBJECTS': 'TYPES',\r | |
1637 | 'TRACEBACKS': 'TYPES',\r | |
1638 | 'NONE': ('bltin-null-object', ''),\r | |
1639 | 'ELLIPSIS': ('bltin-ellipsis-object', 'SLICINGS'),\r | |
1640 | 'FILES': ('bltin-file-objects', ''),\r | |
1641 | 'SPECIALATTRIBUTES': ('specialattrs', ''),\r | |
1642 | 'CLASSES': ('types', 'class SPECIALMETHODS PRIVATENAMES'),\r | |
1643 | 'MODULES': ('typesmodules', 'import'),\r | |
1644 | 'PACKAGES': 'import',\r | |
1645 | 'EXPRESSIONS': ('operator-summary', 'lambda or and not in is BOOLEAN '\r | |
1646 | 'COMPARISON BITWISE SHIFTING BINARY FORMATTING POWER '\r | |
1647 | 'UNARY ATTRIBUTES SUBSCRIPTS SLICINGS CALLS TUPLES '\r | |
1648 | 'LISTS DICTIONARIES BACKQUOTES'),\r | |
1649 | 'OPERATORS': 'EXPRESSIONS',\r | |
1650 | 'PRECEDENCE': 'EXPRESSIONS',\r | |
1651 | 'OBJECTS': ('objects', 'TYPES'),\r | |
1652 | 'SPECIALMETHODS': ('specialnames', 'BASICMETHODS ATTRIBUTEMETHODS '\r | |
1653 | 'CALLABLEMETHODS SEQUENCEMETHODS1 MAPPINGMETHODS '\r | |
1654 | 'SEQUENCEMETHODS2 NUMBERMETHODS CLASSES'),\r | |
1655 | 'BASICMETHODS': ('customization', 'cmp hash repr str SPECIALMETHODS'),\r | |
1656 | 'ATTRIBUTEMETHODS': ('attribute-access', 'ATTRIBUTES SPECIALMETHODS'),\r | |
1657 | 'CALLABLEMETHODS': ('callable-types', 'CALLS SPECIALMETHODS'),\r | |
1658 | 'SEQUENCEMETHODS1': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS2 '\r | |
1659 | 'SPECIALMETHODS'),\r | |
1660 | 'SEQUENCEMETHODS2': ('sequence-methods', 'SEQUENCES SEQUENCEMETHODS1 '\r | |
1661 | 'SPECIALMETHODS'),\r | |
1662 | 'MAPPINGMETHODS': ('sequence-types', 'MAPPINGS SPECIALMETHODS'),\r | |
1663 | 'NUMBERMETHODS': ('numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT '\r | |
1664 | 'SPECIALMETHODS'),\r | |
1665 | 'EXECUTION': ('execmodel', 'NAMESPACES DYNAMICFEATURES EXCEPTIONS'),\r | |
1666 | 'NAMESPACES': ('naming', 'global ASSIGNMENT DELETION DYNAMICFEATURES'),\r | |
1667 | 'DYNAMICFEATURES': ('dynamic-features', ''),\r | |
1668 | 'SCOPING': 'NAMESPACES',\r | |
1669 | 'FRAMES': 'NAMESPACES',\r | |
1670 | 'EXCEPTIONS': ('exceptions', 'try except finally raise'),\r | |
1671 | 'COERCIONS': ('coercion-rules','CONVERSIONS'),\r | |
1672 | 'CONVERSIONS': ('conversions', 'COERCIONS'),\r | |
1673 | 'IDENTIFIERS': ('identifiers', 'keywords SPECIALIDENTIFIERS'),\r | |
1674 | 'SPECIALIDENTIFIERS': ('id-classes', ''),\r | |
1675 | 'PRIVATENAMES': ('atom-identifiers', ''),\r | |
1676 | 'LITERALS': ('atom-literals', 'STRINGS BACKQUOTES NUMBERS '\r | |
1677 | 'TUPLELITERALS LISTLITERALS DICTIONARYLITERALS'),\r | |
1678 | 'TUPLES': 'SEQUENCES',\r | |
1679 | 'TUPLELITERALS': ('exprlists', 'TUPLES LITERALS'),\r | |
1680 | 'LISTS': ('typesseq-mutable', 'LISTLITERALS'),\r | |
1681 | 'LISTLITERALS': ('lists', 'LISTS LITERALS'),\r | |
1682 | 'DICTIONARIES': ('typesmapping', 'DICTIONARYLITERALS'),\r | |
1683 | 'DICTIONARYLITERALS': ('dict', 'DICTIONARIES LITERALS'),\r | |
1684 | 'BACKQUOTES': ('string-conversions', 'repr str STRINGS LITERALS'),\r | |
1685 | 'ATTRIBUTES': ('attribute-references', 'getattr hasattr setattr '\r | |
1686 | 'ATTRIBUTEMETHODS'),\r | |
1687 | 'SUBSCRIPTS': ('subscriptions', 'SEQUENCEMETHODS1'),\r | |
1688 | 'SLICINGS': ('slicings', 'SEQUENCEMETHODS2'),\r | |
1689 | 'CALLS': ('calls', 'EXPRESSIONS'),\r | |
1690 | 'POWER': ('power', 'EXPRESSIONS'),\r | |
1691 | 'UNARY': ('unary', 'EXPRESSIONS'),\r | |
1692 | 'BINARY': ('binary', 'EXPRESSIONS'),\r | |
1693 | 'SHIFTING': ('shifting', 'EXPRESSIONS'),\r | |
1694 | 'BITWISE': ('bitwise', 'EXPRESSIONS'),\r | |
1695 | 'COMPARISON': ('comparisons', 'EXPRESSIONS BASICMETHODS'),\r | |
1696 | 'BOOLEAN': ('booleans', 'EXPRESSIONS TRUTHVALUE'),\r | |
1697 | 'ASSERTION': 'assert',\r | |
1698 | 'ASSIGNMENT': ('assignment', 'AUGMENTEDASSIGNMENT'),\r | |
1699 | 'AUGMENTEDASSIGNMENT': ('augassign', 'NUMBERMETHODS'),\r | |
1700 | 'DELETION': 'del',\r | |
1701 | 'PRINTING': 'print',\r | |
1702 | 'RETURNING': 'return',\r | |
1703 | 'IMPORTING': 'import',\r | |
1704 | 'CONDITIONAL': 'if',\r | |
1705 | 'LOOPING': ('compound', 'for while break continue'),\r | |
1706 | 'TRUTHVALUE': ('truth', 'if while and or not BASICMETHODS'),\r | |
1707 | 'DEBUGGING': ('debugger', 'pdb'),\r | |
1708 | 'CONTEXTMANAGERS': ('context-managers', 'with'),\r | |
1709 | }\r | |
1710 | \r | |
1711 | def __init__(self, input=None, output=None):\r | |
1712 | self._input = input\r | |
1713 | self._output = output\r | |
1714 | \r | |
1715 | input = property(lambda self: self._input or sys.stdin)\r | |
1716 | output = property(lambda self: self._output or sys.stdout)\r | |
1717 | \r | |
1718 | def __repr__(self):\r | |
1719 | if inspect.stack()[1][3] == '?':\r | |
1720 | self()\r | |
1721 | return ''\r | |
1722 | return '<pydoc.Helper instance>'\r | |
1723 | \r | |
1724 | _GoInteractive = object()\r | |
1725 | def __call__(self, request=_GoInteractive):\r | |
1726 | if request is not self._GoInteractive:\r | |
1727 | self.help(request)\r | |
1728 | else:\r | |
1729 | self.intro()\r | |
1730 | self.interact()\r | |
1731 | self.output.write('''\r | |
1732 | You are now leaving help and returning to the Python interpreter.\r | |
1733 | If you want to ask for help on a particular object directly from the\r | |
1734 | interpreter, you can type "help(object)". Executing "help('string')"\r | |
1735 | has the same effect as typing a particular string at the help> prompt.\r | |
1736 | ''')\r | |
1737 | \r | |
1738 | def interact(self):\r | |
1739 | self.output.write('\n')\r | |
1740 | while True:\r | |
1741 | try:\r | |
1742 | request = self.getline('help> ')\r | |
1743 | if not request: break\r | |
1744 | except (KeyboardInterrupt, EOFError):\r | |
1745 | break\r | |
1746 | request = strip(replace(request, '"', '', "'", ''))\r | |
1747 | if lower(request) in ('q', 'quit'): break\r | |
1748 | self.help(request)\r | |
1749 | \r | |
1750 | def getline(self, prompt):\r | |
1751 | """Read one line, using raw_input when available."""\r | |
1752 | if self.input is sys.stdin:\r | |
1753 | return raw_input(prompt)\r | |
1754 | else:\r | |
1755 | self.output.write(prompt)\r | |
1756 | self.output.flush()\r | |
1757 | return self.input.readline()\r | |
1758 | \r | |
1759 | def help(self, request):\r | |
1760 | if type(request) is type(''):\r | |
1761 | request = request.strip()\r | |
1762 | if request == 'help': self.intro()\r | |
1763 | elif request == 'keywords': self.listkeywords()\r | |
1764 | elif request == 'symbols': self.listsymbols()\r | |
1765 | elif request == 'topics': self.listtopics()\r | |
1766 | elif request == 'modules': self.listmodules()\r | |
1767 | elif request[:8] == 'modules ':\r | |
1768 | self.listmodules(split(request)[1])\r | |
1769 | elif request in self.symbols: self.showsymbol(request)\r | |
1770 | elif request in self.keywords: self.showtopic(request)\r | |
1771 | elif request in self.topics: self.showtopic(request)\r | |
1772 | elif request: doc(request, 'Help on %s:')\r | |
1773 | elif isinstance(request, Helper): self()\r | |
1774 | else: doc(request, 'Help on %s:')\r | |
1775 | self.output.write('\n')\r | |
1776 | \r | |
1777 | def intro(self):\r | |
1778 | self.output.write('''\r | |
1779 | Welcome to Python %s! This is the online help utility.\r | |
1780 | \r | |
1781 | If this is your first time using Python, you should definitely check out\r | |
1782 | the tutorial on the Internet at http://docs.python.org/tutorial/.\r | |
1783 | \r | |
1784 | Enter the name of any module, keyword, or topic to get help on writing\r | |
1785 | Python programs and using Python modules. To quit this help utility and\r | |
1786 | return to the interpreter, just type "quit".\r | |
1787 | \r | |
1788 | To get a list of available modules, keywords, or topics, type "modules",\r | |
1789 | "keywords", or "topics". Each module also comes with a one-line summary\r | |
1790 | of what it does; to list the modules whose summaries contain a given word\r | |
1791 | such as "spam", type "modules spam".\r | |
1792 | ''' % sys.version[:3])\r | |
1793 | \r | |
1794 | def list(self, items, columns=4, width=80):\r | |
1795 | items = items[:]\r | |
1796 | items.sort()\r | |
1797 | colw = width / columns\r | |
1798 | rows = (len(items) + columns - 1) / columns\r | |
1799 | for row in range(rows):\r | |
1800 | for col in range(columns):\r | |
1801 | i = col * rows + row\r | |
1802 | if i < len(items):\r | |
1803 | self.output.write(items[i])\r | |
1804 | if col < columns - 1:\r | |
1805 | self.output.write(' ' + ' ' * (colw-1 - len(items[i])))\r | |
1806 | self.output.write('\n')\r | |
1807 | \r | |
1808 | def listkeywords(self):\r | |
1809 | self.output.write('''\r | |
1810 | Here is a list of the Python keywords. Enter any keyword to get more help.\r | |
1811 | \r | |
1812 | ''')\r | |
1813 | self.list(self.keywords.keys())\r | |
1814 | \r | |
1815 | def listsymbols(self):\r | |
1816 | self.output.write('''\r | |
1817 | Here is a list of the punctuation symbols which Python assigns special meaning\r | |
1818 | to. Enter any symbol to get more help.\r | |
1819 | \r | |
1820 | ''')\r | |
1821 | self.list(self.symbols.keys())\r | |
1822 | \r | |
1823 | def listtopics(self):\r | |
1824 | self.output.write('''\r | |
1825 | Here is a list of available topics. Enter any topic name to get more help.\r | |
1826 | \r | |
1827 | ''')\r | |
1828 | self.list(self.topics.keys())\r | |
1829 | \r | |
1830 | def showtopic(self, topic, more_xrefs=''):\r | |
1831 | try:\r | |
1832 | import pydoc_data.topics\r | |
1833 | except ImportError:\r | |
1834 | self.output.write('''\r | |
1835 | Sorry, topic and keyword documentation is not available because the\r | |
1836 | module "pydoc_data.topics" could not be found.\r | |
1837 | ''')\r | |
1838 | return\r | |
1839 | target = self.topics.get(topic, self.keywords.get(topic))\r | |
1840 | if not target:\r | |
1841 | self.output.write('no documentation found for %s\n' % repr(topic))\r | |
1842 | return\r | |
1843 | if type(target) is type(''):\r | |
1844 | return self.showtopic(target, more_xrefs)\r | |
1845 | \r | |
1846 | label, xrefs = target\r | |
1847 | try:\r | |
1848 | doc = pydoc_data.topics.topics[label]\r | |
1849 | except KeyError:\r | |
1850 | self.output.write('no documentation found for %s\n' % repr(topic))\r | |
1851 | return\r | |
1852 | pager(strip(doc) + '\n')\r | |
1853 | if more_xrefs:\r | |
1854 | xrefs = (xrefs or '') + ' ' + more_xrefs\r | |
1855 | if xrefs:\r | |
1856 | import StringIO, formatter\r | |
1857 | buffer = StringIO.StringIO()\r | |
1858 | formatter.DumbWriter(buffer).send_flowing_data(\r | |
1859 | 'Related help topics: ' + join(split(xrefs), ', ') + '\n')\r | |
1860 | self.output.write('\n%s\n' % buffer.getvalue())\r | |
1861 | \r | |
1862 | def showsymbol(self, symbol):\r | |
1863 | target = self.symbols[symbol]\r | |
1864 | topic, _, xrefs = target.partition(' ')\r | |
1865 | self.showtopic(topic, xrefs)\r | |
1866 | \r | |
1867 | def listmodules(self, key=''):\r | |
1868 | if key:\r | |
1869 | self.output.write('''\r | |
1870 | Here is a list of matching modules. Enter any module name to get more help.\r | |
1871 | \r | |
1872 | ''')\r | |
1873 | apropos(key)\r | |
1874 | else:\r | |
1875 | self.output.write('''\r | |
1876 | Please wait a moment while I gather a list of all available modules...\r | |
1877 | \r | |
1878 | ''')\r | |
1879 | modules = {}\r | |
1880 | def callback(path, modname, desc, modules=modules):\r | |
1881 | if modname and modname[-9:] == '.__init__':\r | |
1882 | modname = modname[:-9] + ' (package)'\r | |
1883 | if find(modname, '.') < 0:\r | |
1884 | modules[modname] = 1\r | |
1885 | def onerror(modname):\r | |
1886 | callback(None, modname, None)\r | |
1887 | ModuleScanner().run(callback, onerror=onerror)\r | |
1888 | self.list(modules.keys())\r | |
1889 | self.output.write('''\r | |
1890 | Enter any module name to get more help. Or, type "modules spam" to search\r | |
1891 | for modules whose descriptions contain the word "spam".\r | |
1892 | ''')\r | |
1893 | \r | |
1894 | help = Helper()\r | |
1895 | \r | |
1896 | class Scanner:\r | |
1897 | """A generic tree iterator."""\r | |
1898 | def __init__(self, roots, children, descendp):\r | |
1899 | self.roots = roots[:]\r | |
1900 | self.state = []\r | |
1901 | self.children = children\r | |
1902 | self.descendp = descendp\r | |
1903 | \r | |
1904 | def next(self):\r | |
1905 | if not self.state:\r | |
1906 | if not self.roots:\r | |
1907 | return None\r | |
1908 | root = self.roots.pop(0)\r | |
1909 | self.state = [(root, self.children(root))]\r | |
1910 | node, children = self.state[-1]\r | |
1911 | if not children:\r | |
1912 | self.state.pop()\r | |
1913 | return self.next()\r | |
1914 | child = children.pop(0)\r | |
1915 | if self.descendp(child):\r | |
1916 | self.state.append((child, self.children(child)))\r | |
1917 | return child\r | |
1918 | \r | |
1919 | \r | |
1920 | class ModuleScanner:\r | |
1921 | """An interruptible scanner that searches module synopses."""\r | |
1922 | \r | |
1923 | def run(self, callback, key=None, completer=None, onerror=None):\r | |
1924 | if key: key = lower(key)\r | |
1925 | self.quit = False\r | |
1926 | seen = {}\r | |
1927 | \r | |
1928 | for modname in sys.builtin_module_names:\r | |
1929 | if modname != '__main__':\r | |
1930 | seen[modname] = 1\r | |
1931 | if key is None:\r | |
1932 | callback(None, modname, '')\r | |
1933 | else:\r | |
1934 | desc = split(__import__(modname).__doc__ or '', '\n')[0]\r | |
1935 | if find(lower(modname + ' - ' + desc), key) >= 0:\r | |
1936 | callback(None, modname, desc)\r | |
1937 | \r | |
1938 | for importer, modname, ispkg in pkgutil.walk_packages(onerror=onerror):\r | |
1939 | if self.quit:\r | |
1940 | break\r | |
1941 | if key is None:\r | |
1942 | callback(None, modname, '')\r | |
1943 | else:\r | |
1944 | loader = importer.find_module(modname)\r | |
1945 | if hasattr(loader,'get_source'):\r | |
1946 | import StringIO\r | |
1947 | desc = source_synopsis(\r | |
1948 | StringIO.StringIO(loader.get_source(modname))\r | |
1949 | ) or ''\r | |
1950 | if hasattr(loader,'get_filename'):\r | |
1951 | path = loader.get_filename(modname)\r | |
1952 | else:\r | |
1953 | path = None\r | |
1954 | else:\r | |
1955 | module = loader.load_module(modname)\r | |
1956 | desc = (module.__doc__ or '').splitlines()[0]\r | |
1957 | path = getattr(module,'__file__',None)\r | |
1958 | if find(lower(modname + ' - ' + desc), key) >= 0:\r | |
1959 | callback(path, modname, desc)\r | |
1960 | \r | |
1961 | if completer:\r | |
1962 | completer()\r | |
1963 | \r | |
1964 | def apropos(key):\r | |
1965 | """Print all the one-line module summaries that contain a substring."""\r | |
1966 | def callback(path, modname, desc):\r | |
1967 | if modname[-9:] == '.__init__':\r | |
1968 | modname = modname[:-9] + ' (package)'\r | |
1969 | print modname, desc and '- ' + desc\r | |
1970 | try: import warnings\r | |
1971 | except ImportError: pass\r | |
1972 | else: warnings.filterwarnings('ignore') # ignore problems during import\r | |
1973 | ModuleScanner().run(callback, key)\r | |
1974 | \r | |
1975 | # --------------------------------------------------- web browser interface\r | |
1976 | \r | |
1977 | def serve(port, callback=None, completer=None):\r | |
1978 | import BaseHTTPServer, mimetools, select\r | |
1979 | \r | |
1980 | # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.\r | |
1981 | class Message(mimetools.Message):\r | |
1982 | def __init__(self, fp, seekable=1):\r | |
1983 | Message = self.__class__\r | |
1984 | Message.__bases__[0].__bases__[0].__init__(self, fp, seekable)\r | |
1985 | self.encodingheader = self.getheader('content-transfer-encoding')\r | |
1986 | self.typeheader = self.getheader('content-type')\r | |
1987 | self.parsetype()\r | |
1988 | self.parseplist()\r | |
1989 | \r | |
1990 | class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler):\r | |
1991 | def send_document(self, title, contents):\r | |
1992 | try:\r | |
1993 | self.send_response(200)\r | |
1994 | self.send_header('Content-Type', 'text/html')\r | |
1995 | self.end_headers()\r | |
1996 | self.wfile.write(html.page(title, contents))\r | |
1997 | except IOError: pass\r | |
1998 | \r | |
1999 | def do_GET(self):\r | |
2000 | path = self.path\r | |
2001 | if path[-5:] == '.html': path = path[:-5]\r | |
2002 | if path[:1] == '/': path = path[1:]\r | |
2003 | if path and path != '.':\r | |
2004 | try:\r | |
2005 | obj = locate(path, forceload=1)\r | |
2006 | except ErrorDuringImport, value:\r | |
2007 | self.send_document(path, html.escape(str(value)))\r | |
2008 | return\r | |
2009 | if obj:\r | |
2010 | self.send_document(describe(obj), html.document(obj, path))\r | |
2011 | else:\r | |
2012 | self.send_document(path,\r | |
2013 | 'no Python documentation found for %s' % repr(path))\r | |
2014 | else:\r | |
2015 | heading = html.heading(\r | |
2016 | '<big><big><strong>Python: Index of Modules</strong></big></big>',\r | |
2017 | '#ffffff', '#7799ee')\r | |
2018 | def bltinlink(name):\r | |
2019 | return '<a href="%s.html">%s</a>' % (name, name)\r | |
2020 | names = filter(lambda x: x != '__main__',\r | |
2021 | sys.builtin_module_names)\r | |
2022 | contents = html.multicolumn(names, bltinlink)\r | |
2023 | indices = ['<p>' + html.bigsection(\r | |
2024 | 'Built-in Modules', '#ffffff', '#ee77aa', contents)]\r | |
2025 | \r | |
2026 | seen = {}\r | |
2027 | for dir in sys.path:\r | |
2028 | indices.append(html.index(dir, seen))\r | |
2029 | contents = heading + join(indices) + '''<p align=right>\r | |
2030 | <font color="#909090" face="helvetica, arial"><strong>\r | |
2031 | pydoc</strong> by Ka-Ping Yee <ping@lfw.org></font>'''\r | |
2032 | self.send_document('Index of Modules', contents)\r | |
2033 | \r | |
2034 | def log_message(self, *args): pass\r | |
2035 | \r | |
2036 | class DocServer(BaseHTTPServer.HTTPServer):\r | |
2037 | def __init__(self, port, callback):\r | |
2038 | host = 'localhost'\r | |
2039 | self.address = (host, port)\r | |
2040 | self.url = 'http://%s:%d/' % (host, port)\r | |
2041 | self.callback = callback\r | |
2042 | self.base.__init__(self, self.address, self.handler)\r | |
2043 | \r | |
2044 | def serve_until_quit(self):\r | |
2045 | import select\r | |
2046 | self.quit = False\r | |
2047 | while not self.quit:\r | |
2048 | rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)\r | |
2049 | if rd: self.handle_request()\r | |
2050 | \r | |
2051 | def server_activate(self):\r | |
2052 | self.base.server_activate(self)\r | |
2053 | if self.callback: self.callback(self)\r | |
2054 | \r | |
2055 | DocServer.base = BaseHTTPServer.HTTPServer\r | |
2056 | DocServer.handler = DocHandler\r | |
2057 | DocHandler.MessageClass = Message\r | |
2058 | try:\r | |
2059 | try:\r | |
2060 | DocServer(port, callback).serve_until_quit()\r | |
2061 | except (KeyboardInterrupt, select.error):\r | |
2062 | pass\r | |
2063 | finally:\r | |
2064 | if completer: completer()\r | |
2065 | \r | |
2066 | # ----------------------------------------------------- graphical interface\r | |
2067 | \r | |
2068 | def gui():\r | |
2069 | """Graphical interface (starts web server and pops up a control window)."""\r | |
2070 | class GUI:\r | |
2071 | def __init__(self, window, port=7464):\r | |
2072 | self.window = window\r | |
2073 | self.server = None\r | |
2074 | self.scanner = None\r | |
2075 | \r | |
2076 | import Tkinter\r | |
2077 | self.server_frm = Tkinter.Frame(window)\r | |
2078 | self.title_lbl = Tkinter.Label(self.server_frm,\r | |
2079 | text='Starting server...\n ')\r | |
2080 | self.open_btn = Tkinter.Button(self.server_frm,\r | |
2081 | text='open browser', command=self.open, state='disabled')\r | |
2082 | self.quit_btn = Tkinter.Button(self.server_frm,\r | |
2083 | text='quit serving', command=self.quit, state='disabled')\r | |
2084 | \r | |
2085 | self.search_frm = Tkinter.Frame(window)\r | |
2086 | self.search_lbl = Tkinter.Label(self.search_frm, text='Search for')\r | |
2087 | self.search_ent = Tkinter.Entry(self.search_frm)\r | |
2088 | self.search_ent.bind('<Return>', self.search)\r | |
2089 | self.stop_btn = Tkinter.Button(self.search_frm,\r | |
2090 | text='stop', pady=0, command=self.stop, state='disabled')\r | |
2091 | if sys.platform == 'win32':\r | |
2092 | # Trying to hide and show this button crashes under Windows.\r | |
2093 | self.stop_btn.pack(side='right')\r | |
2094 | \r | |
2095 | self.window.title('pydoc')\r | |
2096 | self.window.protocol('WM_DELETE_WINDOW', self.quit)\r | |
2097 | self.title_lbl.pack(side='top', fill='x')\r | |
2098 | self.open_btn.pack(side='left', fill='x', expand=1)\r | |
2099 | self.quit_btn.pack(side='right', fill='x', expand=1)\r | |
2100 | self.server_frm.pack(side='top', fill='x')\r | |
2101 | \r | |
2102 | self.search_lbl.pack(side='left')\r | |
2103 | self.search_ent.pack(side='right', fill='x', expand=1)\r | |
2104 | self.search_frm.pack(side='top', fill='x')\r | |
2105 | self.search_ent.focus_set()\r | |
2106 | \r | |
2107 | font = ('helvetica', sys.platform == 'win32' and 8 or 10)\r | |
2108 | self.result_lst = Tkinter.Listbox(window, font=font, height=6)\r | |
2109 | self.result_lst.bind('<Button-1>', self.select)\r | |
2110 | self.result_lst.bind('<Double-Button-1>', self.goto)\r | |
2111 | self.result_scr = Tkinter.Scrollbar(window,\r | |
2112 | orient='vertical', command=self.result_lst.yview)\r | |
2113 | self.result_lst.config(yscrollcommand=self.result_scr.set)\r | |
2114 | \r | |
2115 | self.result_frm = Tkinter.Frame(window)\r | |
2116 | self.goto_btn = Tkinter.Button(self.result_frm,\r | |
2117 | text='go to selected', command=self.goto)\r | |
2118 | self.hide_btn = Tkinter.Button(self.result_frm,\r | |
2119 | text='hide results', command=self.hide)\r | |
2120 | self.goto_btn.pack(side='left', fill='x', expand=1)\r | |
2121 | self.hide_btn.pack(side='right', fill='x', expand=1)\r | |
2122 | \r | |
2123 | self.window.update()\r | |
2124 | self.minwidth = self.window.winfo_width()\r | |
2125 | self.minheight = self.window.winfo_height()\r | |
2126 | self.bigminheight = (self.server_frm.winfo_reqheight() +\r | |
2127 | self.search_frm.winfo_reqheight() +\r | |
2128 | self.result_lst.winfo_reqheight() +\r | |
2129 | self.result_frm.winfo_reqheight())\r | |
2130 | self.bigwidth, self.bigheight = self.minwidth, self.bigminheight\r | |
2131 | self.expanded = 0\r | |
2132 | self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))\r | |
2133 | self.window.wm_minsize(self.minwidth, self.minheight)\r | |
2134 | self.window.tk.willdispatch()\r | |
2135 | \r | |
2136 | import threading\r | |
2137 | threading.Thread(\r | |
2138 | target=serve, args=(port, self.ready, self.quit)).start()\r | |
2139 | \r | |
2140 | def ready(self, server):\r | |
2141 | self.server = server\r | |
2142 | self.title_lbl.config(\r | |
2143 | text='Python documentation server at\n' + server.url)\r | |
2144 | self.open_btn.config(state='normal')\r | |
2145 | self.quit_btn.config(state='normal')\r | |
2146 | \r | |
2147 | def open(self, event=None, url=None):\r | |
2148 | url = url or self.server.url\r | |
2149 | try:\r | |
2150 | import webbrowser\r | |
2151 | webbrowser.open(url)\r | |
2152 | except ImportError: # pre-webbrowser.py compatibility\r | |
2153 | if sys.platform == 'win32':\r | |
2154 | os.system('start "%s"' % url)\r | |
2155 | else:\r | |
2156 | rc = os.system('netscape -remote "openURL(%s)" &' % url)\r | |
2157 | if rc: os.system('netscape "%s" &' % url)\r | |
2158 | \r | |
2159 | def quit(self, event=None):\r | |
2160 | if self.server:\r | |
2161 | self.server.quit = 1\r | |
2162 | self.window.quit()\r | |
2163 | \r | |
2164 | def search(self, event=None):\r | |
2165 | key = self.search_ent.get()\r | |
2166 | self.stop_btn.pack(side='right')\r | |
2167 | self.stop_btn.config(state='normal')\r | |
2168 | self.search_lbl.config(text='Searching for "%s"...' % key)\r | |
2169 | self.search_ent.forget()\r | |
2170 | self.search_lbl.pack(side='left')\r | |
2171 | self.result_lst.delete(0, 'end')\r | |
2172 | self.goto_btn.config(state='disabled')\r | |
2173 | self.expand()\r | |
2174 | \r | |
2175 | import threading\r | |
2176 | if self.scanner:\r | |
2177 | self.scanner.quit = 1\r | |
2178 | self.scanner = ModuleScanner()\r | |
2179 | threading.Thread(target=self.scanner.run,\r | |
2180 | args=(self.update, key, self.done)).start()\r | |
2181 | \r | |
2182 | def update(self, path, modname, desc):\r | |
2183 | if modname[-9:] == '.__init__':\r | |
2184 | modname = modname[:-9] + ' (package)'\r | |
2185 | self.result_lst.insert('end',\r | |
2186 | modname + ' - ' + (desc or '(no description)'))\r | |
2187 | \r | |
2188 | def stop(self, event=None):\r | |
2189 | if self.scanner:\r | |
2190 | self.scanner.quit = 1\r | |
2191 | self.scanner = None\r | |
2192 | \r | |
2193 | def done(self):\r | |
2194 | self.scanner = None\r | |
2195 | self.search_lbl.config(text='Search for')\r | |
2196 | self.search_lbl.pack(side='left')\r | |
2197 | self.search_ent.pack(side='right', fill='x', expand=1)\r | |
2198 | if sys.platform != 'win32': self.stop_btn.forget()\r | |
2199 | self.stop_btn.config(state='disabled')\r | |
2200 | \r | |
2201 | def select(self, event=None):\r | |
2202 | self.goto_btn.config(state='normal')\r | |
2203 | \r | |
2204 | def goto(self, event=None):\r | |
2205 | selection = self.result_lst.curselection()\r | |
2206 | if selection:\r | |
2207 | modname = split(self.result_lst.get(selection[0]))[0]\r | |
2208 | self.open(url=self.server.url + modname + '.html')\r | |
2209 | \r | |
2210 | def collapse(self):\r | |
2211 | if not self.expanded: return\r | |
2212 | self.result_frm.forget()\r | |
2213 | self.result_scr.forget()\r | |
2214 | self.result_lst.forget()\r | |
2215 | self.bigwidth = self.window.winfo_width()\r | |
2216 | self.bigheight = self.window.winfo_height()\r | |
2217 | self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))\r | |
2218 | self.window.wm_minsize(self.minwidth, self.minheight)\r | |
2219 | self.expanded = 0\r | |
2220 | \r | |
2221 | def expand(self):\r | |
2222 | if self.expanded: return\r | |
2223 | self.result_frm.pack(side='bottom', fill='x')\r | |
2224 | self.result_scr.pack(side='right', fill='y')\r | |
2225 | self.result_lst.pack(side='top', fill='both', expand=1)\r | |
2226 | self.window.wm_geometry('%dx%d' % (self.bigwidth, self.bigheight))\r | |
2227 | self.window.wm_minsize(self.minwidth, self.bigminheight)\r | |
2228 | self.expanded = 1\r | |
2229 | \r | |
2230 | def hide(self, event=None):\r | |
2231 | self.stop()\r | |
2232 | self.collapse()\r | |
2233 | \r | |
2234 | import Tkinter\r | |
2235 | try:\r | |
2236 | root = Tkinter.Tk()\r | |
2237 | # Tk will crash if pythonw.exe has an XP .manifest\r | |
2238 | # file and the root has is not destroyed explicitly.\r | |
2239 | # If the problem is ever fixed in Tk, the explicit\r | |
2240 | # destroy can go.\r | |
2241 | try:\r | |
2242 | gui = GUI(root)\r | |
2243 | root.mainloop()\r | |
2244 | finally:\r | |
2245 | root.destroy()\r | |
2246 | except KeyboardInterrupt:\r | |
2247 | pass\r | |
2248 | \r | |
2249 | # -------------------------------------------------- command-line interface\r | |
2250 | \r | |
2251 | def ispath(x):\r | |
2252 | return isinstance(x, str) and find(x, os.sep) >= 0\r | |
2253 | \r | |
2254 | def cli():\r | |
2255 | """Command-line interface (looks at sys.argv to decide what to do)."""\r | |
2256 | import getopt\r | |
2257 | class BadUsage: pass\r | |
2258 | \r | |
2259 | # Scripts don't get the current directory in their path by default\r | |
2260 | # unless they are run with the '-m' switch\r | |
2261 | if '' not in sys.path:\r | |
2262 | scriptdir = os.path.dirname(sys.argv[0])\r | |
2263 | if scriptdir in sys.path:\r | |
2264 | sys.path.remove(scriptdir)\r | |
2265 | sys.path.insert(0, '.')\r | |
2266 | \r | |
2267 | try:\r | |
2268 | opts, args = getopt.getopt(sys.argv[1:], 'gk:p:w')\r | |
2269 | writing = 0\r | |
2270 | \r | |
2271 | for opt, val in opts:\r | |
2272 | if opt == '-g':\r | |
2273 | gui()\r | |
2274 | return\r | |
2275 | if opt == '-k':\r | |
2276 | apropos(val)\r | |
2277 | return\r | |
2278 | if opt == '-p':\r | |
2279 | try:\r | |
2280 | port = int(val)\r | |
2281 | except ValueError:\r | |
2282 | raise BadUsage\r | |
2283 | def ready(server):\r | |
2284 | print 'pydoc server ready at %s' % server.url\r | |
2285 | def stopped():\r | |
2286 | print 'pydoc server stopped'\r | |
2287 | serve(port, ready, stopped)\r | |
2288 | return\r | |
2289 | if opt == '-w':\r | |
2290 | writing = 1\r | |
2291 | \r | |
2292 | if not args: raise BadUsage\r | |
2293 | for arg in args:\r | |
2294 | if ispath(arg) and not os.path.exists(arg):\r | |
2295 | print 'file %r does not exist' % arg\r | |
2296 | break\r | |
2297 | try:\r | |
2298 | if ispath(arg) and os.path.isfile(arg):\r | |
2299 | arg = importfile(arg)\r | |
2300 | if writing:\r | |
2301 | if ispath(arg) and os.path.isdir(arg):\r | |
2302 | writedocs(arg)\r | |
2303 | else:\r | |
2304 | writedoc(arg)\r | |
2305 | else:\r | |
2306 | help.help(arg)\r | |
2307 | except ErrorDuringImport, value:\r | |
2308 | print value\r | |
2309 | \r | |
2310 | except (getopt.error, BadUsage):\r | |
2311 | cmd = os.path.basename(sys.argv[0])\r | |
2312 | print """pydoc - the Python documentation tool\r | |
2313 | \r | |
2314 | %s <name> ...\r | |
2315 | Show text documentation on something. <name> may be the name of a\r | |
2316 | Python keyword, topic, function, module, or package, or a dotted\r | |
2317 | reference to a class or function within a module or module in a\r | |
2318 | package. If <name> contains a '%s', it is used as the path to a\r | |
2319 | Python source file to document. If name is 'keywords', 'topics',\r | |
2320 | or 'modules', a listing of these things is displayed.\r | |
2321 | \r | |
2322 | %s -k <keyword>\r | |
2323 | Search for a keyword in the synopsis lines of all available modules.\r | |
2324 | \r | |
2325 | %s -p <port>\r | |
2326 | Start an HTTP server on the given port on the local machine.\r | |
2327 | \r | |
2328 | %s -g\r | |
2329 | Pop up a graphical interface for finding and serving documentation.\r | |
2330 | \r | |
2331 | %s -w <name> ...\r | |
2332 | Write out the HTML documentation for a module to a file in the current\r | |
2333 | directory. If <name> contains a '%s', it is treated as a filename; if\r | |
2334 | it names a directory, documentation is written for all the contents.\r | |
2335 | """ % (cmd, os.sep, cmd, cmd, cmd, cmd, os.sep)\r | |
2336 | \r | |
2337 | if __name__ == '__main__': cli()\r |