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