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