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, tempfile, shutil
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, TerminalController
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 from epydoc.docwriter.latex_sty import STYLESHEETS as STY_STYLESHEETS
79 from epydoc.docwriter.dotgraph import DotGraph
80 from epydoc.docwriter.dotgraph import COLOR as GRAPH_COLOR
81
82
83 try:
84 from epydoc.docwriter import xlink
85 except:
86 xlink = None
87
88 INHERITANCE_STYLES = ('grouped', 'listed', 'included', 'hidden')
89 GRAPH_TYPES = ('classtree', 'callgraph', 'umlclasstree')
90 ACTIONS = ('html', 'text', 'latex', 'dvi', 'ps', 'pdf', 'check')
91 DEFAULT_DOCFORMAT = 'epytext'
92 PROFILER = 'profile'
93 TARGET_ACTIONS = ('html', 'latex', 'dvi', 'ps', 'pdf')
94 DEFAULT_ACTIONS = ('html',)
95 PDFDRIVERS = ('pdflatex', 'latex', 'auto')
96
97
98
99
100
101 DOCFORMATS = ('epytext', 'plaintext', 'restructuredtext', 'javadoc')
102 HELP_TOPICS = {
103 'docformat': textwrap.dedent('''\
104 __docformat__ is a module variable that specifies the markup
105 language for the docstrings in a module. Its value is a
106 string, consisting the name of a markup language, optionally
107 followed by a language code (such as "en" for English). Epydoc
108 currently recognizes the following markup language names:
109 ''' + ', '.join(DOCFORMATS)),
110 'inheritance': textwrap.dedent('''\
111 The following inheritance formats are currently supported:
112 - grouped: inherited objects are gathered into groups,
113 based on what class they were inherited from.
114 - listed: inherited objects are listed in a short list
115 at the end of their section.
116 - included: inherited objects are mixed in with
117 non-inherited objects.'''),
118 'css': textwrap.dedent(
119 'The following built-in CSS stylesheets are available:\n' +
120 '\n'.join([' %10s: %s' % (key, descr)
121 for (key, (sheet, descr))
122 in CSS_STYLESHEETS.items()])),
123 'sty': textwrap.dedent(
124 'The following built-in LaTeX style files are available:\n' +
125 ', '.join(STY_STYLESHEETS)),
126
127
128
129 }
130
131
132 HELP_TOPICS['topics'] = wordwrap(
133 'Epydoc can provide additional help for the following topics: ' +
134 ', '.join(['%r' % topic for topic in HELP_TOPICS.keys()]))
135
136
137
138
139
140 DEFAULT_TARGET = dict(
141 html='html', latex='latex', dvi='api.dvi', ps='api.ps',
142 pdf='api.pdf', pickle='api.pickle')
143
145 return dict(
146 actions=[], show_frames=True, docformat=DEFAULT_DOCFORMAT,
147 show_private=True, show_imports=False, inheritance="listed",
148 verbose=0, quiet=0, load_pickle=False, parse=True, introspect=True,
149 debug=epydoc.DEBUG, profile=False, graphs=[],
150 list_classes_separately=False, graph_font=None, graph_font_size=None,
151 include_source_code=True, pstat_files=[], simple_term=False,
152 fail_on=None, exclude=[], exclude_parse=[], exclude_introspect=[],
153 external_api=[], external_api_file=[], external_api_root=[],
154 redundant_details=False, src_code_tab_width=8, verbosity=0,
155 include_timestamp=True, target={}, default_target=None,
156 pdfdriver='auto', show_submodule_list=True, inherit_from_object=False,
157 show_deprecated=True)
158
159
160
162 action = opt.replace('-', '')
163 optparser.values.actions.append(action)
164
166 if optparser.values.actions:
167 optparser.values.target[optparser.values.actions[-1]] = value
168 elif optparser.values.default_target is None:
169 optparser.values.default_target = value
170 else:
171 optparser.error("Default output target specified multiple times!")
172
174
175 usage = '%prog [ACTION] [options] NAMES...'
176 version = "Epydoc, version %s" % epydoc.__version__
177 optparser = OptionParser(usage=usage, add_help_option=False)
178
179 optparser.add_option('--config',
180 action='append', dest="configfiles", metavar='FILE',
181 help=("A configuration file, specifying additional OPTIONS "
182 "and/or NAMES. This option may be repeated."))
183
184 optparser.add_option("--output", "-o",
185 action='callback', callback=add_target, type='string', metavar="PATH",
186 help="The output directory. If PATH does not exist, then "
187 "it will be created.")
188
189 optparser.add_option("--quiet", "-q",
190 action="count", dest="quiet",
191 help="Decrease the verbosity.")
192
193 optparser.add_option("--verbose", "-v",
194 action="count", dest="verbose",
195 help="Increase the verbosity.")
196
197 optparser.add_option("--debug",
198 action="store_true", dest="debug",
199 help="Show full tracebacks for internal errors.")
200
201 optparser.add_option("--simple-term",
202 action="store_true", dest="simple_term",
203 help="Do not try to use color or cursor control when displaying "
204 "the progress bar, warnings, or errors.")
205
206 action_group = OptionGroup(optparser, 'Actions')
207 optparser.add_option_group(action_group)
208
209 action_group.add_option("--html",
210 action='callback', callback=add_action,
211 help="Write HTML output.")
212
213 action_group.add_option("--text",
214 action='callback', callback=add_action,
215 help="Write plaintext output. (not implemented yet)")
216
217 action_group.add_option("--latex",
218 action='callback', callback=add_action,
219 help="Write LaTeX output.")
220
221 action_group.add_option("--dvi",
222 action='callback', callback=add_action,
223 help="Write DVI output.")
224
225 action_group.add_option("--ps",
226 action='callback', callback=add_action,
227 help="Write Postscript output.")
228
229 action_group.add_option("--pdf",
230 action='callback', callback=add_action,
231 help="Write PDF output.")
232
233 action_group.add_option("--check",
234 action='callback', callback=add_action,
235 help="Check completeness of docs.")
236
237 action_group.add_option("--pickle",
238 action='callback', callback=add_action,
239 help="Write the documentation to a pickle file.")
240
241
242 action_group.add_option("--version",
243 action='callback', callback=add_action,
244 help="Show epydoc's version number and exit.")
245
246 action_group.add_option("-h", "--help",
247 action='callback', callback=add_action,
248 help="Show this message and exit. For help on specific "
249 "topics, use \"--help TOPIC\". Use \"--help topics\" for a "
250 "list of available help topics")
251
252
253 generation_group = OptionGroup(optparser, 'Generation Options')
254 optparser.add_option_group(generation_group)
255
256 generation_group.add_option("--docformat",
257 dest="docformat", metavar="NAME",
258 help="The default markup language for docstrings. Defaults "
259 "to \"%s\"." % DEFAULT_DOCFORMAT)
260
261 generation_group.add_option("--parse-only",
262 action="store_false", dest="introspect",
263 help="Get all information from parsing (don't introspect)")
264
265 generation_group.add_option("--introspect-only",
266 action="store_false", dest="parse",
267 help="Get all information from introspecting (don't parse)")
268
269 generation_group.add_option("--exclude",
270 dest="exclude", metavar="PATTERN", action="append",
271 help="Exclude modules whose dotted name matches "
272 "the regular expression PATTERN")
273
274 generation_group.add_option("--exclude-introspect",
275 dest="exclude_introspect", metavar="PATTERN", action="append",
276 help="Exclude introspection of modules whose dotted name matches "
277 "the regular expression PATTERN")
278
279 generation_group.add_option("--exclude-parse",
280 dest="exclude_parse", metavar="PATTERN", action="append",
281 help="Exclude parsing of modules whose dotted name matches "
282 "the regular expression PATTERN")
283
284 generation_group.add_option("--inheritance",
285 dest="inheritance", metavar="STYLE",
286 help="The format for showing inheritance objects. STYLE "
287 "should be one of: %s." % ', '.join(INHERITANCE_STYLES))
288
289 generation_group.add_option("--show-private",
290 action="store_true", dest="show_private",
291 help="Include private variables in the output. (default)")
292
293 generation_group.add_option("--no-private",
294 action="store_false", dest="show_private",
295 help="Do not include private variables in the output.")
296
297 generation_group.add_option("--show-deprecated",
298 action="store_true", dest="show_deprecated",
299 help="Include deprecated objects in the output. (default)")
300
301 generation_group.add_option("--no-deprecated",
302 action="store_false", dest="show_deprecated",
303 help="Do not include deprecated objects in the output.")
304
305 generation_group.add_option("--show-imports",
306 action="store_true", dest="show_imports",
307 help="List each module's imports.")
308
309 generation_group.add_option("--no-imports",
310 action="store_false", dest="show_imports",
311 help="Do not list each module's imports. (default)")
312
313 generation_group.add_option('--show-sourcecode',
314 action='store_true', dest='include_source_code',
315 help=("Include source code with syntax highlighting in the "
316 "HTML output. (default)"))
317
318 generation_group.add_option('--no-sourcecode',
319 action='store_false', dest='include_source_code',
320 help=("Do not include source code with syntax highlighting in the "
321 "HTML output."))
322
323 generation_group.add_option('--include-log',
324 action='store_true', dest='include_log',
325 help=("Include a page with the process log (epydoc-log.html)"))
326
327 generation_group.add_option('--redundant-details',
328 action='store_true', dest='redundant_details',
329 help=("Include values in the details lists even if all info "
330 "about them is already provided by the summary table."))
331
332 generation_group.add_option('--show-submodule-list',
333 action='store_true', dest='show_submodule_list',
334 help="Include a list of submodules on package documentation "
335 "pages. (default)")
336
337 generation_group.add_option('--no-submodule-list',
338 action='store_false', dest='show_submodule_list',
339 help="Do not include a list of submodules on package "
340 "documentation pages.")
341
342 generation_group.add_option('--inherit-from-object',
343 action='store_true', dest='inherit_from_object',
344 help="Include methods & properties that are inherited from "
345 "\"object\".")
346
347 generation_group.add_option('--no-inherit-from-object',
348 action='store_false', dest='inherit_from_object',
349 help="Do not include methods & properties that are inherited "
350 "from \"object\". (default)")
351
352 generation_group.add_option('--google-analytics',
353 action='store', dest='google_analytics',
354 help="Tracker code for google analytics (eg UA-427085-11).")
355
356 output_group = OptionGroup(optparser, 'Output Options')
357 optparser.add_option_group(output_group)
358
359 output_group.add_option("--name", "-n",
360 dest="prj_name", metavar="NAME",
361 help="The documented project's name (for the navigation bar).")
362
363 output_group.add_option("--css", "-c",
364 dest="css", metavar="STYLESHEET",
365 help="The CSS stylesheet. STYLESHEET can be either a "
366 "builtin stylesheet or the name of a CSS file.")
367
368 output_group.add_option("--sty",
369 dest="sty", metavar="LATEXSTYLE",
370 help="The LaTeX style file. LATEXSTYLE can be either a "
371 "builtin style file or the name of a .sty file.")
372
373 output_group.add_option("--pdfdriver",
374 dest="pdfdriver", metavar="DRIVER",
375 help="The command sequence that should be used to render "
376 "pdf output. \"pdflatex\" will generate the pdf directly "
377 "using pdflatex. \"latex\" will generate the pdf using "
378 "latex, dvips, and ps2pdf in succession. \"auto\" will use "
379 "pdflatex if available, and latex otherwise.")
380
381 output_group.add_option("--url", "-u",
382 dest="prj_url", metavar="URL",
383 help="The documented project's URL (for the navigation bar).")
384
385 output_group.add_option("--navlink",
386 dest="prj_link", metavar="HTML",
387 help="HTML code for a navigation link to place in the "
388 "navigation bar.")
389
390 output_group.add_option("--top",
391 dest="top_page", metavar="PAGE",
392 help="The \"top\" page for the HTML documentation. PAGE can "
393 "be a URL, the name of a module or class, or one of the "
394 "special names \"trees.html\", \"indices.html\", or \"help.html\"")
395
396 output_group.add_option("--help-file",
397 dest="help_file", metavar="FILE",
398 help="An alternate help file. FILE should contain the body "
399 "of an HTML file -- navigation bars will be added to it.")
400
401 output_group.add_option("--show-frames",
402 action="store_true", dest="show_frames",
403 help="Include frames in the HTML output. (default)")
404
405 output_group.add_option("--no-frames",
406 action="store_false", dest="show_frames",
407 help="Do not include frames in the HTML output.")
408
409 output_group.add_option('--separate-classes',
410 action='store_true', dest='list_classes_separately',
411 help=("When generating LaTeX or PDF output, list each class in "
412 "its own section, instead of listing them under their "
413 "containing module."))
414
415 output_group.add_option('--src-code-tab-width',
416 action='store', type='int', dest='src_code_tab_width',
417 help=("When generating HTML output, sets the number of spaces "
418 "each tab in source code listings is replaced with."))
419
420 output_group.add_option('--suppress-timestamp',
421 action='store_false', dest='include_timestamp',
422 help=("Do not include a timestamp in the generated output."))
423
424
425
426 if xlink is not None:
427 link_group = OptionGroup(optparser,
428 xlink.ApiLinkReader.settings_spec[0])
429 optparser.add_option_group(link_group)
430
431 for help, names, opts in xlink.ApiLinkReader.settings_spec[2]:
432 opts = opts.copy()
433 opts['help'] = help
434 link_group.add_option(*names, **opts)
435
436 graph_group = OptionGroup(optparser, 'Graph Options')
437 optparser.add_option_group(graph_group)
438
439 graph_group.add_option('--graph',
440 action='append', dest='graphs', metavar='GRAPHTYPE',
441 help=("Include graphs of type GRAPHTYPE in the generated output. "
442 "Graphs are generated using the Graphviz dot executable. "
443 "If this executable is not on the path, then use --dotpath "
444 "to specify its location. This option may be repeated to "
445 "include multiple graph types in the output. GRAPHTYPE "
446 "should be one of: all, %s." % ', '.join(GRAPH_TYPES)))
447
448 graph_group.add_option("--dotpath",
449 dest="dotpath", metavar='PATH',
450 help="The path to the Graphviz 'dot' executable.")
451
452 graph_group.add_option('--graph-font',
453 dest='graph_font', metavar='FONT',
454 help=("Specify the font used to generate Graphviz graphs. (e.g., "
455 "helvetica or times)."))
456
457 graph_group.add_option('--graph-font-size',
458 dest='graph_font_size', metavar='SIZE',
459 help=("Specify the font size used to generate Graphviz graphs, "
460 "in points."))
461
462 graph_group.add_option('--pstat',
463 action='append', dest='pstat_files', metavar='FILE',
464 help="A pstat output file, to be used in generating call graphs.")
465
466 graph_group.add_option('--max-html-graph-size',
467 action='store', dest='max_html_graph_size', metavar='SIZE',
468 help="Set the maximum graph size for HTML graphs. This should "
469 "be a string of the form \"w,h\", specifying the maximum width "
470 "and height in inches. Default=%r" % DotGraph.DEFAULT_HTML_SIZE)
471
472 graph_group.add_option('--max-latex-graph-size',
473 action='store', dest='max_latex_graph_size', metavar='SIZE',
474 help="Set the maximum graph size for LATEX graphs. This should "
475 "be a string of the form \"w,h\", specifying the maximum width "
476 "and height in inches. Default=%r" % DotGraph.DEFAULT_LATEX_SIZE)
477
478 graph_group.add_option('--graph-image-format',
479 dest='graph_image_format', metavar='FORMAT',
480 help="Specify the file format used for graph images in the HTML "
481 "output. Can be one of gif, png, jpg. Default=%r" %
482 DotGraph.DEFAULT_HTML_IMAGE_FORMAT)
483
484
485 graph_group.add_option("--profile-epydoc",
486 action="store_true", dest="profile",
487 help=SUPPRESS_HELP or
488 ("Run the hotshot profiler on epydoc itself. Output "
489 "will be written to profile.out."))
490
491 return_group = OptionGroup(optparser, 'Return Value Options')
492 optparser.add_option_group(return_group)
493
494 return_group.add_option("--fail-on-error",
495 action="store_const", dest="fail_on", const=log.ERROR,
496 help="Return a non-zero exit status, indicating failure, if any "
497 "errors are encountered.")
498
499 return_group.add_option("--fail-on-warning",
500 action="store_const", dest="fail_on", const=log.WARNING,
501 help="Return a non-zero exit status, indicating failure, if any "
502 "errors or warnings are encountered (not including docstring "
503 "warnings).")
504
505 return_group.add_option("--fail-on-docstring-warning",
506 action="store_const", dest="fail_on", const=log.DOCSTRING_WARNING,
507 help="Return a non-zero exit status, indicating failure, if any "
508 "errors or warnings are encountered (including docstring "
509 "warnings).")
510
511
512 optparser.set_defaults(**option_defaults())
513
514
515 options, names = optparser.parse_args()
516
517
518
519 if 'help' in options.actions:
520 names = set([n.lower() for n in names])
521 for (topic, msg) in HELP_TOPICS.items():
522 if topic.lower() in names:
523 print '\n' + msg.rstrip() + '\n'
524 sys.exit(0)
525 optparser.print_help()
526 sys.exit(0)
527
528
529 if 'version' in options.actions:
530 print version
531 sys.exit(0)
532
533
534 if options.configfiles:
535 try:
536 parse_configfiles(options.configfiles, options, names)
537 except (KeyboardInterrupt,SystemExit): raise
538 except Exception, e:
539 if len(options.configfiles) == 1:
540 cf_name = 'config file %s' % options.configfiles[0]
541 else:
542 cf_name = 'config files %s' % ', '.join(options.configfiles)
543 optparser.error('Error reading %s:\n %s' % (cf_name, e))
544
545
546 for name in names:
547 if name.endswith('.pickle'):
548 if len(names) != 1:
549 optparser.error("When a pickle file is specified, no other "
550 "input files may be specified.")
551 options.load_pickle = True
552
553
554 if len(names) == 0:
555 optparser.error("No names specified.")
556
557
558 for i, name in reversed(list(enumerate(names[:]))):
559 if '?' in name or '*' in name:
560 names[i:i+1] = glob(name)
561
562 if options.inheritance not in INHERITANCE_STYLES:
563 optparser.error("Bad inheritance style. Valid options are " +
564 ",".join(INHERITANCE_STYLES))
565 if not options.parse and not options.introspect:
566 optparser.error("Invalid option combination: --parse-only "
567 "and --introspect-only.")
568
569
570
571 options.graphs = [graph_type.lower() for graph_type in options.graphs]
572 for graph_type in options.graphs:
573 if graph_type == 'callgraph' and not options.pstat_files:
574 optparser.error('"callgraph" graph type may only be used if '
575 'one or more pstat files are specified.')
576
577
578 if graph_type == 'all':
579 if options.pstat_files:
580 options.graphs = GRAPH_TYPES
581 else:
582 options.graphs = [g for g in GRAPH_TYPES if g != 'callgraph']
583 break
584 elif graph_type not in GRAPH_TYPES:
585 optparser.error("Invalid graph type %s. Expected one of: %s." %
586 (graph_type, ', '.join(GRAPH_TYPES + ('all',))))
587
588
589
590 options.pdfdriver = options.pdfdriver.lower()
591 if options.pdfdriver not in PDFDRIVERS:
592 optparser.error("Invalid pdf driver %r. Expected one of: %s" %
593 (options.pdfdriver, ', '.join(PDF_DRIVERS)))
594 if (options.pdfdriver == 'pdflatex' and
595 ('dvi' in options.actions or 'ps' in options.actions)):
596 optparser.error("Use of the pdflatex driver is incompatible "
597 "with generating dvi or ps output.")
598
599
600 if options.max_html_graph_size:
601 if not re.match(r'^\d+\s*,\s*\d+$', options.max_html_graph_size):
602 optparser.error("Bad max-html-graph-size value: %r" %
603 options.max_html_graph_size)
604 DotGraph.DEFAULT_HTML_SIZE = options.max_html_graph_size
605 if options.max_latex_graph_size:
606 if not re.match(r'^\d+\s*,\s*\d+$', options.max_latex_graph_size):
607 optparser.error("Bad max-latex-graph-size value: %r" %
608 options.max_latex_graph_size)
609 DotGraph.DEFAULT_LATEX_SIZE = options.max_latex_graph_size
610 if options.graph_image_format:
611 if options.graph_image_format not in ('jpg', 'png', 'gif'):
612 optparser.error("Bad graph-image-format %r; expected one of: "
613 "jpg, png, gif." % options.graph_image_format)
614 DotGraph.DEFAULT_HTML_IMAGE_FORMAT = options.graph_image_format
615
616
617 verbosity = getattr(options, 'verbosity', 0)
618 options.verbosity = verbosity + options.verbose - options.quiet
619
620
621 options.names = names
622 return options
623
625 configparser = ConfigParser.ConfigParser()
626
627
628 for configfile in configfiles:
629 fp = open(configfile, 'r')
630 configparser.readfp(fp, configfile)
631 fp.close()
632 for optname in configparser.options('epydoc'):
633 val = configparser.get('epydoc', optname, vars=os.environ).strip()
634 optname = optname.lower().strip()
635
636 if optname in ('modules', 'objects', 'values',
637 'module', 'object', 'value'):
638 names.extend(_str_to_list(val))
639 elif optname == 'target':
640 options.default_target = val
641 elif optname == 'output':
642 for action, target in re.findall('(\w+)\s*(?:->\s*(\S+))?', val):
643 if action not in ACTIONS:
644 raise ValueError('"%s" expected one of %s, got %r' %
645 (optname, ', '.join(ACTIONS), action))
646 options.actions.append(action)
647 if target: options.target[action] = target
648 elif optname == 'verbosity':
649 options.verbosity = _str_to_int(val, optname)
650 elif optname == 'debug':
651 options.debug = _str_to_bool(val, optname)
652 elif optname in ('simple-term', 'simple_term'):
653 options.simple_term = _str_to_bool(val, optname)
654
655
656 elif optname == 'docformat':
657 options.docformat = val
658 elif optname == 'parse':
659 options.parse = _str_to_bool(val, optname)
660 elif optname == 'introspect':
661 options.introspect = _str_to_bool(val, optname)
662 elif optname == 'exclude':
663 options.exclude.extend(_str_to_list(val))
664 elif optname in ('exclude-parse', 'exclude_parse'):
665 options.exclude_parse.extend(_str_to_list(val))
666 elif optname in ('exclude-introspect', 'exclude_introspect'):
667 options.exclude_introspect.extend(_str_to_list(val))
668 elif optname == 'inheritance':
669 if val.lower() not in INHERITANCE_STYLES:
670 raise ValueError('"%s" expected one of: %s.' %
671 (optname, ', '.join(INHERITANCE_STYLES)))
672 options.inheritance = val.lower()
673 elif optname =='private':
674 options.show_private = _str_to_bool(val, optname)
675 elif optname =='deprecated':
676 options.show_deprecated = _str_to_bool(val, optname)
677 elif optname =='imports':
678 options.show_imports = _str_to_bool(val, optname)
679 elif optname == 'sourcecode':
680 options.include_source_code = _str_to_bool(val, optname)
681 elif optname in ('include-log', 'include_log'):
682 options.include_log = _str_to_bool(val, optname)
683 elif optname in ('redundant-details', 'redundant_details'):
684 options.redundant_details = _str_to_bool(val, optname)
685 elif optname in ('submodule-list', 'submodule_list'):
686 options.show_submodule_list = _str_to_bool(val, optname)
687 elif optname in ('inherit-from-object', 'inherit_from_object'):
688 options.inherit_from_object = _str_to_bool(val, optname)
689 elif optname in ('google-analytics', 'google_analytics'):
690 options.google_analytics = val
691
692
693 elif optname == 'name':
694 options.prj_name = val
695 elif optname == 'css':
696 options.css = val
697 elif optname == 'sty':
698 options.sty = val
699 elif optname == 'pdfdriver':
700 options.pdfdriver = val
701 elif optname == 'url':
702 options.prj_url = val
703 elif optname == 'link':
704 options.prj_link = val
705 elif optname == 'top':
706 options.top_page = val
707 elif optname == 'help':
708 options.help_file = val
709 elif optname =='frames':
710 options.show_frames = _str_to_bool(val, optname)
711 elif optname in ('separate-classes', 'separate_classes'):
712 options.list_classes_separately = _str_to_bool(val, optname)
713 elif optname in ('src-code-tab-width', 'src_code_tab_width'):
714 options.src_code_tab_width = _str_to_int(val, optname)
715 elif optname == 'timestamp':
716 options.include_timestamp = _str_to_bool(val, optname)
717
718
719 elif optname in ('external-api', 'external_api'):
720 options.external_api.extend(_str_to_list(val))
721 elif optname in ('external-api-file', 'external_api_file'):
722 options.external_api_file.extend(_str_to_list(val))
723 elif optname in ('external-api-root', 'external_api_root'):
724 options.external_api_root.extend(_str_to_list(val))
725
726
727 elif optname == 'graph':
728 graphtypes = _str_to_list(val)
729 for graphtype in graphtypes:
730 if graphtype not in GRAPH_TYPES + ('all',):
731 raise ValueError('"%s" expected one of: all, %s.' %
732 (optname, ', '.join(GRAPH_TYPES)))
733 options.graphs.extend(graphtypes)
734 elif optname == 'dotpath':
735 options.dotpath = val
736 elif optname in ('graph-font', 'graph_font'):
737 options.graph_font = val
738 elif optname in ('graph-font-size', 'graph_font_size'):
739 options.graph_font_size = _str_to_int(val, optname)
740 elif optname == 'pstat':
741 options.pstat_files.extend(_str_to_list(val))
742 elif optname in ('max-html-graph-size', 'max_html_graph_size'):
743 options.max_html_graph_size = val
744 elif optname in ('max-latex-graph-size', 'max_latex_graph_size'):
745 options.max_latex_graph_size = val
746 elif optname in ('graph-image-format', 'graph_image_format'):
747 options.graph_image_format = val
748 elif optname.startswith('graph-'):
749 color = optname[6:].upper().strip()
750 color = color.replace('-', '_')
751 color = color.replace('_BACKGROUND', '_BG')
752 if color in GRAPH_COLOR:
753 if not re.match(r'#[a-fA-F0-9]+|\w+', val):
754 raise ValueError('Bad color %r for %r' % (val, color))
755 GRAPH_COLOR[color] = val
756 else:
757 raise ValueError('Unknown option %s' % optname)
758
759
760 elif optname in ('failon', 'fail-on', 'fail_on'):
761 if val.lower().strip() in ('error', 'errors'):
762 options.fail_on = log.ERROR
763 elif val.lower().strip() in ('warning', 'warnings'):
764 options.fail_on = log.WARNING
765 elif val.lower().strip() in ('docstring_warning',
766 'docstring_warnings'):
767 options.fail_on = log.DOCSTRING_WARNING
768 else:
769 raise ValueError("%r expected one of: error, warning, "
770 "docstring_warning" % optname)
771 else:
772 raise ValueError('Unknown option %s' % optname)
773
775 if val.lower() in ('0', 'no', 'false', 'n', 'f', 'hide'):
776 return False
777 elif val.lower() in ('1', 'yes', 'true', 'y', 't', 'show'):
778 return True
779 else:
780 raise ValueError('"%s" option expected a boolean' % optname)
781
783 try:
784 return int(val)
785 except ValueError:
786 raise ValueError('"%s" option expected an int' % optname)
787
789 return val.replace(',', ' ').split()
790
791
792
793
794
796 """
797 Perform all actions indicated by the given set of options.
798
799 @return: the L{epydoc.apidoc.DocIndex} object created while
800 running epydoc (or None).
801 """
802
803 if options.debug:
804 epydoc.DEBUG = True
805
806 if not options.actions:
807 options.actions = DEFAULT_ACTIONS
808
809
810 loggers = []
811 if options.simple_term:
812 TerminalController.FORCE_SIMPLE_TERM = True
813 if options.actions == ['text']:
814 pass
815 elif options.verbosity > 1:
816 logger = ConsoleLogger(options.verbosity)
817 log.register_logger(logger)
818 loggers.append(logger)
819 else:
820
821
822 stages = [40,
823 7,
824 1,
825 3,
826 1,
827 30,
828 1,
829 2]
830 if options.load_pickle:
831 stages = [30]
832 if 'html' in options.actions: stages += [100]
833 if 'check' in options.actions: stages += [10]
834 if 'pickle' in options.actions: stages += [10]
835 if 'latex' in options.actions: stages += [60]
836 if 'pdf' in options.actions: stages += [50]
837 elif 'ps' in options.actions: stages += [40]
838 elif 'dvi' in options.actions: stages += [30]
839 if 'text' in options.actions: stages += [30]
840
841 if options.parse and not options.introspect:
842 del stages[1]
843 if options.introspect and not options.parse:
844 del stages[1:3]
845 logger = UnifiedProgressConsoleLogger(options.verbosity, stages)
846 log.register_logger(logger)
847 loggers.append(logger)
848
849
850 for (key, val) in DEFAULT_TARGET.items():
851 if options.default_target is not None:
852 options.target.setdefault(key, options.default_target)
853 else:
854 options.target.setdefault(key, val)
855
856
857 for action in ['pdf', 'ps', 'dvi', 'pickle']:
858 if action in options.target:
859 if not options.target[action].endswith('.%s' % action):
860 options.target[action] += '.%s' % action
861
862
863 for action in options.actions:
864 if action not in TARGET_ACTIONS: continue
865 target = options.target[action]
866 if os.path.exists(target):
867 if action not in ['html', 'latex'] and os.path.isdir(target):
868 log.error("%s is a directory" % target)
869 sys.exit(1)
870 elif action in ['html', 'latex'] and not os.path.isdir(target):
871 log.error("%s is not a directory" % target)
872 sys.exit(1)
873
874 if options.include_log:
875 if 'html' in options.actions:
876 if not os.path.exists(options.target['html']):
877 os.mkdir(options.target['html'])
878 logger = HTMLLogger(options.target['html'], options)
879 log.register_logger(logger)
880 loggers.append(logger)
881 else:
882 log.warning("--include-log requires --html")
883
884
885 from epydoc import docstringparser
886 docstringparser.DEFAULT_DOCFORMAT = options.docformat
887
888
889 if xlink is not None:
890 try:
891 xlink.ApiLinkReader.read_configuration(options, problematic=False)
892 except Exception, exc:
893 log.error("Error while configuring external API linking: %s: %s"
894 % (exc.__class__.__name__, exc))
895
896
897 if options.dotpath:
898 from epydoc.docwriter import dotgraph
899 dotgraph.DOT_COMMAND = options.dotpath
900
901
902 if options.graph_font:
903 from epydoc.docwriter import dotgraph
904 fontname = options.graph_font
905 dotgraph.DotGraph.DEFAULT_NODE_DEFAULTS['fontname'] = fontname
906 dotgraph.DotGraph.DEFAULT_EDGE_DEFAULTS['fontname'] = fontname
907 if options.graph_font_size:
908 from epydoc.docwriter import dotgraph
909 fontsize = options.graph_font_size
910 dotgraph.DotGraph.DEFAULT_NODE_DEFAULTS['fontsize'] = fontsize
911 dotgraph.DotGraph.DEFAULT_EDGE_DEFAULTS['fontsize'] = fontsize
912
913
914
915 if options.load_pickle:
916 assert len(options.names) == 1
917 log.start_progress('Deserializing')
918 log.progress(0.1, 'Loading %r' % options.names[0])
919 t0 = time.time()
920 unpickler = pickle.Unpickler(open(options.names[0], 'rb'))
921 unpickler.persistent_load = pickle_persistent_load
922 docindex = unpickler.load()
923 log.debug('deserialization time: %.1f sec' % (time.time()-t0))
924 log.end_progress()
925 else:
926
927 from epydoc.docbuilder import build_doc_index
928 exclude_parse = '|'.join(options.exclude_parse+options.exclude)
929 exclude_introspect = '|'.join(options.exclude_introspect+
930 options.exclude)
931 inherit_from_object = options.inherit_from_object
932 docindex = build_doc_index(options.names,
933 options.introspect, options.parse,
934 add_submodules=(options.actions!=['text']),
935 exclude_introspect=exclude_introspect,
936 exclude_parse=exclude_parse,
937 inherit_from_object=inherit_from_object)
938
939 if docindex is None:
940 for logger in loggers:
941 if log.ERROR in logger.reported_message_levels:
942 sys.exit(1)
943 else:
944 for logger in loggers: log.remove_logger(logger)
945 return
946
947
948 if options.pstat_files:
949 try: import pstats
950 except ImportError:
951 log.error("Could not import pstats -- ignoring pstat files.")
952 try:
953 profile_stats = pstats.Stats(options.pstat_files[0])
954 for filename in options.pstat_files[1:]:
955 profile_stats.add(filename)
956 except KeyboardInterrupt:
957 for logger in loggers: log.remove_logger(logger)
958 raise
959 except Exception, e:
960 log.error("Error reading pstat file: %s" % e)
961 profile_stats = None
962 if profile_stats is not None:
963 docindex.read_profiling_info(profile_stats)
964
965
966
967
968 if 'html' in options.actions:
969 write_html(docindex, options)
970 if 'check' in options.actions:
971 check_docs(docindex, options)
972 if 'pickle' in options.actions:
973 write_pickle(docindex, options)
974 if ('latex' in options.actions or 'dvi' in options.actions or
975 'ps' in options.actions or 'pdf' in options.actions):
976 write_latex(docindex, options)
977 if 'text' in options.actions:
978 write_text(docindex, options)
979
980
981 for logger in loggers:
982 if (isinstance(logger, ConsoleLogger) and
983 logger.suppressed_docstring_warning):
984 if logger.suppressed_docstring_warning == 1:
985 prefix = '1 markup error was found'
986 else:
987 prefix = ('%d markup errors were found' %
988 logger.suppressed_docstring_warning)
989 log.warning("%s while processing docstrings. Use the verbose "
990 "switch (-v) to display markup errors." % prefix)
991
992
993 if options.verbosity >= 2:
994 for logger in loggers:
995 if isinstance(logger, ConsoleLogger):
996 logger.print_times()
997 break
998
999
1000
1001 if options.fail_on is not None:
1002 max_reported_message_level = max(logger.reported_message_levels)
1003 if max_reported_message_level >= options.fail_on:
1004 sys.exit(2)
1005
1006
1007 for logger in loggers: log.remove_logger(logger)
1008
1009
1010 return docindex
1011
1021
1023 """Helper for writing output to a pickle file, which can then be
1024 read in at a later time. But loading the pickle is only marginally
1025 faster than building the docs from scratch, so this has pretty
1026 limited application."""
1027 log.start_progress('Serializing output')
1028 log.progress(0.2, 'Writing %r' % options.target['pickle'])
1029 outfile = open(options.target['pickle'], 'wb')
1030 pickler = pickle.Pickler(outfile, protocol=0)
1031 pickler.persistent_id = pickle_persistent_id
1032 pickler.dump(docindex)
1033 outfile.close()
1034 log.end_progress()
1035
1037 """Helper for pickling, which allows us to save and restore UNKNOWN,
1038 which is required to be identical to apidoc.UNKNOWN."""
1039 if obj is UNKNOWN: return 'UNKNOWN'
1040 else: return None
1041
1043 """Helper for pickling, which allows us to save and restore UNKNOWN,
1044 which is required to be identical to apidoc.UNKNOWN."""
1045 if identifier == 'UNKNOWN': return UNKNOWN
1046 else: raise pickle.UnpicklingError, 'Invalid persistent id'
1047
1048 _RERUN_LATEX_RE = re.compile(r'(?im)^LaTeX\s+Warning:\s+Label\(s\)\s+may'
1049 r'\s+have\s+changed.\s+Rerun')
1050
1052
1053
1054 if 'latex' in options.actions:
1055 latex_target = options.target['latex']
1056 else:
1057 latex_target = tempfile.mkdtemp()
1058
1059 log.start_progress('Writing LaTeX docs')
1060
1061
1062 if options.pdfdriver=='auto' and ('latex' in options.actions or
1063 'dvi' in options.actions or
1064 'ps' in options.actions or
1065 'pdf' in options.actions):
1066 if 'dvi' in options.actions or 'ps' in options.actions:
1067 options.pdfdriver = 'latex'
1068 else:
1069 try:
1070 run_subprocess('pdflatex --version')
1071 options.pdfdriver = 'pdflatex'
1072 except RunSubprocessError, e:
1073 options.pdfdriver = 'latex'
1074 log.info('%r pdfdriver selected' % options.pdfdriver)
1075
1076 from epydoc.docwriter.latex import LatexWriter, show_latex_warnings
1077 from epydoc.docwriter.latex import find_latex_error
1078 latex_writer = LatexWriter(docindex, **options.__dict__)
1079 try:
1080 latex_writer.write(latex_target)
1081 except IOError, e:
1082 log.error(e)
1083 log.end_progress()
1084 log.start_progress()
1085 log.end_progress()
1086 return
1087 log.end_progress()
1088
1089
1090 if 'pdf' in options.actions:
1091 if options.pdfdriver == 'latex': steps = 6
1092 elif 'ps' in options.actions: steps = 8
1093 elif 'dvi' in options.actions: steps = 7
1094 else: steps = 4
1095 elif 'ps' in options.actions: steps = 5
1096 elif 'dvi' in options.actions: steps = 4
1097 else:
1098
1099
1100 assert 'latex' in options.actions
1101 return
1102
1103
1104 if options.pdfdriver == 'latex':
1105 latex_commands = ['latex']
1106 elif 'dvi' in options.actions or 'ps' in options.actions:
1107 latex_commands = ['latex', 'pdflatex']
1108 else:
1109 latex_commands = ['pdflatex']
1110
1111 log.start_progress('Processing LaTeX docs')
1112 oldpath = os.path.abspath(os.curdir)
1113 running = None
1114 step = 0.
1115 try:
1116 try:
1117 os.chdir(latex_target)
1118
1119
1120 for ext in 'aux log out idx ilg toc ind'.split():
1121 if os.path.exists('api.%s' % ext):
1122 os.remove('api.%s' % ext)
1123
1124 for latex_command in latex_commands:
1125 LaTeX = latex_command.replace('latex', 'LaTeX')
1126
1127 running = latex_command
1128 log.progress(step/steps, '%s (First pass)' % LaTeX)
1129 step += 1
1130 run_subprocess('%s api.tex' % latex_command)
1131
1132
1133 running = 'makeindex'
1134 log.progress(step/steps, '%s (Build index)' % LaTeX)
1135 step += 1
1136 run_subprocess('makeindex api.idx')
1137
1138
1139 running = latex_command
1140 log.progress(step/steps, '%s (Second pass)' % LaTeX)
1141 step += 1
1142 out, err = run_subprocess('%s api.tex' % latex_command)
1143
1144
1145
1146 running = latex_command
1147 if _RERUN_LATEX_RE.search(out):
1148 log.progress(step/steps, '%s (Third pass)' % LaTeX)
1149 out, err = run_subprocess('%s api.tex' % latex_command)
1150
1151
1152 running = latex_command
1153 if _RERUN_LATEX_RE.search(out):
1154 log.progress(step/steps, '%s (Fourth pass)' % LaTeX)
1155 out, err = run_subprocess('%s api.tex' % latex_command)
1156 step += 1
1157
1158
1159 if options.verbosity > 2 or epydoc.DEBUG:
1160 show_latex_warnings(out)
1161
1162
1163 if ('ps' in options.actions or
1164 ('pdf' in options.actions and options.pdfdriver=='latex')):
1165 running = 'dvips'
1166 log.progress(step/steps, 'dvips')
1167 step += 1
1168 run_subprocess('dvips api.dvi -o api.ps -G0 -Ppdf')
1169
1170
1171 if 'pdf' in options.actions and options.pdfdriver=='latex':
1172 running = 'ps2pdf'
1173 log.progress(step/steps, 'ps2pdf')
1174 step += 1
1175 run_subprocess(
1176 'ps2pdf -sPAPERSIZE#letter -dMaxSubsetPct#100 '
1177 '-dSubsetFonts#true -dCompatibilityLevel#1.2 '
1178 '-dEmbedAllFonts#true api.ps api.pdf')
1179
1180
1181 if 'dvi' in options.actions:
1182 dst = os.path.join(oldpath, options.target['dvi'])
1183 shutil.copy2('api.dvi', dst)
1184 if 'ps' in options.actions:
1185 dst = os.path.join(oldpath, options.target['ps'])
1186 shutil.copy2('api.ps', dst)
1187 if 'pdf' in options.actions:
1188 dst = os.path.join(oldpath, options.target['pdf'])
1189 shutil.copy2('api.pdf', dst)
1190
1191 except RunSubprocessError, e:
1192 if running in ('latex', 'pdflatex'):
1193 filename, pageno = find_latex_error(e.out)
1194 e.out = re.sub(r'(?sm)\A.*?!( LaTeX Error:)?', r'', e.out)
1195 e.out = re.sub(r'(?sm)\s*Type X to quit.*', '', e.out)
1196 e.out = re.sub(r'(?sm)^! Emergency stop.*', '', e.out)
1197 e.out = '(%s, page %s)\n%s' % (filename, pageno, e.out)
1198 log.error("%s failed: %s" % (running, (e.out+e.err).lstrip()))
1199 except OSError, e:
1200 log.error("%s failed: %s" % (running, e))
1201 finally:
1202 os.chdir(oldpath)
1203
1204 if 'latex' not in options.actions:
1205
1206 log.info('Cleaning up %s' % latex_target)
1207 try:
1208 for filename in os.listdir(latex_target):
1209 os.remove(os.path.join(latex_target, filename))
1210 os.rmdir(latex_target)
1211 except Exception, e:
1212 log.error("Error cleaning up tempdir %s: %s" %
1213 (latex_target, e))
1214
1215 log.end_progress()
1216
1217 -def write_text(docindex, options):
1218 log.start_progress('Writing output')
1219 from epydoc.docwriter.plaintext import PlaintextWriter
1220 plaintext_writer = PlaintextWriter()
1221 s = '\n'
1222 for apidoc in docindex.root:
1223 s += plaintext_writer.write(apidoc, **options.__dict__)+'\n'
1224 log.end_progress()
1225 if isinstance(s, unicode):
1226 s = s.encode('ascii', 'backslashreplace')
1227 sys.stdout.write(s)
1228
1232
1234 """
1235 Perform all actions indicated by the options in sys.argv.
1236
1237 @return: the L{epydoc.apidoc.DocIndex} object created while
1238 running epydoc (or None).
1239 """
1240
1241 options = parse_arguments()
1242
1243 try:
1244 try:
1245 if options.profile:
1246 _profile()
1247 else:
1248 return main(options)
1249 finally:
1250 log.close()
1251 except SystemExit:
1252 raise
1253 except KeyboardInterrupt:
1254 print '\n\n'
1255 print >>sys.stderr, 'Keyboard interrupt.'
1256 except:
1257 if options.debug: raise
1258 print '\n\n'
1259 exc_info = sys.exc_info()
1260 if isinstance(exc_info[0], basestring): e = exc_info[0]
1261 else: e = exc_info[1]
1262 print >>sys.stderr, ('\nUNEXPECTED ERROR:\n'
1263 '%s\n' % (str(e) or e.__class__.__name__))
1264 print >>sys.stderr, 'Use --debug to see trace information.'
1265 sys.exit(3)
1266
1268
1269 if PROFILER == 'hotshot':
1270 try: import hotshot, hotshot.stats
1271 except ImportError:
1272 print >>sys.stderr, "Could not import profile module!"
1273 return
1274 try:
1275 prof = hotshot.Profile('hotshot.out')
1276 prof = prof.runctx('main(parse_arguments())', globals(), {})
1277 except SystemExit:
1278 pass
1279 prof.close()
1280
1281 print 'Consolidating hotshot profiling info...'
1282 hotshot.stats.load('hotshot.out').dump_stats('profile.out')
1283
1284
1285 elif PROFILER == 'profile':
1286
1287
1288 try: from cProfile import Profile
1289 except ImportError:
1290 try: from profile import Profile
1291 except ImportError:
1292 print >>sys.stderr, "Could not import profile module!"
1293 return
1294
1295
1296
1297
1298
1299 if (hasattr(Profile, 'dispatch') and
1300 Profile.dispatch['c_exception'] is
1301 Profile.trace_dispatch_exception.im_func):
1302 trace_dispatch_return = Profile.trace_dispatch_return.im_func
1303 Profile.dispatch['c_exception'] = trace_dispatch_return
1304 try:
1305 prof = Profile()
1306 prof = prof.runctx('main(parse_arguments())', globals(), {})
1307 except SystemExit:
1308 pass
1309 prof.dump_stats('profile.out')
1310
1311 else:
1312 print >>sys.stderr, 'Unknown profiler %s' % PROFILER
1313 return
1314
1315
1316
1317
1318
1319
1321 - def __init__(self, verbosity, progress_mode=None):
1322 self._verbosity = verbosity
1323 self._progress = None
1324 self._message_blocks = []
1325
1326 self._progress_start_time = None
1327
1328 self._task_times = []
1329 self._progress_header = None
1330
1331 self.reported_message_levels = set()
1332 """This set contains all the message levels (WARNING, ERROR,
1333 etc) that have been reported. It is used by the options
1334 --fail-on-warning etc to determine the return value."""
1335
1336 self.suppressed_docstring_warning = 0
1337 """This variable will be incremented once every time a
1338 docstring warning is reported tothe logger, but the verbosity
1339 level is too low for it to be displayed."""
1340
1341 self.term = TerminalController()
1342
1343
1344 if verbosity >= 2: self._progress_mode = 'list'
1345 elif verbosity >= 0:
1346 if progress_mode is not None:
1347 self._progress_mode = progress_mode
1348 elif self.term.COLS < 15:
1349 self._progress_mode = 'simple-bar'
1350 elif self.term.BOL and self.term.CLEAR_EOL and self.term.UP:
1351 self._progress_mode = 'multiline-bar'
1352 elif self.term.BOL and self.term.CLEAR_LINE:
1353 self._progress_mode = 'bar'
1354 else:
1355 self._progress_mode = 'simple-bar'
1356 else: self._progress_mode = 'hide'
1357
1359 self._message_blocks.append( (header, []) )
1360
1362 header, messages = self._message_blocks.pop()
1363 if messages:
1364 width = self.term.COLS - 5 - 2*len(self._message_blocks)
1365 prefix = self.term.CYAN+self.term.BOLD+'| '+self.term.NORMAL
1366 divider = (self.term.CYAN+self.term.BOLD+'+'+'-'*(width-1)+
1367 self.term.NORMAL)
1368
1369 header = wordwrap(header, right=width-2, splitchars='\\/').rstrip()
1370 header = '\n'.join([prefix+self.term.CYAN+l+self.term.NORMAL
1371 for l in header.split('\n')])
1372
1373 body = ''
1374 for message in messages:
1375 if message.endswith('\n'): body += message
1376 else: body += message+'\n'
1377
1378 body = '\n'.join([prefix+' '+l for l in body.split('\n')])
1379
1380 message = divider + '\n' + header + '\n' + body + '\n'
1381 self._report(message)
1382
1398
1399 - def log(self, level, message):
1400 self.reported_message_levels.add(level)
1401 if self._verbosity >= -2 and level >= log.ERROR:
1402 message = self._format(' Error: ', message, self.term.RED)
1403 elif self._verbosity >= -1 and level >= log.WARNING:
1404 message = self._format('Warning: ', message, self.term.YELLOW)
1405 elif self._verbosity >= 1 and level >= log.DOCSTRING_WARNING:
1406 message = self._format('Warning: ', message, self.term.YELLOW)
1407 elif self._verbosity >= 3 and level >= log.INFO:
1408 message = self._format(' Info: ', message, self.term.NORMAL)
1409 elif epydoc.DEBUG and level == log.DEBUG:
1410 message = self._format(' Debug: ', message, self.term.CYAN)
1411 else:
1412 if level >= log.DOCSTRING_WARNING:
1413 self.suppressed_docstring_warning += 1
1414 return
1415
1416 self._report(message)
1417
1419 if not message.endswith('\n'): message += '\n'
1420
1421 if self._message_blocks:
1422 self._message_blocks[-1][-1].append(message)
1423 else:
1424
1425
1426 if self._progress_mode == 'simple-bar':
1427 if self._progress is not None:
1428 print
1429 self._progress = None
1430 if self._progress_mode == 'bar':
1431 sys.stdout.write(self.term.CLEAR_LINE)
1432 if self._progress_mode == 'multiline-bar':
1433 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 +
1434 self.term.CLEAR_EOL + self.term.UP*2)
1435
1436
1437 sys.stdout.write(message)
1438 sys.stdout.flush()
1439
1440 - def progress(self, percent, message=''):
1441 percent = min(1.0, percent)
1442 message = '%s' % message
1443
1444 if self._progress_mode == 'list':
1445 if message:
1446 print '[%3d%%] %s' % (100*percent, message)
1447 sys.stdout.flush()
1448
1449 elif self._progress_mode == 'bar':
1450 dots = int((self.term.COLS/2-8)*percent)
1451 background = '-'*(self.term.COLS/2-8)
1452 if len(message) > self.term.COLS/2:
1453 message = message[:self.term.COLS/2-3]+'...'
1454 sys.stdout.write(self.term.CLEAR_LINE + '%3d%% '%(100*percent) +
1455 self.term.GREEN + '[' + self.term.BOLD +
1456 '='*dots + background[dots:] + self.term.NORMAL +
1457 self.term.GREEN + '] ' + self.term.NORMAL +
1458 message + self.term.BOL)
1459 sys.stdout.flush()
1460 self._progress = percent
1461 elif self._progress_mode == 'multiline-bar':
1462 dots = int((self.term.COLS-10)*percent)
1463 background = '-'*(self.term.COLS-10)
1464
1465 if len(message) > self.term.COLS-10:
1466 message = message[:self.term.COLS-10-3]+'...'
1467 else:
1468 message = message.center(self.term.COLS-10)
1469
1470 time_elapsed = time.time()-self._progress_start_time
1471 if percent > 0:
1472 time_remain = (time_elapsed / percent) * (1-percent)
1473 else:
1474 time_remain = 0
1475
1476 sys.stdout.write(
1477
1478 self.term.CLEAR_EOL + ' ' +
1479 '%-8s' % self._timestr(time_elapsed) +
1480 self.term.BOLD + 'Progress:'.center(self.term.COLS-26) +
1481 self.term.NORMAL + '%8s' % self._timestr(time_remain) + '\n' +
1482
1483 self.term.CLEAR_EOL + ('%3d%% ' % (100*percent)) +
1484 self.term.GREEN + '[' + self.term.BOLD + '='*dots +
1485 background[dots:] + self.term.NORMAL + self.term.GREEN +
1486 ']' + self.term.NORMAL + '\n' +
1487
1488 self.term.CLEAR_EOL + ' ' + message + self.term.BOL +
1489 self.term.UP + self.term.UP)
1490
1491 sys.stdout.flush()
1492 self._progress = percent
1493 elif self._progress_mode == 'simple-bar':
1494 if self._progress is None:
1495 sys.stdout.write(' [')
1496 self._progress = 0.0
1497 dots = int((self.term.COLS-2)*percent)
1498 progress_dots = int((self.term.COLS-2)*self._progress)
1499 if dots > progress_dots:
1500 sys.stdout.write('.'*(dots-progress_dots))
1501 sys.stdout.flush()
1502 self._progress = percent
1503
1505 dt = int(dt)
1506 if dt >= 3600:
1507 return '%d:%02d:%02d' % (dt/3600, dt%3600/60, dt%60)
1508 else:
1509 return '%02d:%02d' % (dt/60, dt%60)
1510
1512 if self._progress is not None:
1513 raise ValueError('previous progress bar not ended')
1514 self._progress = None
1515 self._progress_start_time = time.time()
1516 self._progress_header = header
1517 if self._progress_mode != 'hide' and header:
1518 print self.term.BOLD + header + self.term.NORMAL
1519
1521 self.progress(1.)
1522 if self._progress_mode == 'bar':
1523 sys.stdout.write(self.term.CLEAR_LINE)
1524 if self._progress_mode == 'multiline-bar':
1525 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 +
1526 self.term.CLEAR_EOL + self.term.UP*2)
1527 if self._progress_mode == 'simple-bar':
1528 print ']'
1529 self._progress = None
1530 self._task_times.append( (time.time()-self._progress_start_time,
1531 self._progress_header) )
1532
1534 print
1535 print 'Timing summary:'
1536 total = sum([time for (time, task) in self._task_times])
1537 max_t = max([time for (time, task) in self._task_times])
1538 for (time, task) in self._task_times:
1539 task = task[:34]
1540 print ' %s%s%7.1fs' % (task, '.'*(37-len(task)), time),
1541 if self.term.COLS > 58:
1542 print '|'+'=' * int((self.term.COLS-56) * time / max_t)
1543 else:
1544 print
1545 print
1546
1548 - def __init__(self, verbosity, stages, progress_mode=None):
1549 self.stage = 0
1550 self.stages = stages
1551 self.task = None
1552 ConsoleLogger.__init__(self, verbosity, progress_mode)
1553
1554 - def progress(self, percent, message=''):
1555
1556 i = self.stage-1
1557 p = ((sum(self.stages[:i]) + percent*self.stages[i]) /
1558 float(sum(self.stages)))
1559
1560 if message is UNKNOWN: message = None
1561 if message: message = '%s: %s' % (self.task, message)
1562 ConsoleLogger.progress(self, p, message)
1563
1565 self.task = header
1566 if self.stage == 0:
1567 ConsoleLogger.start_progress(self)
1568 self.stage += 1
1569 if self.stage > len(self.stages):
1570 self.stage = len(self.stages)
1571
1575
1578
1580 """
1581 A logger used to generate a log of all warnings and messages to an
1582 HTML file.
1583 """
1584
1585 FILENAME = "epydoc-log.html"
1586 HEADER = textwrap.dedent('''\
1587 <?xml version="1.0" encoding="ascii"?>
1588 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1589 "DTD/xhtml1-transitional.dtd">
1590 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
1591 <head>
1592 <title>Epydoc Log</title>
1593 <link rel="stylesheet" href="epydoc.css" type="text/css" />
1594 </head>
1595
1596 <body bgcolor="white" text="black" link="blue" vlink="#204080"
1597 alink="#204080">
1598 <h1 class="epydoc">Epydoc Log</h1>
1599 <p class="log">Epydoc started at %s</p>''')
1600 START_BLOCK = '<div class="log-block"><h2 class="log-hdr">%s</h2>'
1601 MESSAGE = ('<div class="log-%s"><b>%s</b>: \n'
1602 '%s</div>\n')
1603 END_BLOCK = '</div>'
1604 FOOTER = "</body>\n</html>\n"
1605
1606 - def __init__(self, directory, options):
1607 self.start_time = time.time()
1608 self.out = open(os.path.join(directory, self.FILENAME), 'w')
1609 self.out.write(self.HEADER % time.ctime(self.start_time))
1610 self.is_empty = True
1611 self.options = options
1612
1614 self.out.write(self.START_BLOCK % 'Epydoc Options')
1615 msg = '<table border="0" cellpadding="0" cellspacing="0">\n'
1616 opts = [(key, getattr(options, key)) for key in dir(options)
1617 if key not in dir(optparse.Values)]
1618 defaults = option_defaults()
1619 opts = [(val==defaults.get(key), key, val)
1620 for (key, val) in opts]
1621 for is_default, key, val in sorted(opts):
1622 css = is_default and 'opt-default' or 'opt-changed'
1623 msg += ('<tr valign="top" class="%s"><td valign="top">%s</td>'
1624 '<td valign="top"><tt> = </tt></td>'
1625 '<td valign="top"><tt>%s</tt></td></tr>' %
1626 (css, key, plaintext_to_html(repr(val))))
1627 msg += '</table>\n'
1628 self.out.write('<div class="log-info">\n%s</div>\n' % msg)
1629 self.out.write(self.END_BLOCK)
1630
1633
1636
1637 - def log(self, level, message):
1645
1647 self.is_empty = False
1648 message = plaintext_to_html(message)
1649 if '\n' in message:
1650 message = '<pre class="log">%s</pre>' % message
1651 hdr = ' '.join([w.capitalize() for w in level.split()])
1652 return self.MESSAGE % (level.split()[-1], hdr, message)
1653
1655 if self.out is None: return
1656 if self.is_empty:
1657 self.out.write('<div class="log-info">'
1658 'No warnings or errors!</div>')
1659 self.write_options(self.options)
1660 self.out.write('<p class="log">Epydoc finished at %s</p>\n'
1661 '<p class="log">(Elapsed time: %s)</p>' %
1662 (time.ctime(), self._elapsed_time()))
1663 self.out.write(self.FOOTER)
1664 self.out.close()
1665 self.out = None
1666
1668 secs = int(time.time()-self.start_time)
1669 if secs < 60:
1670 return '%d seconds' % secs
1671 if secs < 3600:
1672 return '%d minutes, %d seconds' % (secs/60, secs%60)
1673 else:
1674 return '%d hours, %d minutes' % (secs/3600, secs%3600)
1675
1676
1677
1678
1679
1680
1681 if __name__ == '__main__':
1682 cli()
1683