1
2
3
4
5
6
7
8
9 """
10 Command-line interface for epydoc. Abbreviated Usage::
11
12 epydoc [options] NAMES...
13
14 NAMES... The Python modules to document.
15 --html Generate HTML output (default).
16 --latex Generate LaTeX output.
17 --pdf Generate pdf output, via LaTeX.
18 -o DIR, --output DIR The output directory.
19 --inheritance STYLE The format for showing inherited objects.
20 -V, --version Print the version of epydoc.
21 -h, --help Display a usage message.
22
23 Run \"epydoc --help\" for a complete option list. See the epydoc(1)
24 man page for more information.
25
26 Config Files
27 ============
28 Configuration files can be specified with the C{--config} option.
29 These files are read using U{ConfigParser
30 <http://docs.python.org/lib/module-ConfigParser.html>}. Configuration
31 files may set options or add names of modules to document. Option
32 names are (usually) identical to the long names of command line
33 options. To specify names to document, use any of the following
34 option names::
35
36 module modules value values object objects
37
38 A simple example of a config file is::
39
40 [epydoc]
41 modules: sys, os, os.path, re, %(MYSANDBOXPATH)/utilities.py
42 name: Example
43 graph: classtree
44 introspect: no
45
46 All ConfigParser interpolations are done using local values and the
47 environment variables.
48
49
50 Verbosity Levels
51 ================
52 The C{-v} and C{-q} options increase and decrease verbosity,
53 respectively. The default verbosity level is zero. The verbosity
54 levels are currently defined as follows::
55
56 Progress Markup warnings Warnings Errors
57 -3 none no no no
58 -2 none no no yes
59 -1 none no yes yes
60 0 (default) bar no yes yes
61 1 bar yes yes yes
62 2 list yes yes yes
63 """
64 __docformat__ = 'epytext en'
65
66 import sys, os, time, re, pickle, textwrap
67 from glob import glob
68 from optparse import OptionParser, OptionGroup, SUPPRESS_HELP
69 import optparse
70 import epydoc
71 from epydoc import log
72 from epydoc.util import wordwrap, run_subprocess, RunSubprocessError
73 from epydoc.util import plaintext_to_html
74 from epydoc.apidoc import UNKNOWN
75 from epydoc.compat import *
76 import ConfigParser
77 from epydoc.docwriter.html_css import STYLESHEETS as CSS_STYLESHEETS
78
79
80 try:
81 from epydoc.docwriter import xlink
82 except:
83 xlink = None
84
85 INHERITANCE_STYLES = ('grouped', 'listed', 'included')
86 GRAPH_TYPES = ('classtree', 'callgraph', 'umlclasstree')
87 ACTIONS = ('html', 'text', 'latex', 'dvi', 'ps', 'pdf', 'check')
88 DEFAULT_DOCFORMAT = 'epytext'
89 PROFILER = 'hotshot'
90
91
92
93
94
95 DOCFORMATS = ('epytext', 'plaintext', 'restructuredtext', 'javadoc')
96 HELP_TOPICS = {
97 'docformat': textwrap.dedent('''\
98 __docformat__ is a module variable that specifies the markup
99 language for the docstrings in a module. Its value is a
100 string, consisting the name of a markup language, optionally
101 followed by a language code (such as "en" for English). Epydoc
102 currently recognizes the following markup language names:
103 ''' + ', '.join(DOCFORMATS)),
104 'inheritance': textwrap.dedent('''\
105 The following inheritance formats are currently supported:
106 - grouped: inherited objects are gathered into groups,
107 based on what class they were inherited from.
108 - listed: inherited objects are listed in a short list
109 at the end of their section.
110 - included: inherited objects are mixed in with
111 non-inherited objects.'''),
112 'css': textwrap.dedent(
113 'The following built-in CSS stylesheets are available:\n' +
114 '\n'.join([' %10s: %s' % (key, descr)
115 for (key, (sheet, descr))
116 in CSS_STYLESHEETS.items()])),
117
118
119
120 }
121
122
123 HELP_TOPICS['topics'] = wordwrap(
124 'Epydoc can provide additional help for the following topics: ' +
125 ', '.join(['%r' % topic for topic in HELP_TOPICS.keys()]))
126
127
128
129
130
131 OPTION_DEFAULTS = dict(
132 action="html", show_frames=True, docformat=DEFAULT_DOCFORMAT,
133 show_private=True, show_imports=False, inheritance="listed",
134 verbose=0, quiet=0, load_pickle=False, parse=True, introspect=True,
135 debug=epydoc.DEBUG, profile=False, graphs=[],
136 list_classes_separately=False, graph_font=None, graph_font_size=None,
137 include_source_code=True, pstat_files=[], simple_term=False, fail_on=None,
138 exclude=[], exclude_parse=[], exclude_introspect=[],
139 external_api=[],external_api_file=[],external_api_root=[])
140
142
143 usage = '%prog [ACTION] [options] NAMES...'
144 version = "Epydoc, version %s" % epydoc.__version__
145 optparser = OptionParser(usage=usage, add_help_option=False)
146
147 optparser.add_option('--config',
148 action='append', dest="configfiles", metavar='FILE',
149 help=("A configuration file, specifying additional OPTIONS "
150 "and/or NAMES. This option may be repeated."))
151
152 optparser.add_option("--output", "-o",
153 dest="target", metavar="PATH",
154 help="The output directory. If PATH does not exist, then "
155 "it will be created.")
156
157 optparser.add_option("--quiet", "-q",
158 action="count", dest="quiet",
159 help="Decrease the verbosity.")
160
161 optparser.add_option("--verbose", "-v",
162 action="count", dest="verbose",
163 help="Increase the verbosity.")
164
165 optparser.add_option("--debug",
166 action="store_true", dest="debug",
167 help="Show full tracebacks for internal errors.")
168
169 optparser.add_option("--simple-term",
170 action="store_true", dest="simple_term",
171 help="Do not try to use color or cursor control when displaying "
172 "the progress bar, warnings, or errors.")
173
174
175 action_group = OptionGroup(optparser, 'Actions')
176 optparser.add_option_group(action_group)
177
178 action_group.add_option("--html",
179 action="store_const", dest="action", const="html",
180 help="Write HTML output.")
181
182 action_group.add_option("--text",
183 action="store_const", dest="action", const="text",
184 help="Write plaintext output. (not implemented yet)")
185
186 action_group.add_option("--latex",
187 action="store_const", dest="action", const="latex",
188 help="Write LaTeX output.")
189
190 action_group.add_option("--dvi",
191 action="store_const", dest="action", const="dvi",
192 help="Write DVI output.")
193
194 action_group.add_option("--ps",
195 action="store_const", dest="action", const="ps",
196 help="Write Postscript output.")
197
198 action_group.add_option("--pdf",
199 action="store_const", dest="action", const="pdf",
200 help="Write PDF output.")
201
202 action_group.add_option("--check",
203 action="store_const", dest="action", const="check",
204 help="Check completeness of docs.")
205
206 action_group.add_option("--pickle",
207 action="store_const", dest="action", const="pickle",
208 help="Write the documentation to a pickle file.")
209
210
211 action_group.add_option("--version",
212 action="store_const", dest="action", const="version",
213 help="Show epydoc's version number and exit.")
214
215 action_group.add_option("-h", "--help",
216 action="store_const", dest="action", const="help",
217 help="Show this message and exit. For help on specific "
218 "topics, use \"--help TOPIC\". Use \"--help topics\" for a "
219 "list of available help topics")
220
221
222 generation_group = OptionGroup(optparser, 'Generation Options')
223 optparser.add_option_group(generation_group)
224
225 generation_group.add_option("--docformat",
226 dest="docformat", metavar="NAME",
227 help="The default markup language for docstrings. Defaults "
228 "to \"%s\"." % DEFAULT_DOCFORMAT)
229
230 generation_group.add_option("--parse-only",
231 action="store_false", dest="introspect",
232 help="Get all information from parsing (don't introspect)")
233
234 generation_group.add_option("--introspect-only",
235 action="store_false", dest="parse",
236 help="Get all information from introspecting (don't parse)")
237
238 generation_group.add_option("--exclude",
239 dest="exclude", metavar="PATTERN", action="append",
240 help="Exclude modules whose dotted name matches "
241 "the regular expression PATTERN")
242
243 generation_group.add_option("--exclude-introspect",
244 dest="exclude_introspect", metavar="PATTERN", action="append",
245 help="Exclude introspection of modules whose dotted name matches "
246 "the regular expression PATTERN")
247
248 generation_group.add_option("--exclude-parse",
249 dest="exclude_parse", metavar="PATTERN", action="append",
250 help="Exclude parsing of modules whose dotted name matches "
251 "the regular expression PATTERN")
252
253 generation_group.add_option("--inheritance",
254 dest="inheritance", metavar="STYLE",
255 help="The format for showing inheritance objects. STYLE "
256 "should be one of: %s." % ', '.join(INHERITANCE_STYLES))
257
258 generation_group.add_option("--show-private",
259 action="store_true", dest="show_private",
260 help="Include private variables in the output. (default)")
261
262 generation_group.add_option("--no-private",
263 action="store_false", dest="show_private",
264 help="Do not include private variables in the output.")
265
266 generation_group.add_option("--show-imports",
267 action="store_true", dest="show_imports",
268 help="List each module's imports.")
269
270 generation_group.add_option("--no-imports",
271 action="store_false", dest="show_imports",
272 help="Do not list each module's imports. (default)")
273
274 generation_group.add_option('--show-sourcecode',
275 action='store_true', dest='include_source_code',
276 help=("Include source code with syntax highlighting in the "
277 "HTML output. (default)"))
278
279 generation_group.add_option('--no-sourcecode',
280 action='store_false', dest='include_source_code',
281 help=("Do not include source code with syntax highlighting in the "
282 "HTML output."))
283
284 generation_group.add_option('--include-log',
285 action='store_true', dest='include_log',
286 help=("Include a page with the process log (epydoc-log.html)"))
287
288
289 output_group = OptionGroup(optparser, 'Output Options')
290 optparser.add_option_group(output_group)
291
292 output_group.add_option("--name",
293 dest="prj_name", metavar="NAME",
294 help="The documented project's name (for the navigation bar).")
295
296 output_group.add_option("--css",
297 dest="css", metavar="STYLESHEET",
298 help="The CSS stylesheet. STYLESHEET can be either a "
299 "builtin stylesheet or the name of a CSS file.")
300
301 output_group.add_option("--url",
302 dest="prj_url", metavar="URL",
303 help="The documented project's URL (for the navigation bar).")
304
305 output_group.add_option("--navlink",
306 dest="prj_link", metavar="HTML",
307 help="HTML code for a navigation link to place in the "
308 "navigation bar.")
309
310 output_group.add_option("--top",
311 dest="top_page", metavar="PAGE",
312 help="The \"top\" page for the HTML documentation. PAGE can "
313 "be a URL, the name of a module or class, or one of the "
314 "special names \"trees.html\", \"indices.html\", or \"help.html\"")
315
316 output_group.add_option("--help-file",
317 dest="help_file", metavar="FILE",
318 help="An alternate help file. FILE should contain the body "
319 "of an HTML file -- navigation bars will be added to it.")
320
321 output_group.add_option("--show-frames",
322 action="store_true", dest="show_frames",
323 help="Include frames in the HTML output. (default)")
324
325 output_group.add_option("--no-frames",
326 action="store_false", dest="show_frames",
327 help="Do not include frames in the HTML output.")
328
329 output_group.add_option('--separate-classes',
330 action='store_true', dest='list_classes_separately',
331 help=("When generating LaTeX or PDF output, list each class in "
332 "its own section, instead of listing them under their "
333 "containing module."))
334
335
336
337 if xlink is not None:
338 link_group = OptionGroup(optparser,
339 xlink.ApiLinkReader.settings_spec[0])
340 optparser.add_option_group(link_group)
341
342 for help, names, opts in xlink.ApiLinkReader.settings_spec[2]:
343 opts = opts.copy()
344 opts['help'] = help
345 link_group.add_option(*names, **opts)
346
347 graph_group = OptionGroup(optparser, 'Graph Options')
348 optparser.add_option_group(graph_group)
349
350 graph_group.add_option('--graph',
351 action='append', dest='graphs', metavar='GRAPHTYPE',
352 help=("Include graphs of type GRAPHTYPE in the generated output. "
353 "Graphs are generated using the Graphviz dot executable. "
354 "If this executable is not on the path, then use --dotpath "
355 "to specify its location. This option may be repeated to "
356 "include multiple graph types in the output. GRAPHTYPE "
357 "should be one of: all, %s." % ', '.join(GRAPH_TYPES)))
358
359 graph_group.add_option("--dotpath",
360 dest="dotpath", metavar='PATH',
361 help="The path to the Graphviz 'dot' executable.")
362
363 graph_group.add_option('--graph-font',
364 dest='graph_font', metavar='FONT',
365 help=("Specify the font used to generate Graphviz graphs. (e.g., "
366 "helvetica or times)."))
367
368 graph_group.add_option('--graph-font-size',
369 dest='graph_font_size', metavar='SIZE',
370 help=("Specify the font size used to generate Graphviz graphs, "
371 "in points."))
372
373 graph_group.add_option('--pstat',
374 action='append', dest='pstat_files', metavar='FILE',
375 help="A pstat output file, to be used in generating call graphs.")
376
377
378 graph_group.add_option("--profile-epydoc",
379 action="store_true", dest="profile",
380 help=SUPPRESS_HELP or
381 ("Run the hotshot profiler on epydoc itself. Output "
382 "will be written to profile.out."))
383
384
385 return_group = OptionGroup(optparser, 'Return Value Options')
386 optparser.add_option_group(return_group)
387
388 return_group.add_option("--fail-on-error",
389 action="store_const", dest="fail_on", const=log.ERROR,
390 help="Return a non-zero exit status, indicating failure, if any "
391 "errors are encountered.")
392
393 return_group.add_option("--fail-on-warning",
394 action="store_const", dest="fail_on", const=log.WARNING,
395 help="Return a non-zero exit status, indicating failure, if any "
396 "errors or warnings are encountered (not including docstring "
397 "warnings).")
398
399 return_group.add_option("--fail-on-docstring-warning",
400 action="store_const", dest="fail_on", const=log.DOCSTRING_WARNING,
401 help="Return a non-zero exit status, indicating failure, if any "
402 "errors or warnings are encountered (including docstring "
403 "warnings).")
404
405
406 optparser.set_defaults(**OPTION_DEFAULTS)
407
408
409 options, names = optparser.parse_args()
410
411
412
413 if options.action == 'help':
414 names = set([n.lower() for n in names])
415 for (topic, msg) in HELP_TOPICS.items():
416 if topic.lower() in names:
417 print '\n' + msg.rstrip() + '\n'
418 sys.exit(0)
419 optparser.print_help()
420 sys.exit(0)
421
422
423 if options.action == 'version':
424 print version
425 sys.exit(0)
426
427
428 if options.configfiles:
429 try:
430 parse_configfiles(options.configfiles, options, names)
431 except (KeyboardInterrupt,SystemExit): raise
432 except Exception, e:
433 if len(options.configfiles) == 1:
434 cf_name = 'config file %s' % options.configfiles[0]
435 else:
436 cf_name = 'config files %s' % ', '.join(options.configfiles)
437 optparser.error('Error reading %s:\n %s' % (cf_name, e))
438
439
440 for name in names:
441 if name.endswith('.pickle'):
442 if len(names) != 1:
443 optparse.error("When a pickle file is specified, no other "
444 "input files may be specified.")
445 options.load_pickle = True
446
447
448 if len(names) == 0:
449 optparser.error("No names specified.")
450
451
452 for i, name in reversed(list(enumerate(names[:]))):
453 if '?' in name or '*' in name:
454 names[i:i+1] = glob(name)
455
456 if options.inheritance not in INHERITANCE_STYLES:
457 optparser.error("Bad inheritance style. Valid options are " +
458 ",".join(INHERITANCE_STYLES))
459 if not options.parse and not options.introspect:
460 optparser.error("Invalid option combination: --parse-only "
461 "and --introspect-only.")
462 if options.action == 'text' and len(names) > 1:
463 optparser.error("--text option takes only one name.")
464
465
466
467 options.graphs = [graph_type.lower() for graph_type in options.graphs]
468 for graph_type in options.graphs:
469 if graph_type == 'callgraph' and not options.pstat_files:
470 optparser.error('"callgraph" graph type may only be used if '
471 'one or more pstat files are specified.')
472
473
474 if graph_type == 'all':
475 if options.pstat_files:
476 options.graphs = GRAPH_TYPES
477 else:
478 options.graphs = [g for g in GRAPH_TYPES if g != 'callgraph']
479 break
480 elif graph_type not in GRAPH_TYPES:
481 optparser.error("Invalid graph type %s." % graph_type)
482
483
484 verbosity = getattr(options, 'verbosity', 0)
485 options.verbosity = verbosity + options.verbose - options.quiet
486
487
488 if options.target is None:
489 options.target = options.action
490
491
492 options.names = names
493 return options, names
494
496 configparser = ConfigParser.ConfigParser()
497
498
499 for configfile in configfiles:
500 fp = open(configfile, 'r')
501 configparser.readfp(fp, configfile)
502 fp.close()
503 for optname in configparser.options('epydoc'):
504 val = configparser.get('epydoc', optname, vars=os.environ).strip()
505 optname = optname.lower().strip()
506
507 if optname in ('modules', 'objects', 'values',
508 'module', 'object', 'value'):
509 names.extend(val.replace(',', ' ').split())
510 elif optname == 'target':
511 options.target = val
512 elif optname == 'output':
513 if val.lower() not in ACTIONS:
514 raise ValueError('"%s" expected one of: %s' %
515 (optname, ', '.join(ACTIONS)))
516 options.action = val.lower()
517 elif optname == 'verbosity':
518 options.verbosity = _str_to_int(val, optname)
519 elif optname == 'debug':
520 options.debug = _str_to_bool(val, optname)
521 elif optname in ('simple-term', 'simple_term'):
522 options.simple_term = _str_to_bool(val, optname)
523
524
525 elif optname == 'docformat':
526 options.docformat = val
527 elif optname == 'parse':
528 options.parse = _str_to_bool(val, optname)
529 elif optname == 'introspect':
530 options.introspect = _str_to_bool(val, optname)
531 elif optname == 'exclude':
532 options.exclude.append(val)
533 elif optname in ('exclude-parse', 'exclude_parse'):
534 options.exclude_parse.append(val)
535 elif optname in ('exclude-introspect', 'exclude_introspect'):
536 options.exclude_introspect.append(val)
537 elif optname == 'inheritance':
538 if val.lower() not in INHERITANCE_STYLES:
539 raise ValueError('"%s" expected one of: %s.' %
540 (optname, ', '.join(INHERITANCE_STYLES)))
541 options.inerhitance = val.lower()
542 elif optname =='private':
543 options.private = _str_to_bool(val, optname)
544 elif optname =='imports':
545 options.imports = _str_to_bool(val, optname)
546 elif optname == 'sourcecode':
547 options.include_source_code = _str_to_bool(val, optname)
548 elif optname in ('include-log', 'include_log'):
549 options.include_log = _str_to_bool(val, optname)
550
551
552 elif optname == 'name':
553 options.prj_name = val
554 elif optname == 'css':
555 options.css = val
556 elif optname == 'url':
557 options.prj_url = val
558 elif optname == 'link':
559 options.prj_link = val
560 elif optname == 'top':
561 options.top_page = val
562 elif optname == 'help':
563 options.help_file = val
564 elif optname =='frames':
565 options.show_frames = _str_to_bool(val, optname)
566 elif optname in ('separate-classes', 'separate_classes'):
567 options.list_classes_separately = _str_to_bool(val, optname)
568
569
570 elif optname in ('external-api', 'external_api'):
571 options.external_api.extend(val.replace(',', ' ').split())
572 elif optname in ('external-api-file', 'external_api_file'):
573 options.external_api_file.append(val)
574 elif optname in ('external-api-root', 'external_api_root'):
575 options.external_api_root.append(val)
576
577
578 elif optname == 'graph':
579 graphtypes = val.replace(',', '').split()
580 for graphtype in graphtypes:
581 if graphtype not in GRAPH_TYPES + ('all',):
582 raise ValueError('"%s" expected one of: all, %s.' %
583 (optname, ', '.join(GRAPH_TYPES)))
584 options.graphs.extend(graphtypes)
585 elif optname == 'dotpath':
586 options.dotpath = val
587 elif optname in ('graph-font', 'graph_font'):
588 options.graph_font = val
589 elif optname in ('graph-font-size', 'graph_font_size'):
590 options.graph_font_size = _str_to_int(val, optname)
591 elif optname == 'pstat':
592 options.pstat_files.extend(val.replace(',', ' ').split())
593
594
595 elif optname in ('failon', 'fail-on', 'fail_on'):
596 if val.lower().strip() in ('error', 'errors'):
597 options.fail_on = log.ERROR
598 elif val.lower().strip() in ('warning', 'warnings'):
599 options.fail_on = log.WARNING
600 elif val.lower().strip() in ('docstring_warning',
601 'docstring_warnings'):
602 options.fail_on = log.DOCSTRING_WARNING
603 else:
604 raise ValueError("%r expected one of: error, warning, "
605 "docstring_warning" % optname)
606 else:
607 raise ValueError('Unknown option %s' % optname)
608
610 if val.lower() in ('0', 'no', 'false', 'n', 'f', 'hide'):
611 return False
612 elif val.lower() in ('1', 'yes', 'true', 'y', 't', 'show'):
613 return True
614 else:
615 raise ValueError('"%s" option expected a boolean' % optname)
616
618 try:
619 return int(val)
620 except ValueError:
621 raise ValueError('"%s" option expected an int' % optname)
622
623
624
625
626
627 -def main(options, names):
628
629 if options.debug:
630 epydoc.DEBUG = True
631
632
633
634
635
636
637
638 if options.simple_term:
639 TerminalController.FORCE_SIMPLE_TERM = True
640 if options.action == 'text':
641 logger = None
642 elif options.verbosity > 1:
643 logger = ConsoleLogger(options.verbosity)
644 log.register_logger(logger)
645 else:
646
647
648 stages = [40,
649 7,
650 1,
651 3,
652 30,
653 1,
654 2]
655 if options.load_pickle:
656 stages = [30]
657 if options.action == 'html': stages += [100]
658 elif options.action == 'text': stages += [30]
659 elif options.action == 'latex': stages += [60]
660 elif options.action == 'dvi': stages += [60,30]
661 elif options.action == 'ps': stages += [60,40]
662 elif options.action == 'pdf': stages += [60,50]
663 elif options.action == 'check': stages += [10]
664 elif options.action == 'pickle': stages += [10]
665 else: raise ValueError, '%r not supported' % options.action
666 if options.parse and not options.introspect:
667 del stages[1]
668 if options.introspect and not options.parse:
669 del stages[1:3]
670 logger = UnifiedProgressConsoleLogger(options.verbosity, stages)
671 log.register_logger(logger)
672
673
674 if options.action not in ('text', 'check', 'pickle'):
675 if os.path.exists(options.target):
676 if not os.path.isdir(options.target):
677 log.error("%s is not a directory" % options.target)
678 sys.exit(1)
679
680 if options.include_log:
681 if options.action == 'html':
682 if not os.path.exists(options.target):
683 os.mkdir(options.target)
684 log.register_logger(HTMLLogger(options.target, options))
685 else:
686 log.warning("--include-log requires --html")
687
688
689 from epydoc import docstringparser
690 docstringparser.DEFAULT_DOCFORMAT = options.docformat
691
692
693 if xlink is not None:
694 try:
695 xlink.ApiLinkReader.read_configuration(options, problematic=False)
696 except Exception, exc:
697 log.error("Error while configuring external API linking: %s: %s"
698 % (exc.__class__.__name__, exc))
699
700
701 if options.dotpath:
702 from epydoc.docwriter import dotgraph
703 dotgraph.DOT_COMMAND = options.dotpath
704
705
706 if options.graph_font:
707 from epydoc.docwriter import dotgraph
708 fontname = options.graph_font
709 dotgraph.DotGraph.DEFAULT_NODE_DEFAULTS['fontname'] = fontname
710 dotgraph.DotGraph.DEFAULT_EDGE_DEFAULTS['fontname'] = fontname
711 if options.graph_font_size:
712 from epydoc.docwriter import dotgraph
713 fontsize = options.graph_font_size
714 dotgraph.DotGraph.DEFAULT_NODE_DEFAULTS['fontsize'] = fontsize
715 dotgraph.DotGraph.DEFAULT_EDGE_DEFAULTS['fontsize'] = fontsize
716
717
718
719 if options.load_pickle:
720 assert len(names) == 1
721 log.start_progress('Deserializing')
722 log.progress(0.1, 'Loading %r' % names[0])
723 t0 = time.time()
724 unpickler = pickle.Unpickler(open(names[0], 'rb'))
725 unpickler.persistent_load = pickle_persistent_load
726 docindex = unpickler.load()
727 log.debug('deserialization time: %.1f sec' % (time.time()-t0))
728 log.end_progress()
729 else:
730
731 from epydoc.docbuilder import build_doc_index
732 exclude_parse = '|'.join(options.exclude_parse+options.exclude)
733 exclude_introspect = '|'.join(options.exclude_introspect+
734 options.exclude)
735 docindex = build_doc_index(names, options.introspect, options.parse,
736 add_submodules=(options.action!='text'),
737 exclude_introspect=exclude_introspect,
738 exclude_parse=exclude_parse)
739
740 if docindex is None:
741 if log.ERROR in logger.reported_message_levels:
742 sys.exit(1)
743 else:
744 return
745
746
747 if options.pstat_files:
748 try: import pstats
749 except ImportError:
750 log.error("Could not import pstats -- ignoring pstat files.")
751 try:
752 profile_stats = pstats.Stats(options.pstat_files[0])
753 for filename in options.pstat_files[1:]:
754 profile_stats.add(filename)
755 except KeyboardInterrupt: raise
756 except Exception, e:
757 log.error("Error reading pstat file: %s" % e)
758 profile_stats = None
759 if profile_stats is not None:
760 docindex.read_profiling_info(profile_stats)
761
762
763 if options.action == 'html':
764 write_html(docindex, options)
765 elif options.action in ('latex', 'dvi', 'ps', 'pdf'):
766 write_latex(docindex, options, options.action)
767 elif options.action == 'text':
768 write_text(docindex, options)
769 elif options.action == 'check':
770 check_docs(docindex, options)
771 elif options.action == 'pickle':
772 write_pickle(docindex, options)
773 else:
774 print >>sys.stderr, '\nUnsupported action %s!' % options.action
775
776
777 if logger is not None and logger.supressed_docstring_warning:
778 if logger.supressed_docstring_warning == 1:
779 prefix = '1 markup error was found'
780 else:
781 prefix = ('%d markup errors were found' %
782 logger.supressed_docstring_warning)
783 log.warning("%s while processing docstrings. Use the verbose "
784 "switch (-v) to display markup errors." % prefix)
785
786
787 if options.verbosity >= 2 and logger is not None:
788 logger.print_times()
789
790
791
792 if options.fail_on is not None:
793 max_reported_message_level = max(logger.reported_message_levels)
794 if max_reported_message_level >= options.fail_on:
795 sys.exit(2)
796
806
808 """Helper for writing output to a pickle file, which can then be
809 read in at a later time. But loading the pickle is only marginally
810 faster than building the docs from scratch, so this has pretty
811 limited application."""
812 if options.target == 'pickle':
813 options.target = 'api.pickle'
814 elif not options.target.endswith('.pickle'):
815 options.target += '.pickle'
816
817 log.start_progress('Serializing output')
818 log.progress(0.2, 'Writing %r' % options.target)
819 outfile = open(options.target, 'wb')
820 pickler = pickle.Pickler(outfile, protocol=0)
821 pickler.persistent_id = pickle_persistent_id
822 pickler.dump(docindex)
823 outfile.close()
824 log.end_progress()
825
827 """Helper for pickling, which allows us to save and restore UNKNOWN,
828 which is required to be identical to apidoc.UNKNOWN."""
829 if obj is UNKNOWN: return 'UNKNOWN'
830 else: return None
831
833 """Helper for pickling, which allows us to save and restore UNKNOWN,
834 which is required to be identical to apidoc.UNKNOWN."""
835 if identifier == 'UNKNOWN': return UNKNOWN
836 else: raise pickle.UnpicklingError, 'Invalid persistent id'
837
838 _RERUN_LATEX_RE = re.compile(r'(?im)^LaTeX\s+Warning:\s+Label\(s\)\s+may'
839 r'\s+have\s+changed.\s+Rerun')
840
842 from epydoc.docwriter.latex import LatexWriter
843 latex_writer = LatexWriter(docindex, **options.__dict__)
844 log.start_progress('Writing LaTeX docs')
845 latex_writer.write(options.target)
846 log.end_progress()
847
848
849 if format == 'latex': return
850
851 if format == 'dvi': steps = 4
852 elif format == 'ps': steps = 5
853 elif format == 'pdf': steps = 6
854
855 log.start_progress('Processing LaTeX docs')
856 oldpath = os.path.abspath(os.curdir)
857 running = None
858 try:
859 try:
860 os.chdir(options.target)
861
862
863 for ext in 'tex aux log out idx ilg toc ind'.split():
864 if os.path.exists('apidoc.%s' % ext):
865 os.remove('apidoc.%s' % ext)
866
867
868 running = 'latex'
869 log.progress(0./steps, 'LaTeX: First pass')
870 run_subprocess('latex api.tex')
871
872
873 running = 'makeindex'
874 log.progress(1./steps, 'LaTeX: Build index')
875 run_subprocess('makeindex api.idx')
876
877
878 running = 'latex'
879 log.progress(2./steps, 'LaTeX: Second pass')
880 out, err = run_subprocess('latex api.tex')
881
882
883
884 running = 'latex'
885 if _RERUN_LATEX_RE.match(out):
886 log.progress(3./steps, 'LaTeX: Third pass')
887 out, err = run_subprocess('latex api.tex')
888
889
890 running = 'latex'
891 if _RERUN_LATEX_RE.match(out):
892 log.progress(3./steps, 'LaTeX: Fourth pass')
893 run_subprocess('latex api.tex')
894
895
896 if format in ('ps', 'pdf'):
897 running = 'dvips'
898 log.progress(4./steps, 'dvips')
899 run_subprocess('dvips api.dvi -o api.ps -G0 -Ppdf')
900
901
902 if format in ('pdf'):
903 running = 'ps2pdf'
904 log.progress(5./steps, 'ps2pdf')
905 run_subprocess(
906 'ps2pdf -sPAPERSIZE#letter -dMaxSubsetPct#100 '
907 '-dSubsetFonts#true -dCompatibilityLevel#1.2 '
908 '-dEmbedAllFonts#true api.ps api.pdf')
909 except RunSubprocessError, e:
910 if running == 'latex':
911 e.out = re.sub(r'(?sm)\A.*?!( LaTeX Error:)?', r'', e.out)
912 e.out = re.sub(r'(?sm)\s*Type X to quit.*', '', e.out)
913 e.out = re.sub(r'(?sm)^! Emergency stop.*', '', e.out)
914 log.error("%s failed: %s" % (running, (e.out+e.err).lstrip()))
915 except OSError, e:
916 log.error("%s failed: %s" % (running, e))
917 finally:
918 os.chdir(oldpath)
919 log.end_progress()
920
921 -def write_text(docindex, options):
922 log.start_progress('Writing output')
923 from epydoc.docwriter.plaintext import PlaintextWriter
924 plaintext_writer = PlaintextWriter()
925 s = ''
926 for apidoc in docindex.root:
927 s += plaintext_writer.write(apidoc)
928 log.end_progress()
929 if isinstance(s, unicode):
930 s = s.encode('ascii', 'backslashreplace')
931 print s
932
936
938
939 options, names = parse_arguments()
940
941 try:
942 try:
943 if options.profile:
944 _profile()
945 else:
946 main(options, names)
947 finally:
948 log.close()
949 except SystemExit:
950 raise
951 except KeyboardInterrupt:
952 print '\n\n'
953 print >>sys.stderr, 'Keyboard interrupt.'
954 except:
955 if options.debug: raise
956 print '\n\n'
957 exc_info = sys.exc_info()
958 if isinstance(exc_info[0], basestring): e = exc_info[0]
959 else: e = exc_info[1]
960 print >>sys.stderr, ('\nUNEXPECTED ERROR:\n'
961 '%s\n' % (str(e) or e.__class__.__name__))
962 print >>sys.stderr, 'Use --debug to see trace information.'
963 sys.exit(3)
964
966
967 if PROFILER == 'hotshot':
968 try: import hotshot, hotshot.stats
969 except ImportError:
970 print >>sys.stderr, "Could not import profile module!"
971 return
972 try:
973 prof = hotshot.Profile('hotshot.out')
974 prof = prof.runctx('main(*parse_arguments())', globals(), {})
975 except SystemExit:
976 pass
977 prof.close()
978
979 print 'Consolidating hotshot profiling info...'
980 hotshot.stats.load('hotshot.out').dump_stats('profile.out')
981
982
983 elif PROFILER == 'profile':
984 try: from profile import Profile
985 except ImportError:
986 print >>sys.stderr, "Could not import profile module!"
987 return
988
989
990
991
992
993 if (Profile.dispatch['c_exception'] is
994 Profile.trace_dispatch_exception.im_func):
995 trace_dispatch_return = Profile.trace_dispatch_return.im_func
996 Profile.dispatch['c_exception'] = trace_dispatch_return
997 try:
998 prof = Profile()
999 prof = prof.runctx('main(*parse_arguments())', globals(), {})
1000 except SystemExit:
1001 pass
1002 prof.dump_stats('profile.out')
1003
1004 else:
1005 print >>sys.stderr, 'Unknown profiler %s' % PROFILER
1006 return
1007
1008
1009
1010
1011
1013 """
1014 A class that can be used to portably generate formatted output to
1015 a terminal. See
1016 U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116}
1017 for documentation. (This is a somewhat stripped-down version.)
1018 """
1019 BOL = ''
1020 UP = ''
1021 DOWN = ''
1022 LEFT = ''
1023 RIGHT = ''
1024 CLEAR_EOL = ''
1025 CLEAR_LINE = ''
1026 BOLD = ''
1027 NORMAL = ''
1028 COLS = 75
1029 BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
1030
1031 _STRING_CAPABILITIES = """
1032 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
1033 CLEAR_EOL=el BOLD=bold UNDERLINE=smul NORMAL=sgr0""".split()
1034 _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
1035 _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
1036
1037
1038
1039
1040 FORCE_SIMPLE_TERM = False
1041
1042 - def __init__(self, term_stream=sys.stdout):
1043
1044 if not term_stream.isatty(): return
1045 if self.FORCE_SIMPLE_TERM: return
1046
1047
1048 try: import curses
1049 except:
1050
1051
1052 self.BOL = '\r'
1053 self.CLEAR_LINE = '\r' + ' '*self.COLS + '\r'
1054
1055
1056
1057 try: curses.setupterm()
1058 except: return
1059
1060
1061 self.COLS = curses.tigetnum('cols')
1062
1063
1064 for capability in self._STRING_CAPABILITIES:
1065 (attrib, cap_name) = capability.split('=')
1066 setattr(self, attrib, self._tigetstr(cap_name) or '')
1067 if self.BOL and self.CLEAR_EOL:
1068 self.CLEAR_LINE = self.BOL+self.CLEAR_EOL
1069
1070
1071 set_fg = self._tigetstr('setf')
1072 if set_fg:
1073 for i,color in zip(range(len(self._COLORS)), self._COLORS):
1074 setattr(self, color, curses.tparm(set_fg, i) or '')
1075 set_fg_ansi = self._tigetstr('setaf')
1076 if set_fg_ansi:
1077 for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
1078 setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
1079
1081
1082
1083
1084 import curses
1085 cap = curses.tigetstr(cap_name) or ''
1086 return re.sub(r'\$<\d+>[/*]?', '', cap)
1087
1089 - def __init__(self, verbosity, progress_mode=None):
1090 self._verbosity = verbosity
1091 self._progress = None
1092 self._message_blocks = []
1093
1094 self._progress_start_time = None
1095
1096 self._task_times = []
1097 self._progress_header = None
1098
1099 self.reported_message_levels = set()
1100 """This set contains all the message levels (WARNING, ERROR,
1101 etc) that have been reported. It is used by the options
1102 --fail-on-warning etc to determine the return value."""
1103
1104 self.supressed_docstring_warning = 0
1105 """This variable will be incremented once every time a
1106 docstring warning is reported tothe logger, but the verbosity
1107 level is too low for it to be displayed."""
1108
1109 self.term = TerminalController()
1110
1111
1112 if verbosity >= 2: self._progress_mode = 'list'
1113 elif verbosity >= 0:
1114 if progress_mode is not None:
1115 self._progress_mode = progress_mode
1116 elif self.term.COLS < 15:
1117 self._progress_mode = 'simple-bar'
1118 elif self.term.BOL and self.term.CLEAR_EOL and self.term.UP:
1119 self._progress_mode = 'multiline-bar'
1120 elif self.term.BOL and self.term.CLEAR_LINE:
1121 self._progress_mode = 'bar'
1122 else:
1123 self._progress_mode = 'simple-bar'
1124 else: self._progress_mode = 'hide'
1125
1127 self._message_blocks.append( (header, []) )
1128
1130 header, messages = self._message_blocks.pop()
1131 if messages:
1132 width = self.term.COLS - 5 - 2*len(self._message_blocks)
1133 prefix = self.term.CYAN+self.term.BOLD+'| '+self.term.NORMAL
1134 divider = (self.term.CYAN+self.term.BOLD+'+'+'-'*(width-1)+
1135 self.term.NORMAL)
1136
1137 header = wordwrap(header, right=width-2, splitchars='\\/').rstrip()
1138 header = '\n'.join([prefix+self.term.CYAN+l+self.term.NORMAL
1139 for l in header.split('\n')])
1140
1141 body = ''
1142 for message in messages:
1143 if message.endswith('\n'): body += message
1144 else: body += message+'\n'
1145
1146 body = '\n'.join([prefix+' '+l for l in body.split('\n')])
1147
1148 message = divider + '\n' + header + '\n' + body + '\n'
1149 self._report(message)
1150
1166
1167 - def log(self, level, message):
1168 self.reported_message_levels.add(level)
1169 if self._verbosity >= -2 and level >= log.ERROR:
1170 message = self._format(' Error: ', message, self.term.RED)
1171 elif self._verbosity >= -1 and level >= log.WARNING:
1172 message = self._format('Warning: ', message, self.term.YELLOW)
1173 elif self._verbosity >= 1 and level >= log.DOCSTRING_WARNING:
1174 message = self._format('Warning: ', message, self.term.YELLOW)
1175 elif self._verbosity >= 3 and level >= log.INFO:
1176 message = self._format(' Info: ', message, self.term.NORMAL)
1177 elif epydoc.DEBUG and level == log.DEBUG:
1178 message = self._format(' Debug: ', message, self.term.CYAN)
1179 else:
1180 if level >= log.DOCSTRING_WARNING:
1181 self.supressed_docstring_warning += 1
1182 return
1183
1184 self._report(message)
1185
1187 if not message.endswith('\n'): message += '\n'
1188
1189 if self._message_blocks:
1190 self._message_blocks[-1][-1].append(message)
1191 else:
1192
1193
1194 if self._progress_mode == 'simple-bar':
1195 if self._progress is not None:
1196 print
1197 self._progress = None
1198 if self._progress_mode == 'bar':
1199 sys.stdout.write(self.term.CLEAR_LINE)
1200 if self._progress_mode == 'multiline-bar':
1201 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 +
1202 self.term.CLEAR_EOL + self.term.UP*2)
1203
1204
1205 sys.stdout.write(message)
1206 sys.stdout.flush()
1207
1208 - def progress(self, percent, message=''):
1209 percent = min(1.0, percent)
1210 message = '%s' % message
1211
1212 if self._progress_mode == 'list':
1213 if message:
1214 print '[%3d%%] %s' % (100*percent, message)
1215 sys.stdout.flush()
1216
1217 elif self._progress_mode == 'bar':
1218 dots = int((self.term.COLS/2-8)*percent)
1219 background = '-'*(self.term.COLS/2-8)
1220 if len(message) > self.term.COLS/2:
1221 message = message[:self.term.COLS/2-3]+'...'
1222 sys.stdout.write(self.term.CLEAR_LINE + '%3d%% '%(100*percent) +
1223 self.term.GREEN + '[' + self.term.BOLD +
1224 '='*dots + background[dots:] + self.term.NORMAL +
1225 self.term.GREEN + '] ' + self.term.NORMAL +
1226 message + self.term.BOL)
1227 sys.stdout.flush()
1228 self._progress = percent
1229 elif self._progress_mode == 'multiline-bar':
1230 dots = int((self.term.COLS-10)*percent)
1231 background = '-'*(self.term.COLS-10)
1232
1233 if len(message) > self.term.COLS-10:
1234 message = message[:self.term.COLS-10-3]+'...'
1235 else:
1236 message = message.center(self.term.COLS-10)
1237
1238 time_elapsed = time.time()-self._progress_start_time
1239 if percent > 0:
1240 time_remain = (time_elapsed / percent) * (1-percent)
1241 else:
1242 time_remain = 0
1243
1244 sys.stdout.write(
1245
1246 self.term.CLEAR_EOL + ' ' +
1247 '%-8s' % self._timestr(time_elapsed) +
1248 self.term.BOLD + 'Progress:'.center(self.term.COLS-26) +
1249 self.term.NORMAL + '%8s' % self._timestr(time_remain) + '\n' +
1250
1251 self.term.CLEAR_EOL + ('%3d%% ' % (100*percent)) +
1252 self.term.GREEN + '[' + self.term.BOLD + '='*dots +
1253 background[dots:] + self.term.NORMAL + self.term.GREEN +
1254 ']' + self.term.NORMAL + '\n' +
1255
1256 self.term.CLEAR_EOL + ' ' + message + self.term.BOL +
1257 self.term.UP + self.term.UP)
1258
1259 sys.stdout.flush()
1260 self._progress = percent
1261 elif self._progress_mode == 'simple-bar':
1262 if self._progress is None:
1263 sys.stdout.write(' [')
1264 self._progress = 0.0
1265 dots = int((self.term.COLS-2)*percent)
1266 progress_dots = int((self.term.COLS-2)*self._progress)
1267 if dots > progress_dots:
1268 sys.stdout.write('.'*(dots-progress_dots))
1269 sys.stdout.flush()
1270 self._progress = percent
1271
1273 dt = int(dt)
1274 if dt >= 3600:
1275 return '%d:%02d:%02d' % (dt/3600, dt%3600/60, dt%60)
1276 else:
1277 return '%02d:%02d' % (dt/60, dt%60)
1278
1280 if self._progress is not None:
1281 raise ValueError
1282 self._progress = None
1283 self._progress_start_time = time.time()
1284 self._progress_header = header
1285 if self._progress_mode != 'hide' and header:
1286 print self.term.BOLD + header + self.term.NORMAL
1287
1289 self.progress(1.)
1290 if self._progress_mode == 'bar':
1291 sys.stdout.write(self.term.CLEAR_LINE)
1292 if self._progress_mode == 'multiline-bar':
1293 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 +
1294 self.term.CLEAR_EOL + self.term.UP*2)
1295 if self._progress_mode == 'simple-bar':
1296 print ']'
1297 self._progress = None
1298 self._task_times.append( (time.time()-self._progress_start_time,
1299 self._progress_header) )
1300
1302 print
1303 print 'Timing summary:'
1304 total = sum([time for (time, task) in self._task_times])
1305 max_t = max([time for (time, task) in self._task_times])
1306 for (time, task) in self._task_times:
1307 task = task[:31]
1308 print ' %s%s %7.1fs' % (task, '.'*(35-len(task)), time),
1309 if self.term.COLS > 55:
1310 print '|'+'=' * int((self.term.COLS-53) * time / max_t)
1311 else:
1312 print
1313 print
1314
1316 - def __init__(self, verbosity, stages, progress_mode=None):
1317 self.stage = 0
1318 self.stages = stages
1319 self.task = None
1320 ConsoleLogger.__init__(self, verbosity, progress_mode)
1321
1322 - def progress(self, percent, message=''):
1323
1324 i = self.stage-1
1325 p = ((sum(self.stages[:i]) + percent*self.stages[i]) /
1326 float(sum(self.stages)))
1327
1328 if message is UNKNOWN: message = None
1329 if message: message = '%s: %s' % (self.task, message)
1330 ConsoleLogger.progress(self, p, message)
1331
1337
1341
1344
1346 """
1347 A logger used to generate a log of all warnings and messages to an
1348 HTML file.
1349 """
1350
1351 FILENAME = "epydoc-log.html"
1352 HEADER = textwrap.dedent('''\
1353 <?xml version="1.0" encoding="ascii"?>
1354 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1355 "DTD/xhtml1-transitional.dtd">
1356 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
1357 <head>
1358 <title>Epydoc Log</title>
1359 <link rel="stylesheet" href="epydoc.css" type="text/css" />
1360 </head>
1361
1362 <body bgcolor="white" text="black" link="blue" vlink="#204080"
1363 alink="#204080">
1364 <h1 class="epydoc">Epydoc Log</h1>
1365 <p class="log">Epydoc started at %s</p>''')
1366 START_BLOCK = '<div class="log-block"><h2 class="log-hdr">%s</h2>'
1367 MESSAGE = ('<div class="log-%s"><b>%s</b>: \n'
1368 '%s</div>\n')
1369 END_BLOCK = '</div>'
1370 FOOTER = "</body>\n</html>\n"
1371
1372 - def __init__(self, directory, options):
1373 self.start_time = time.time()
1374 self.out = open(os.path.join(directory, self.FILENAME), 'w')
1375 self.out.write(self.HEADER % time.ctime(self.start_time))
1376 self.is_empty = True
1377 self.options = options
1378
1380 self.out.write(self.START_BLOCK % 'Epydoc Options')
1381 msg = '<table border="0" cellpadding="0" cellspacing="0">\n'
1382 opts = [(key, getattr(options, key)) for key in dir(options)
1383 if key not in dir(optparse.Values)]
1384 opts = [(val==OPTION_DEFAULTS.get(key), key, val)
1385 for (key, val) in opts]
1386 for is_default, key, val in sorted(opts):
1387 css = is_default and 'opt-default' or 'opt-changed'
1388 msg += ('<tr valign="top" class="%s"><td valign="top">%s</td>'
1389 '<td valign="top"><tt> = </tt></td>'
1390 '<td valign="top"><tt>%s</tt></td></tr>' %
1391 (css, key, plaintext_to_html(repr(val))))
1392 msg += '</table>\n'
1393 self.out.write('<div class="log-info">\n%s</div>\n' % msg)
1394 self.out.write(self.END_BLOCK)
1395
1398
1401
1402 - def log(self, level, message):
1410
1412 self.is_empty = False
1413 message = plaintext_to_html(message)
1414 if '\n' in message:
1415 message = '<pre class="log">%s</pre>' % message
1416 hdr = ' '.join([w.capitalize() for w in level.split()])
1417 return self.MESSAGE % (level.split()[-1], hdr, message)
1418
1420 if self.is_empty:
1421 self.out.write('<div class="log-info">'
1422 'No warnings or errors!</div>')
1423 self.write_options(self.options)
1424 self.out.write('<p class="log">Epydoc finished at %s</p>\n'
1425 '<p class="log">(Elapsed time: %s)</p>' %
1426 (time.ctime(), self._elapsed_time()))
1427 self.out.write(self.FOOTER)
1428 self.out.close()
1429
1431 secs = int(time.time()-self.start_time)
1432 if secs < 60:
1433 return '%d seconds' % secs
1434 if secs < 3600:
1435 return '%d minutes, %d seconds' % (secs/60, secs%60)
1436 else:
1437 return '%d hours, %d minutes' % (secs/3600, secs%3600)
1438
1439
1440
1441
1442
1443
1444 if __name__ == '__main__':
1445 cli()
1446