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