Package epydoc :: Module cli
[hide private]
[frames] | no frames]

Source Code for Module epydoc.cli

   1  # epydoc -- Command line interface 
   2  # 
   3  # Copyright (C) 2005 Edward Loper 
   4  # Author: Edward Loper <edloper@loper.org> 
   5  # URL: <http://epydoc.sf.net> 
   6  # 
   7  # $Id: cli.py 1807 2008-03-04 02:32:58Z edloper $ 
   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  # This module is only available if Docutils are in the system 
  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' #: Which profiler to use: 'hotshot' or 'profile' 
  93  TARGET_ACTIONS = ('html', 'latex', 'dvi', 'ps', 'pdf') 
  94  DEFAULT_ACTIONS = ('html',) 
  95  PDFDRIVERS = ('pdflatex', 'latex', 'auto') 
  96   
  97  ###################################################################### 
  98  #{ Help Topics 
  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      #'checks': textwrap.dedent('''\ 
 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  #{ Argument & Config File Parsing 
 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   
144 -def option_defaults():
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 # append_const is not defined in py2.3 or py2.4, so use a callback 160 # instead, with the following function:
161 -def add_action(option, opt, value, optparser):
162 action = opt.replace('-', '') 163 optparser.values.actions.append(action)
164
165 -def add_target(option, opt, value, optparser):
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
173 -def parse_arguments():
174 # Construct the option parser. 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 # Provide our own --help and --version options. 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 # The group of external API options. 425 # Skip if the module couldn't be imported (usually missing docutils) 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 # this option is for developers, not users. 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 # Set the option parser's defaults. 512 optparser.set_defaults(**option_defaults()) 513 514 # Parse the arguments. 515 options, names = optparser.parse_args() 516 517 # Print help message, if requested. We also provide support for 518 # --help [topic] 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 # Print version message, if requested. 529 if 'version' in options.actions: 530 print version 531 sys.exit(0) 532 533 # Process any config files. 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 # Check if the input file is a pickle file. 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 # Check to make sure all options are valid. 554 if len(names) == 0: 555 optparser.error("No names specified.") 556 557 # perform shell expansion. 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 # Check the list of requested graph types to make sure they're 570 # acceptable. 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 # If it's 'all', then add everything (but don't add callgraph if 577 # we don't have any profiling info to base them on). 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 # Check the value of the pdfdriver option; and check for conflicts 589 # between pdfdriver & actions 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 # Set graph defaults 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 # Calculate verbosity. 617 verbosity = getattr(options, 'verbosity', 0) 618 options.verbosity = verbosity + options.verbose - options.quiet 619 620 # Return parsed args. 621 options.names = names 622 return options
623
624 -def parse_configfiles(configfiles, options, names):
625 configparser = ConfigParser.ConfigParser() 626 # ConfigParser.read() silently ignores errors, so open the files 627 # manually (since we want to notify the user of any errors). 628 for configfile in configfiles: 629 fp = open(configfile, 'r') # may raise IOError. 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 # Generation options 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 # Output options 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 # External API 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 # Graph options 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 # Return value options 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
774 -def _str_to_bool(val, optname):
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
782 -def _str_to_int(val, optname):
783 try: 784 return int(val) 785 except ValueError: 786 raise ValueError('"%s" option expected an int' % optname)
787
788 -def _str_to_list(val):
789 return val.replace(',', ' ').split()
790 791 ###################################################################### 792 #{ Interface 793 ###################################################################### 794
795 -def main(options):
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 # Set the debug flag, if '--debug' was specified. 803 if options.debug: 804 epydoc.DEBUG = True 805 806 if not options.actions: 807 options.actions = DEFAULT_ACTIONS 808 809 # Set up the logger 810 loggers = [] 811 if options.simple_term: 812 TerminalController.FORCE_SIMPLE_TERM = True 813 if options.actions == ['text']: 814 pass # no logger for text output. 815 elif options.verbosity > 1: 816 logger = ConsoleLogger(options.verbosity) 817 log.register_logger(logger) 818 loggers.append(logger) 819 else: 820 # Each number is a rough approximation of how long we spend on 821 # that task, used to divide up the unified progress bar. 822 stages = [40, # Building documentation 823 7, # Merging parsed & introspected information 824 1, # Linking imported variables 825 3, # Indexing documentation 826 1, # Checking for overridden methods 827 30, # Parsing Docstrings 828 1, # Inheriting documentation 829 2] # Sorting & Grouping 830 if options.load_pickle: 831 stages = [30] # Loading pickled documentation 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] # implied by pdf 838 elif 'dvi' in options.actions: stages += [30] # implied by ps 839 if 'text' in options.actions: stages += [30] 840 841 if options.parse and not options.introspect: 842 del stages[1] # no merging 843 if options.introspect and not options.parse: 844 del stages[1:3] # no merging or linking 845 logger = UnifiedProgressConsoleLogger(options.verbosity, stages) 846 log.register_logger(logger) 847 loggers.append(logger) 848 849 # Calculate the target directories/files. 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 # Add extensions to target filenames, where appropriate. 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 # check the output targets. 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 # Set the default docformat 885 from epydoc import docstringparser 886 docstringparser.DEFAULT_DOCFORMAT = options.docformat 887 888 # Configure the external API linking 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 # Set the dot path 897 if options.dotpath: 898 from epydoc.docwriter import dotgraph 899 dotgraph.DOT_COMMAND = options.dotpath 900 901 # Set the default graph font & size 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 # If the input name is a pickle file, then read the docindex that 914 # it contains. Otherwise, build the docs for the input names. 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 # Build docs for the named values. 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 # docbuilder already logged an error. 946 947 # Load profile information, if it was given. 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 # Perform the specified action. NOTE: It is important that these 966 # are performed in the same order that the 'stages' list was 967 # constructed, at the top of this function. 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 # If we suppressed docstring warnings, then let the user know. 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 # Basic timing breakdown: 993 if options.verbosity >= 2: 994 for logger in loggers: 995 if isinstance(logger, ConsoleLogger): 996 logger.print_times() 997 break 998 999 # If we encountered any message types that we were requested to 1000 # fail on, then exit with status 2. 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 # Deregister our logger(s). 1007 for logger in loggers: log.remove_logger(logger) 1008 1009 # Return the docindex, in case someone wants to use it programatically. 1010 return docindex
1011
1012 -def write_html(docindex, options):
1013 from epydoc.docwriter.html import HTMLWriter 1014 html_writer = HTMLWriter(docindex, **options.__dict__) 1015 if options.verbose > 0: 1016 log.start_progress('Writing HTML docs to %r' % options.target['html']) 1017 else: 1018 log.start_progress('Writing HTML docs') 1019 html_writer.write(options.target['html']) 1020 log.end_progress()
1021
1022 -def write_pickle(docindex, options):
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
1036 -def pickle_persistent_id(obj):
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
1042 -def pickle_persistent_load(identifier):
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
1051 -def write_latex(docindex, options):
1052 # If latex is an intermediate target, then use a temporary 1053 # directory for its files. 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 # Choose a pdfdriver if we're generating pdf output. 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 # Decide how many steps we need to go through. 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 # If we're just generating the latex, and not any derived 1099 # output format, then we're done. 1100 assert 'latex' in options.actions 1101 return 1102 1103 # Decide whether we need to run latex, pdflatex, or both. 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 # keep track of what we're doing. 1114 step = 0. 1115 try: 1116 try: 1117 os.chdir(latex_target) 1118 1119 # Clear any old files out of the way. 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 # The first pass generates index files. 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 # Build the index. 1133 running = 'makeindex' 1134 log.progress(step/steps, '%s (Build index)' % LaTeX) 1135 step += 1 1136 run_subprocess('makeindex api.idx') 1137 1138 # The second pass generates our output. 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 # The third pass is only necessary if the second pass 1145 # changed what page some things are on. 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 # A fourth path should (almost?) never be necessary. 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 # Show the output, if verbosity is high: 1159 if options.verbosity > 2 or epydoc.DEBUG: 1160 show_latex_warnings(out) 1161 1162 # If requested, convert to postscript. 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 # If requested, convert to pdf. 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 # Copy files to their respective targets. 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 # The latex output went to a tempdir; clean it up. 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
1229 -def check_docs(docindex, options):
1230 from epydoc.checker import DocChecker 1231 DocChecker(docindex).check()
1232
1233 -def cli():
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 # Parse command-line arguments. 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
1267 -def _profile():
1268 # Hotshot profiler. 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 # Convert profile.hotshot -> profile.out 1281 print 'Consolidating hotshot profiling info...' 1282 hotshot.stats.load('hotshot.out').dump_stats('profile.out') 1283 1284 # Standard 'profile' profiler. 1285 elif PROFILER == 'profile': 1286 # cProfile module was added in Python 2.5 -- use it if its' 1287 # available, since it's faster. 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 # There was a bug in Python 2.4's profiler. Check if it's 1296 # present, and if so, fix it. (Bug was fixed in 2.4maint: 1297 # <http://mail.python.org/pipermail/python-checkins/ 1298 # 2005-September/047099.html>) 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 #{ Logging 1317 ###################################################################### 1318 # [xx] this should maybe move to util.py or log.py 1319
1320 -class ConsoleLogger(log.Logger):
1321 - def __init__(self, verbosity, progress_mode=None):
1322 self._verbosity = verbosity 1323 self._progress = None 1324 self._message_blocks = [] 1325 # For ETA display: 1326 self._progress_start_time = None 1327 # For per-task times: 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 # Set the progress bar mode. 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
1358 - def start_block(self, header):
1359 self._message_blocks.append( (header, []) )
1360
1361 - def end_block(self):
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 # Mark up the header: 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 # Construct the body: 1373 body = '' 1374 for message in messages: 1375 if message.endswith('\n'): body += message 1376 else: body += message+'\n' 1377 # Indent the body: 1378 body = '\n'.join([prefix+' '+l for l in body.split('\n')]) 1379 # Put it all together: 1380 message = divider + '\n' + header + '\n' + body + '\n' 1381 self._report(message)
1382
1383 - def _format(self, prefix, message, color):
1384 """ 1385 Rewrap the message; but preserve newlines, and don't touch any 1386 lines that begin with spaces. 1387 """ 1388 lines = message.split('\n') 1389 startindex = indent = len(prefix) 1390 for i in range(len(lines)): 1391 if lines[i].startswith(' '): 1392 lines[i] = ' '*(indent-startindex) + lines[i] + '\n' 1393 else: 1394 width = self.term.COLS - 5 - 4*len(self._message_blocks) 1395 lines[i] = wordwrap(lines[i], indent, width, startindex, '\\/') 1396 startindex = 0 1397 return color+prefix+self.term.NORMAL+''.join(lines)
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
1418 - def _report(self, message):
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 # If we're in the middle of displaying a progress bar, 1425 # then make room for the message. 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 # Display the message message. 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 # Line 1: 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 # Line 2: 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 # Line 3: 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
1504 - def _timestr(self, dt):
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
1511 - def start_progress(self, header=None):
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
1520 - def end_progress(self):
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
1533 - def print_times(self):
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
1547 -class UnifiedProgressConsoleLogger(ConsoleLogger):
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 #p = float(self.stage-1+percent)/self.stages 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
1564 - def start_progress(self, header=None):
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) # should never happen!
1571
1572 - def end_progress(self):
1573 if self.stage == len(self.stages): 1574 ConsoleLogger.end_progress(self)
1575
1576 - def print_times(self):
1577 pass
1578
1579 -class HTMLLogger(log.Logger):
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
1613 - def write_options(self, options):
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>&nbsp;=&nbsp;</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
1631 - def start_block(self, header):
1632 self.out.write(self.START_BLOCK % header)
1633
1634 - def end_block(self):
1635 self.out.write(self.END_BLOCK)
1636
1637 - def log(self, level, message):
1638 if message.endswith("(-v) to display markup errors."): return 1639 if level >= log.ERROR: 1640 self.out.write(self._message('error', message)) 1641 elif level >= log.WARNING: 1642 self.out.write(self._message('warning', message)) 1643 elif level >= log.DOCSTRING_WARNING: 1644 self.out.write(self._message('docstring warning', message))
1645
1646 - def _message(self, level, message):
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
1654 - def close(self):
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
1667 - def _elapsed_time(self):
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 ## main 1679 ###################################################################### 1680 1681 if __name__ == '__main__': 1682 cli() 1683