| # epydoc -- Command line interface | 
 | # | 
 | # Copyright (C) 2005 Edward Loper | 
 | # Author: Edward Loper <edloper@loper.org> | 
 | # URL: <http://epydoc.sf.net> | 
 | # | 
 | # $Id: cli.py 1678 2008-01-29 17:21:29Z edloper $ | 
 |  | 
 | """ | 
 | Command-line interface for epydoc.  Abbreviated Usage:: | 
 |  | 
 |  epydoc [options] NAMES... | 
 |   | 
 |      NAMES...                  The Python modules to document. | 
 |      --html                    Generate HTML output (default). | 
 |      --latex                   Generate LaTeX output. | 
 |      --pdf                     Generate pdf output, via LaTeX. | 
 |      -o DIR, --output DIR      The output directory. | 
 |      --inheritance STYLE       The format for showing inherited objects. | 
 |      -V, --version             Print the version of epydoc. | 
 |      -h, --help                Display a usage message. | 
 |  | 
 | Run \"epydoc --help\" for a complete option list.  See the epydoc(1) | 
 | man page for more information. | 
 |  | 
 | Config Files | 
 | ============ | 
 | Configuration files can be specified with the C{--config} option. | 
 | These files are read using U{ConfigParser | 
 | <http://docs.python.org/lib/module-ConfigParser.html>}.  Configuration | 
 | files may set options or add names of modules to document.  Option | 
 | names are (usually) identical to the long names of command line | 
 | options.  To specify names to document, use any of the following | 
 | option names:: | 
 |  | 
 |   module modules value values object objects | 
 |  | 
 | A simple example of a config file is:: | 
 |  | 
 |   [epydoc] | 
 |   modules: sys, os, os.path, re, %(MYSANDBOXPATH)/utilities.py | 
 |   name: Example | 
 |   graph: classtree | 
 |   introspect: no | 
 |  | 
 | All ConfigParser interpolations are done using local values and the | 
 | environment variables. | 
 |  | 
 |  | 
 | Verbosity Levels | 
 | ================ | 
 | The C{-v} and C{-q} options increase and decrease verbosity, | 
 | respectively.  The default verbosity level is zero.  The verbosity | 
 | levels are currently defined as follows:: | 
 |  | 
 |                 Progress    Markup warnings   Warnings   Errors | 
 |  -3               none            no             no        no | 
 |  -2               none            no             no        yes | 
 |  -1               none            no             yes       yes | 
 |   0 (default)     bar             no             yes       yes | 
 |   1               bar             yes            yes       yes | 
 |   2               list            yes            yes       yes | 
 | """ | 
 | __docformat__ = 'epytext en' | 
 |  | 
 | import sys, os, time, re, pickle, textwrap | 
 | from glob import glob | 
 | from optparse import OptionParser, OptionGroup, SUPPRESS_HELP | 
 | import optparse | 
 | import epydoc | 
 | from epydoc import log | 
 | from epydoc.util import wordwrap, run_subprocess, RunSubprocessError | 
 | from epydoc.util import plaintext_to_html | 
 | from epydoc.apidoc import UNKNOWN | 
 | from epydoc.compat import * | 
 | import ConfigParser | 
 | from epydoc.docwriter.html_css import STYLESHEETS as CSS_STYLESHEETS | 
 |  | 
 | # This module is only available if Docutils are in the system | 
 | try: | 
 |     from epydoc.docwriter import xlink | 
 | except: | 
 |     xlink = None | 
 |  | 
 | INHERITANCE_STYLES = ('grouped', 'listed', 'included') | 
 | GRAPH_TYPES = ('classtree', 'callgraph', 'umlclasstree') | 
 | ACTIONS = ('html', 'text', 'latex', 'dvi', 'ps', 'pdf', 'check') | 
 | DEFAULT_DOCFORMAT = 'epytext' | 
 | PROFILER = 'profile' #: Which profiler to use: 'hotshot' or 'profile' | 
 |  | 
 | ###################################################################### | 
 | #{ Help Topics | 
 | ###################################################################### | 
 |  | 
 | DOCFORMATS = ('epytext', 'plaintext', 'restructuredtext', 'javadoc') | 
 | HELP_TOPICS = { | 
 |     'docformat': textwrap.dedent('''\ | 
 |         __docformat__ is a module variable that specifies the markup | 
 |         language for the docstrings in a module.  Its value is a  | 
 |         string, consisting the name of a markup language, optionally  | 
 |         followed by a language code (such as "en" for English).  Epydoc | 
 |         currently recognizes the following markup language names: | 
 |         ''' + ', '.join(DOCFORMATS)), | 
 |     'inheritance': textwrap.dedent('''\ | 
 |         The following inheritance formats are currently supported: | 
 |             - grouped: inherited objects are gathered into groups, | 
 |               based on what class they were inherited from. | 
 |             - listed: inherited objects are listed in a short list | 
 |               at the end of their section. | 
 |             - included: inherited objects are mixed in with  | 
 |               non-inherited objects.'''), | 
 |     'css': textwrap.dedent( | 
 |         'The following built-in CSS stylesheets are available:\n' + | 
 |         '\n'.join(['  %10s: %s' % (key, descr) | 
 |                    for (key, (sheet, descr)) | 
 |                    in CSS_STYLESHEETS.items()])), | 
 |     #'checks': textwrap.dedent('''\ | 
 |     # | 
 |     #    '''), | 
 |     } | 
 |          | 
 |  | 
 | HELP_TOPICS['topics'] = wordwrap( | 
 |     'Epydoc can provide additional help for the following topics: ' + | 
 |     ', '.join(['%r' % topic for topic in HELP_TOPICS.keys()])) | 
 |      | 
 | ###################################################################### | 
 | #{ Argument & Config File Parsing | 
 | ###################################################################### | 
 |  | 
 | OPTION_DEFAULTS = dict( | 
 |     action="html", show_frames=True, docformat=DEFAULT_DOCFORMAT,  | 
 |     show_private=True, show_imports=False, inheritance="listed", | 
 |     verbose=0, quiet=0, load_pickle=False, parse=True, introspect=True, | 
 |     debug=epydoc.DEBUG, profile=False, graphs=[], | 
 |     list_classes_separately=False, graph_font=None, graph_font_size=None, | 
 |     include_source_code=True, pstat_files=[], simple_term=False, fail_on=None, | 
 |     exclude=[], exclude_parse=[], exclude_introspect=[], | 
 |     external_api=[], external_api_file=[], external_api_root=[], | 
 |     redundant_details=False, src_code_tab_width=8) | 
 |  | 
 | def parse_arguments(): | 
 |     # Construct the option parser. | 
 |     usage = '%prog [ACTION] [options] NAMES...' | 
 |     version = "Epydoc, version %s" % epydoc.__version__ | 
 |     optparser = OptionParser(usage=usage, add_help_option=False) | 
 |  | 
 |     optparser.add_option('--config', | 
 |         action='append', dest="configfiles", metavar='FILE', | 
 |         help=("A configuration file, specifying additional OPTIONS " | 
 |               "and/or NAMES.  This option may be repeated.")) | 
 |  | 
 |     optparser.add_option("--output", "-o", | 
 |         dest="target", metavar="PATH", | 
 |         help="The output directory.  If PATH does not exist, then " | 
 |         "it will be created.") | 
 |  | 
 |     optparser.add_option("--quiet", "-q", | 
 |         action="count", dest="quiet", | 
 |         help="Decrease the verbosity.") | 
 |  | 
 |     optparser.add_option("--verbose", "-v", | 
 |         action="count", dest="verbose", | 
 |         help="Increase the verbosity.") | 
 |  | 
 |     optparser.add_option("--debug", | 
 |         action="store_true", dest="debug", | 
 |         help="Show full tracebacks for internal errors.") | 
 |  | 
 |     optparser.add_option("--simple-term", | 
 |         action="store_true", dest="simple_term", | 
 |         help="Do not try to use color or cursor control when displaying " | 
 |         "the progress bar, warnings, or errors.") | 
 |  | 
 |  | 
 |     action_group = OptionGroup(optparser, 'Actions') | 
 |     optparser.add_option_group(action_group) | 
 |  | 
 |     action_group.add_option("--html", | 
 |         action="store_const", dest="action", const="html", | 
 |         help="Write HTML output.") | 
 |  | 
 |     action_group.add_option("--text", | 
 |         action="store_const", dest="action", const="text", | 
 |         help="Write plaintext output. (not implemented yet)") | 
 |  | 
 |     action_group.add_option("--latex", | 
 |         action="store_const", dest="action", const="latex", | 
 |         help="Write LaTeX output.") | 
 |  | 
 |     action_group.add_option("--dvi", | 
 |         action="store_const", dest="action", const="dvi", | 
 |         help="Write DVI output.") | 
 |  | 
 |     action_group.add_option("--ps", | 
 |         action="store_const", dest="action", const="ps", | 
 |         help="Write Postscript output.") | 
 |  | 
 |     action_group.add_option("--pdf", | 
 |         action="store_const", dest="action", const="pdf", | 
 |         help="Write PDF output.") | 
 |  | 
 |     action_group.add_option("--check", | 
 |         action="store_const", dest="action", const="check", | 
 |         help="Check completeness of docs.") | 
 |  | 
 |     action_group.add_option("--pickle", | 
 |         action="store_const", dest="action", const="pickle", | 
 |         help="Write the documentation to a pickle file.") | 
 |  | 
 |     # Provide our own --help and --version options. | 
 |     action_group.add_option("--version", | 
 |         action="store_const", dest="action", const="version", | 
 |         help="Show epydoc's version number and exit.") | 
 |  | 
 |     action_group.add_option("-h", "--help", | 
 |         action="store_const", dest="action", const="help", | 
 |         help="Show this message and exit.  For help on specific " | 
 |         "topics, use \"--help TOPIC\".  Use \"--help topics\" for a " | 
 |         "list of available help topics") | 
 |  | 
 |  | 
 |     generation_group = OptionGroup(optparser, 'Generation Options') | 
 |     optparser.add_option_group(generation_group) | 
 |  | 
 |     generation_group.add_option("--docformat", | 
 |         dest="docformat", metavar="NAME", | 
 |         help="The default markup language for docstrings.  Defaults " | 
 |         "to \"%s\"." % DEFAULT_DOCFORMAT) | 
 |  | 
 |     generation_group.add_option("--parse-only", | 
 |         action="store_false", dest="introspect", | 
 |         help="Get all information from parsing (don't introspect)") | 
 |  | 
 |     generation_group.add_option("--introspect-only", | 
 |         action="store_false", dest="parse", | 
 |         help="Get all information from introspecting (don't parse)") | 
 |  | 
 |     generation_group.add_option("--exclude", | 
 |         dest="exclude", metavar="PATTERN", action="append", | 
 |         help="Exclude modules whose dotted name matches " | 
 |              "the regular expression PATTERN") | 
 |  | 
 |     generation_group.add_option("--exclude-introspect", | 
 |         dest="exclude_introspect", metavar="PATTERN", action="append", | 
 |         help="Exclude introspection of modules whose dotted name matches " | 
 |              "the regular expression PATTERN") | 
 |  | 
 |     generation_group.add_option("--exclude-parse", | 
 |         dest="exclude_parse", metavar="PATTERN", action="append", | 
 |         help="Exclude parsing of modules whose dotted name matches " | 
 |              "the regular expression PATTERN") | 
 |  | 
 |     generation_group.add_option("--inheritance", | 
 |         dest="inheritance", metavar="STYLE", | 
 |         help="The format for showing inheritance objects.  STYLE " | 
 |         "should be one of: %s." % ', '.join(INHERITANCE_STYLES)) | 
 |  | 
 |     generation_group.add_option("--show-private", | 
 |         action="store_true", dest="show_private", | 
 |         help="Include private variables in the output. (default)") | 
 |  | 
 |     generation_group.add_option("--no-private", | 
 |         action="store_false", dest="show_private", | 
 |         help="Do not include private variables in the output.") | 
 |  | 
 |     generation_group.add_option("--show-imports", | 
 |         action="store_true", dest="show_imports", | 
 |         help="List each module's imports.") | 
 |  | 
 |     generation_group.add_option("--no-imports", | 
 |         action="store_false", dest="show_imports", | 
 |         help="Do not list each module's imports. (default)") | 
 |  | 
 |     generation_group.add_option('--show-sourcecode', | 
 |         action='store_true', dest='include_source_code', | 
 |         help=("Include source code with syntax highlighting in the " | 
 |               "HTML output. (default)")) | 
 |  | 
 |     generation_group.add_option('--no-sourcecode', | 
 |         action='store_false', dest='include_source_code', | 
 |         help=("Do not include source code with syntax highlighting in the " | 
 |               "HTML output.")) | 
 |  | 
 |     generation_group.add_option('--include-log', | 
 |         action='store_true', dest='include_log', | 
 |         help=("Include a page with the process log (epydoc-log.html)")) | 
 |  | 
 |     generation_group.add_option( | 
 |         '--redundant-details', | 
 |         action='store_true', dest='redundant_details', | 
 |         help=("Include values in the details lists even if all info " | 
 |               "about them is already provided by the summary table.")) | 
 |  | 
 |     output_group = OptionGroup(optparser, 'Output Options') | 
 |     optparser.add_option_group(output_group) | 
 |  | 
 |     output_group.add_option("--name", "-n", | 
 |         dest="prj_name", metavar="NAME", | 
 |         help="The documented project's name (for the navigation bar).") | 
 |  | 
 |     output_group.add_option("--css", "-c", | 
 |         dest="css", metavar="STYLESHEET", | 
 |         help="The CSS stylesheet.  STYLESHEET can be either a " | 
 |         "builtin stylesheet or the name of a CSS file.") | 
 |  | 
 |     output_group.add_option("--url", "-u", | 
 |         dest="prj_url", metavar="URL", | 
 |         help="The documented project's URL (for the navigation bar).") | 
 |  | 
 |     output_group.add_option("--navlink", | 
 |         dest="prj_link", metavar="HTML", | 
 |         help="HTML code for a navigation link to place in the " | 
 |         "navigation bar.") | 
 |  | 
 |     output_group.add_option("--top", | 
 |         dest="top_page", metavar="PAGE", | 
 |         help="The \"top\" page for the HTML documentation.  PAGE can " | 
 |         "be a URL, the name of a module or class, or one of the " | 
 |         "special names \"trees.html\", \"indices.html\", or \"help.html\"") | 
 |  | 
 |     output_group.add_option("--help-file", | 
 |         dest="help_file", metavar="FILE", | 
 |         help="An alternate help file.  FILE should contain the body " | 
 |         "of an HTML file -- navigation bars will be added to it.") | 
 |  | 
 |     output_group.add_option("--show-frames", | 
 |         action="store_true", dest="show_frames", | 
 |         help="Include frames in the HTML output. (default)") | 
 |  | 
 |     output_group.add_option("--no-frames", | 
 |         action="store_false", dest="show_frames", | 
 |         help="Do not include frames in the HTML output.") | 
 |  | 
 |     output_group.add_option('--separate-classes', | 
 |         action='store_true', dest='list_classes_separately', | 
 |         help=("When generating LaTeX or PDF output, list each class in " | 
 |               "its own section, instead of listing them under their " | 
 |               "containing module.")) | 
 |  | 
 |     output_group.add_option('--src-code-tab-width', | 
 |         action='store', type='int', dest='src_code_tab_width', | 
 |         help=("When generating HTML output, sets the number of spaces " | 
 |               "each tab in source code listings is replaced with.")) | 
 |      | 
 |     # The group of external API options. | 
 |     # Skip if the module couldn't be imported (usually missing docutils) | 
 |     if xlink is not None: | 
 |         link_group = OptionGroup(optparser, | 
 |                                  xlink.ApiLinkReader.settings_spec[0]) | 
 |         optparser.add_option_group(link_group) | 
 |  | 
 |         for help, names, opts in xlink.ApiLinkReader.settings_spec[2]: | 
 |             opts = opts.copy() | 
 |             opts['help'] = help | 
 |             link_group.add_option(*names, **opts) | 
 |  | 
 |     graph_group = OptionGroup(optparser, 'Graph Options') | 
 |     optparser.add_option_group(graph_group) | 
 |  | 
 |     graph_group.add_option('--graph', | 
 |         action='append', dest='graphs', metavar='GRAPHTYPE', | 
 |         help=("Include graphs of type GRAPHTYPE in the generated output.  " | 
 |               "Graphs are generated using the Graphviz dot executable.  " | 
 |               "If this executable is not on the path, then use --dotpath " | 
 |               "to specify its location.  This option may be repeated to " | 
 |               "include multiple graph types in the output.  GRAPHTYPE " | 
 |               "should be one of: all, %s." % ', '.join(GRAPH_TYPES))) | 
 |  | 
 |     graph_group.add_option("--dotpath", | 
 |         dest="dotpath", metavar='PATH', | 
 |         help="The path to the Graphviz 'dot' executable.") | 
 |  | 
 |     graph_group.add_option('--graph-font', | 
 |         dest='graph_font', metavar='FONT', | 
 |         help=("Specify the font used to generate Graphviz graphs.  (e.g., " | 
 |               "helvetica or times).")) | 
 |  | 
 |     graph_group.add_option('--graph-font-size', | 
 |         dest='graph_font_size', metavar='SIZE', | 
 |         help=("Specify the font size used to generate Graphviz graphs, " | 
 |               "in points.")) | 
 |  | 
 |     graph_group.add_option('--pstat', | 
 |         action='append', dest='pstat_files', metavar='FILE', | 
 |         help="A pstat output file, to be used in generating call graphs.") | 
 |  | 
 |     # this option is for developers, not users. | 
 |     graph_group.add_option("--profile-epydoc", | 
 |         action="store_true", dest="profile", | 
 |         help=SUPPRESS_HELP or | 
 |              ("Run the hotshot profiler on epydoc itself.  Output " | 
 |               "will be written to profile.out.")) | 
 |  | 
 |  | 
 |     return_group = OptionGroup(optparser, 'Return Value Options') | 
 |     optparser.add_option_group(return_group) | 
 |  | 
 |     return_group.add_option("--fail-on-error", | 
 |         action="store_const", dest="fail_on", const=log.ERROR, | 
 |         help="Return a non-zero exit status, indicating failure, if any " | 
 |         "errors are encountered.") | 
 |  | 
 |     return_group.add_option("--fail-on-warning", | 
 |         action="store_const", dest="fail_on", const=log.WARNING, | 
 |         help="Return a non-zero exit status, indicating failure, if any " | 
 |         "errors or warnings are encountered (not including docstring " | 
 |         "warnings).") | 
 |  | 
 |     return_group.add_option("--fail-on-docstring-warning", | 
 |         action="store_const", dest="fail_on", const=log.DOCSTRING_WARNING, | 
 |         help="Return a non-zero exit status, indicating failure, if any " | 
 |         "errors or warnings are encountered (including docstring " | 
 |         "warnings).") | 
 |  | 
 |     # Set the option parser's defaults. | 
 |     optparser.set_defaults(**OPTION_DEFAULTS) | 
 |  | 
 |     # Parse the arguments. | 
 |     options, names = optparser.parse_args() | 
 |  | 
 |     # Print help message, if requested.  We also provide support for | 
 |     # --help [topic] | 
 |     if options.action == 'help': | 
 |         names = set([n.lower() for n in names]) | 
 |         for (topic, msg) in HELP_TOPICS.items(): | 
 |             if topic.lower() in names: | 
 |                 print '\n' + msg.rstrip() + '\n' | 
 |                 sys.exit(0) | 
 |         optparser.print_help() | 
 |         sys.exit(0) | 
 |  | 
 |     # Print version message, if requested. | 
 |     if options.action == 'version': | 
 |         print version | 
 |         sys.exit(0) | 
 |      | 
 |     # Process any config files. | 
 |     if options.configfiles: | 
 |         try: | 
 |             parse_configfiles(options.configfiles, options, names) | 
 |         except (KeyboardInterrupt,SystemExit): raise | 
 |         except Exception, e: | 
 |             if len(options.configfiles) == 1: | 
 |                 cf_name = 'config file %s' % options.configfiles[0] | 
 |             else: | 
 |                 cf_name = 'config files %s' % ', '.join(options.configfiles) | 
 |             optparser.error('Error reading %s:\n    %s' % (cf_name, e)) | 
 |  | 
 |     # Check if the input file is a pickle file. | 
 |     for name in names: | 
 |         if name.endswith('.pickle'): | 
 |             if len(names) != 1: | 
 |                 optparser.error("When a pickle file is specified, no other " | 
 |                                "input files may be specified.") | 
 |             options.load_pickle = True | 
 |      | 
 |     # Check to make sure all options are valid. | 
 |     if len(names) == 0: | 
 |         optparser.error("No names specified.") | 
 |          | 
 |     # perform shell expansion. | 
 |     for i, name in reversed(list(enumerate(names[:]))): | 
 |         if '?' in name or '*' in name: | 
 |             names[i:i+1] = glob(name) | 
 |          | 
 |     if options.inheritance not in INHERITANCE_STYLES: | 
 |         optparser.error("Bad inheritance style.  Valid options are " + | 
 |                         ",".join(INHERITANCE_STYLES)) | 
 |     if not options.parse and not options.introspect: | 
 |         optparser.error("Invalid option combination: --parse-only " | 
 |                         "and --introspect-only.") | 
 |     if options.action == 'text' and len(names) > 1: | 
 |         optparser.error("--text option takes only one name.") | 
 |  | 
 |     # Check the list of requested graph types to make sure they're | 
 |     # acceptable. | 
 |     options.graphs = [graph_type.lower() for graph_type in options.graphs] | 
 |     for graph_type in options.graphs: | 
 |         if graph_type == 'callgraph' and not options.pstat_files: | 
 |             optparser.error('"callgraph" graph type may only be used if ' | 
 |                             'one or more pstat files are specified.') | 
 |         # If it's 'all', then add everything (but don't add callgraph if | 
 |         # we don't have any profiling info to base them on). | 
 |         if graph_type == 'all': | 
 |             if options.pstat_files: | 
 |                 options.graphs = GRAPH_TYPES | 
 |             else: | 
 |                 options.graphs = [g for g in GRAPH_TYPES if g != 'callgraph'] | 
 |             break | 
 |         elif graph_type not in GRAPH_TYPES: | 
 |             optparser.error("Invalid graph type %s." % graph_type) | 
 |  | 
 |     # Calculate verbosity. | 
 |     verbosity = getattr(options, 'verbosity', 0) | 
 |     options.verbosity = verbosity + options.verbose - options.quiet | 
 |  | 
 |     # The target default depends on the action. | 
 |     if options.target is None: | 
 |         options.target = options.action | 
 |      | 
 |     # Return parsed args. | 
 |     options.names = names | 
 |     return options, names | 
 |  | 
 | def parse_configfiles(configfiles, options, names): | 
 |     configparser = ConfigParser.ConfigParser() | 
 |     # ConfigParser.read() silently ignores errors, so open the files | 
 |     # manually (since we want to notify the user of any errors). | 
 |     for configfile in configfiles: | 
 |         fp = open(configfile, 'r') # may raise IOError. | 
 |         configparser.readfp(fp, configfile) | 
 |         fp.close() | 
 |     for optname in configparser.options('epydoc'): | 
 |         val = configparser.get('epydoc', optname, vars=os.environ).strip() | 
 |         optname = optname.lower().strip() | 
 |  | 
 |         if optname in ('modules', 'objects', 'values', | 
 |                        'module', 'object', 'value'): | 
 |             names.extend(_str_to_list(val)) | 
 |         elif optname == 'target': | 
 |             options.target = val | 
 |         elif optname == 'output': | 
 |             if val.lower() not in ACTIONS: | 
 |                 raise ValueError('"%s" expected one of: %s' % | 
 |                                  (optname, ', '.join(ACTIONS))) | 
 |             options.action = val.lower() | 
 |         elif optname == 'verbosity': | 
 |             options.verbosity = _str_to_int(val, optname) | 
 |         elif optname == 'debug': | 
 |             options.debug = _str_to_bool(val, optname) | 
 |         elif optname in ('simple-term', 'simple_term'): | 
 |             options.simple_term = _str_to_bool(val, optname) | 
 |  | 
 |         # Generation options | 
 |         elif optname == 'docformat': | 
 |             options.docformat = val | 
 |         elif optname == 'parse': | 
 |             options.parse = _str_to_bool(val, optname) | 
 |         elif optname == 'introspect': | 
 |             options.introspect = _str_to_bool(val, optname) | 
 |         elif optname == 'exclude': | 
 |             options.exclude.extend(_str_to_list(val)) | 
 |         elif optname in ('exclude-parse', 'exclude_parse'): | 
 |             options.exclude_parse.extend(_str_to_list(val)) | 
 |         elif optname in ('exclude-introspect', 'exclude_introspect'): | 
 |             options.exclude_introspect.extend(_str_to_list(val)) | 
 |         elif optname == 'inheritance': | 
 |             if val.lower() not in INHERITANCE_STYLES: | 
 |                 raise ValueError('"%s" expected one of: %s.' % | 
 |                                  (optname, ', '.join(INHERITANCE_STYLES))) | 
 |             options.inheritance = val.lower() | 
 |         elif optname =='private': | 
 |             options.show_private = _str_to_bool(val, optname) | 
 |         elif optname =='imports': | 
 |             options.show_imports = _str_to_bool(val, optname) | 
 |         elif optname == 'sourcecode': | 
 |             options.include_source_code = _str_to_bool(val, optname) | 
 |         elif optname in ('include-log', 'include_log'): | 
 |             options.include_log = _str_to_bool(val, optname) | 
 |         elif optname in ('redundant-details', 'redundant_details'): | 
 |             options.redundant_details = _str_to_bool(val, optname) | 
 |  | 
 |         # Output options | 
 |         elif optname == 'name': | 
 |             options.prj_name = val | 
 |         elif optname == 'css': | 
 |             options.css = val | 
 |         elif optname == 'url': | 
 |             options.prj_url = val | 
 |         elif optname == 'link': | 
 |             options.prj_link = val | 
 |         elif optname == 'top': | 
 |             options.top_page = val | 
 |         elif optname == 'help': | 
 |             options.help_file = val | 
 |         elif optname =='frames': | 
 |             options.show_frames = _str_to_bool(val, optname) | 
 |         elif optname in ('separate-classes', 'separate_classes'): | 
 |             options.list_classes_separately = _str_to_bool(val, optname) | 
 |         elif optname in ('src-code-tab-width', 'src_code_tab_width'): | 
 |             options.src_code_tab_width = _str_to_int(val, optname) | 
 |  | 
 |         # External API | 
 |         elif optname in ('external-api', 'external_api'): | 
 |             options.external_api.extend(_str_to_list(val)) | 
 |         elif optname in ('external-api-file', 'external_api_file'): | 
 |             options.external_api_file.extend(_str_to_list(val)) | 
 |         elif optname in ('external-api-root', 'external_api_root'): | 
 |             options.external_api_root.extend(_str_to_list(val)) | 
 |  | 
 |         # Graph options | 
 |         elif optname == 'graph': | 
 |             graphtypes = _str_to_list(val) | 
 |             for graphtype in graphtypes: | 
 |                 if graphtype not in GRAPH_TYPES + ('all',): | 
 |                     raise ValueError('"%s" expected one of: all, %s.' % | 
 |                                      (optname, ', '.join(GRAPH_TYPES))) | 
 |             options.graphs.extend(graphtypes) | 
 |         elif optname == 'dotpath': | 
 |             options.dotpath = val | 
 |         elif optname in ('graph-font', 'graph_font'): | 
 |             options.graph_font = val | 
 |         elif optname in ('graph-font-size', 'graph_font_size'): | 
 |             options.graph_font_size = _str_to_int(val, optname) | 
 |         elif optname == 'pstat': | 
 |             options.pstat_files.extend(_str_to_list(val)) | 
 |  | 
 |         # Return value options | 
 |         elif optname in ('failon', 'fail-on', 'fail_on'): | 
 |             if val.lower().strip() in ('error', 'errors'): | 
 |                 options.fail_on = log.ERROR | 
 |             elif val.lower().strip() in ('warning', 'warnings'): | 
 |                 options.fail_on = log.WARNING | 
 |             elif val.lower().strip() in ('docstring_warning', | 
 |                                          'docstring_warnings'): | 
 |                 options.fail_on = log.DOCSTRING_WARNING | 
 |             else: | 
 |                 raise ValueError("%r expected one of: error, warning, " | 
 |                                  "docstring_warning" % optname) | 
 |         else: | 
 |             raise ValueError('Unknown option %s' % optname) | 
 |  | 
 | def _str_to_bool(val, optname): | 
 |     if val.lower() in ('0', 'no', 'false', 'n', 'f', 'hide'): | 
 |         return False | 
 |     elif val.lower() in ('1', 'yes', 'true', 'y', 't', 'show'): | 
 |         return True | 
 |     else: | 
 |         raise ValueError('"%s" option expected a boolean' % optname) | 
 |          | 
 | def _str_to_int(val, optname): | 
 |     try: | 
 |         return int(val) | 
 |     except ValueError: | 
 |         raise ValueError('"%s" option expected an int' % optname) | 
 |  | 
 | def _str_to_list(val): | 
 |     return val.replace(',', ' ').split() | 
 |  | 
 | ###################################################################### | 
 | #{ Interface | 
 | ###################################################################### | 
 |  | 
 | def main(options, names): | 
 |     # Set the debug flag, if '--debug' was specified. | 
 |     if options.debug: | 
 |         epydoc.DEBUG = True | 
 |  | 
 |     ## [XX] Did this serve a purpose?  Commenting out for now: | 
 |     #if options.action == 'text': | 
 |     #    if options.parse and options.introspect: | 
 |     #        options.parse = False | 
 |  | 
 |     # Set up the logger | 
 |     if options.simple_term: | 
 |         TerminalController.FORCE_SIMPLE_TERM = True | 
 |     if options.action == 'text': | 
 |         logger = None # no logger for text output. | 
 |     elif options.verbosity > 1: | 
 |         logger = ConsoleLogger(options.verbosity) | 
 |         log.register_logger(logger) | 
 |     else: | 
 |         # Each number is a rough approximation of how long we spend on | 
 |         # that task, used to divide up the unified progress bar. | 
 |         stages = [40,  # Building documentation | 
 |                   7,   # Merging parsed & introspected information | 
 |                   1,   # Linking imported variables | 
 |                   3,   # Indexing documentation | 
 |                   1,   # Checking for overridden methods | 
 |                   30,  # Parsing Docstrings | 
 |                   1,   # Inheriting documentation | 
 |                   2]   # Sorting & Grouping | 
 |         if options.load_pickle: | 
 |             stages = [30] # Loading pickled documentation | 
 |         if options.action == 'html': stages += [100] | 
 |         elif options.action == 'text': stages += [30] | 
 |         elif options.action == 'latex': stages += [60] | 
 |         elif options.action == 'dvi': stages += [60,30] | 
 |         elif options.action == 'ps': stages += [60,40] | 
 |         elif options.action == 'pdf': stages += [60,50] | 
 |         elif options.action == 'check': stages += [10] | 
 |         elif options.action == 'pickle': stages += [10] | 
 |         else: raise ValueError, '%r not supported' % options.action | 
 |         if options.parse and not options.introspect: | 
 |             del stages[1] # no merging | 
 |         if options.introspect and not options.parse: | 
 |             del stages[1:3] # no merging or linking | 
 |         logger = UnifiedProgressConsoleLogger(options.verbosity, stages) | 
 |         log.register_logger(logger) | 
 |  | 
 |     # check the output directory. | 
 |     if options.action not in ('text', 'check', 'pickle'): | 
 |         if os.path.exists(options.target): | 
 |             if not os.path.isdir(options.target): | 
 |                 log.error("%s is not a directory" % options.target) | 
 |                 sys.exit(1) | 
 |  | 
 |     if options.include_log: | 
 |         if options.action == 'html': | 
 |             if not os.path.exists(options.target): | 
 |                 os.mkdir(options.target) | 
 |             log.register_logger(HTMLLogger(options.target, options)) | 
 |         else: | 
 |             log.warning("--include-log requires --html") | 
 |  | 
 |     # Set the default docformat | 
 |     from epydoc import docstringparser | 
 |     docstringparser.DEFAULT_DOCFORMAT = options.docformat | 
 |  | 
 |     # Configure the external API linking | 
 |     if xlink is not None: | 
 |         try: | 
 |             xlink.ApiLinkReader.read_configuration(options, problematic=False) | 
 |         except Exception, exc: | 
 |             log.error("Error while configuring external API linking: %s: %s" | 
 |                 % (exc.__class__.__name__, exc)) | 
 |  | 
 |     # Set the dot path | 
 |     if options.dotpath: | 
 |         from epydoc.docwriter import dotgraph | 
 |         dotgraph.DOT_COMMAND = options.dotpath | 
 |  | 
 |     # Set the default graph font & size | 
 |     if options.graph_font: | 
 |         from epydoc.docwriter import dotgraph | 
 |         fontname = options.graph_font | 
 |         dotgraph.DotGraph.DEFAULT_NODE_DEFAULTS['fontname'] = fontname | 
 |         dotgraph.DotGraph.DEFAULT_EDGE_DEFAULTS['fontname'] = fontname | 
 |     if options.graph_font_size: | 
 |         from epydoc.docwriter import dotgraph | 
 |         fontsize = options.graph_font_size | 
 |         dotgraph.DotGraph.DEFAULT_NODE_DEFAULTS['fontsize'] = fontsize | 
 |         dotgraph.DotGraph.DEFAULT_EDGE_DEFAULTS['fontsize'] = fontsize | 
 |  | 
 |     # If the input name is a pickle file, then read the docindex that | 
 |     # it contains.  Otherwise, build the docs for the input names. | 
 |     if options.load_pickle: | 
 |         assert len(names) == 1 | 
 |         log.start_progress('Deserializing') | 
 |         log.progress(0.1, 'Loading %r' % names[0]) | 
 |         t0 = time.time() | 
 |         unpickler = pickle.Unpickler(open(names[0], 'rb')) | 
 |         unpickler.persistent_load = pickle_persistent_load | 
 |         docindex = unpickler.load() | 
 |         log.debug('deserialization time: %.1f sec' % (time.time()-t0)) | 
 |         log.end_progress() | 
 |     else: | 
 |         # Build docs for the named values. | 
 |         from epydoc.docbuilder import build_doc_index | 
 |         exclude_parse = '|'.join(options.exclude_parse+options.exclude) | 
 |         exclude_introspect = '|'.join(options.exclude_introspect+ | 
 |                                       options.exclude) | 
 |         docindex = build_doc_index(names, options.introspect, options.parse, | 
 |                                    add_submodules=(options.action!='text'), | 
 |                                    exclude_introspect=exclude_introspect, | 
 |                                    exclude_parse=exclude_parse) | 
 |  | 
 |     if docindex is None: | 
 |         if log.ERROR in logger.reported_message_levels: | 
 |             sys.exit(1) | 
 |         else: | 
 |             return # docbuilder already logged an error. | 
 |  | 
 |     # Load profile information, if it was given. | 
 |     if options.pstat_files: | 
 |         try: import pstats | 
 |         except ImportError: | 
 |             log.error("Could not import pstats -- ignoring pstat files.") | 
 |         try: | 
 |             profile_stats = pstats.Stats(options.pstat_files[0]) | 
 |             for filename in options.pstat_files[1:]: | 
 |                 profile_stats.add(filename) | 
 |         except KeyboardInterrupt: raise | 
 |         except Exception, e: | 
 |             log.error("Error reading pstat file: %s" % e) | 
 |             profile_stats = None | 
 |         if profile_stats is not None: | 
 |             docindex.read_profiling_info(profile_stats) | 
 |  | 
 |     # Perform the specified action. | 
 |     if options.action == 'html': | 
 |         write_html(docindex, options) | 
 |     elif options.action in ('latex', 'dvi', 'ps', 'pdf'): | 
 |         write_latex(docindex, options, options.action) | 
 |     elif options.action == 'text': | 
 |         write_text(docindex, options) | 
 |     elif options.action == 'check': | 
 |         check_docs(docindex, options) | 
 |     elif options.action == 'pickle': | 
 |         write_pickle(docindex, options) | 
 |     else: | 
 |         print >>sys.stderr, '\nUnsupported action %s!' % options.action | 
 |  | 
 |     # If we suppressed docstring warnings, then let the user know. | 
 |     if logger is not None and logger.suppressed_docstring_warning: | 
 |         if logger.suppressed_docstring_warning == 1: | 
 |             prefix = '1 markup error was found' | 
 |         else: | 
 |             prefix = ('%d markup errors were found' % | 
 |                       logger.suppressed_docstring_warning) | 
 |         log.warning("%s while processing docstrings.  Use the verbose " | 
 |                     "switch (-v) to display markup errors." % prefix) | 
 |  | 
 |     # Basic timing breakdown: | 
 |     if options.verbosity >= 2 and logger is not None: | 
 |         logger.print_times() | 
 |  | 
 |     # If we encountered any message types that we were requested to | 
 |     # fail on, then exit with status 2. | 
 |     if options.fail_on is not None: | 
 |         max_reported_message_level = max(logger.reported_message_levels) | 
 |         if max_reported_message_level >= options.fail_on: | 
 |             sys.exit(2) | 
 |  | 
 | def write_html(docindex, options): | 
 |     from epydoc.docwriter.html import HTMLWriter | 
 |     html_writer = HTMLWriter(docindex, **options.__dict__) | 
 |     if options.verbose > 0: | 
 |         log.start_progress('Writing HTML docs to %r' % options.target) | 
 |     else: | 
 |         log.start_progress('Writing HTML docs') | 
 |     html_writer.write(options.target) | 
 |     log.end_progress() | 
 |  | 
 | def write_pickle(docindex, options): | 
 |     """Helper for writing output to a pickle file, which can then be | 
 |     read in at a later time.  But loading the pickle is only marginally | 
 |     faster than building the docs from scratch, so this has pretty | 
 |     limited application.""" | 
 |     if options.target == 'pickle': | 
 |         options.target = 'api.pickle' | 
 |     elif not options.target.endswith('.pickle'): | 
 |         options.target += '.pickle' | 
 |  | 
 |     log.start_progress('Serializing output') | 
 |     log.progress(0.2, 'Writing %r' % options.target) | 
 |     outfile = open(options.target, 'wb') | 
 |     pickler = pickle.Pickler(outfile, protocol=0) | 
 |     pickler.persistent_id = pickle_persistent_id | 
 |     pickler.dump(docindex) | 
 |     outfile.close() | 
 |     log.end_progress() | 
 |  | 
 | def pickle_persistent_id(obj): | 
 |     """Helper for pickling, which allows us to save and restore UNKNOWN, | 
 |     which is required to be identical to apidoc.UNKNOWN.""" | 
 |     if obj is UNKNOWN: return 'UNKNOWN' | 
 |     else: return None | 
 |  | 
 | def pickle_persistent_load(identifier): | 
 |     """Helper for pickling, which allows us to save and restore UNKNOWN, | 
 |     which is required to be identical to apidoc.UNKNOWN.""" | 
 |     if identifier == 'UNKNOWN': return UNKNOWN | 
 |     else: raise pickle.UnpicklingError, 'Invalid persistent id' | 
 |  | 
 | _RERUN_LATEX_RE = re.compile(r'(?im)^LaTeX\s+Warning:\s+Label\(s\)\s+may' | 
 |                              r'\s+have\s+changed.\s+Rerun') | 
 |  | 
 | def write_latex(docindex, options, format): | 
 |     from epydoc.docwriter.latex import LatexWriter | 
 |     latex_writer = LatexWriter(docindex, **options.__dict__) | 
 |     log.start_progress('Writing LaTeX docs') | 
 |     latex_writer.write(options.target) | 
 |     log.end_progress() | 
 |     # If we're just generating the latex, and not any output format, | 
 |     # then we're done. | 
 |     if format == 'latex': return | 
 |      | 
 |     if format == 'dvi': steps = 4 | 
 |     elif format == 'ps': steps = 5 | 
 |     elif format == 'pdf': steps = 6 | 
 |      | 
 |     log.start_progress('Processing LaTeX docs') | 
 |     oldpath = os.path.abspath(os.curdir) | 
 |     running = None # keep track of what we're doing. | 
 |     try: | 
 |         try: | 
 |             os.chdir(options.target) | 
 |  | 
 |             # Clear any old files out of the way. | 
 |             for ext in 'tex aux log out idx ilg toc ind'.split(): | 
 |                 if os.path.exists('apidoc.%s' % ext): | 
 |                     os.remove('apidoc.%s' % ext) | 
 |  | 
 |             # The first pass generates index files. | 
 |             running = 'latex' | 
 |             log.progress(0./steps, 'LaTeX: First pass') | 
 |             run_subprocess('latex api.tex') | 
 |  | 
 |             # Build the index. | 
 |             running = 'makeindex' | 
 |             log.progress(1./steps, 'LaTeX: Build index') | 
 |             run_subprocess('makeindex api.idx') | 
 |  | 
 |             # The second pass generates our output. | 
 |             running = 'latex' | 
 |             log.progress(2./steps, 'LaTeX: Second pass') | 
 |             out, err = run_subprocess('latex api.tex') | 
 |              | 
 |             # The third pass is only necessary if the second pass | 
 |             # changed what page some things are on. | 
 |             running = 'latex' | 
 |             if _RERUN_LATEX_RE.match(out): | 
 |                 log.progress(3./steps, 'LaTeX: Third pass') | 
 |                 out, err = run_subprocess('latex api.tex') | 
 |   | 
 |             # A fourth path should (almost?) never be necessary. | 
 |             running = 'latex' | 
 |             if _RERUN_LATEX_RE.match(out): | 
 |                 log.progress(3./steps, 'LaTeX: Fourth pass') | 
 |                 run_subprocess('latex api.tex') | 
 |  | 
 |             # If requested, convert to postscript. | 
 |             if format in ('ps', 'pdf'): | 
 |                 running = 'dvips' | 
 |                 log.progress(4./steps, 'dvips') | 
 |                 run_subprocess('dvips api.dvi -o api.ps -G0 -Ppdf') | 
 |  | 
 |             # If requested, convert to pdf. | 
 |             if format in ('pdf'): | 
 |                 running = 'ps2pdf' | 
 |                 log.progress(5./steps, 'ps2pdf') | 
 |                 run_subprocess( | 
 |                     'ps2pdf -sPAPERSIZE#letter -dMaxSubsetPct#100 ' | 
 |                     '-dSubsetFonts#true -dCompatibilityLevel#1.2 ' | 
 |                     '-dEmbedAllFonts#true api.ps api.pdf') | 
 |         except RunSubprocessError, e: | 
 |             if running == 'latex': | 
 |                 e.out = re.sub(r'(?sm)\A.*?!( LaTeX Error:)?', r'', e.out) | 
 |                 e.out = re.sub(r'(?sm)\s*Type X to quit.*', '', e.out) | 
 |                 e.out = re.sub(r'(?sm)^! Emergency stop.*', '', e.out) | 
 |             log.error("%s failed: %s" % (running, (e.out+e.err).lstrip())) | 
 |         except OSError, e: | 
 |             log.error("%s failed: %s" % (running, e)) | 
 |     finally: | 
 |         os.chdir(oldpath) | 
 |         log.end_progress() | 
 |  | 
 | def write_text(docindex, options): | 
 |     log.start_progress('Writing output') | 
 |     from epydoc.docwriter.plaintext import PlaintextWriter | 
 |     plaintext_writer = PlaintextWriter() | 
 |     s = '' | 
 |     for apidoc in docindex.root: | 
 |         s += plaintext_writer.write(apidoc) | 
 |     log.end_progress() | 
 |     if isinstance(s, unicode): | 
 |         s = s.encode('ascii', 'backslashreplace') | 
 |     print s | 
 |  | 
 | def check_docs(docindex, options): | 
 |     from epydoc.checker import DocChecker | 
 |     DocChecker(docindex).check() | 
 |                  | 
 | def cli(): | 
 |     # Parse command-line arguments. | 
 |     options, names = parse_arguments() | 
 |  | 
 |     try: | 
 |         try: | 
 |             if options.profile: | 
 |                 _profile() | 
 |             else: | 
 |                 main(options, names) | 
 |         finally: | 
 |             log.close() | 
 |     except SystemExit: | 
 |         raise | 
 |     except KeyboardInterrupt: | 
 |         print '\n\n' | 
 |         print >>sys.stderr, 'Keyboard interrupt.' | 
 |     except: | 
 |         if options.debug: raise | 
 |         print '\n\n' | 
 |         exc_info = sys.exc_info() | 
 |         if isinstance(exc_info[0], basestring): e = exc_info[0] | 
 |         else: e = exc_info[1] | 
 |         print >>sys.stderr, ('\nUNEXPECTED ERROR:\n' | 
 |                              '%s\n' % (str(e) or e.__class__.__name__)) | 
 |         print >>sys.stderr, 'Use --debug to see trace information.' | 
 |         sys.exit(3) | 
 |      | 
 | def _profile(): | 
 |     # Hotshot profiler. | 
 |     if PROFILER == 'hotshot': | 
 |         try: import hotshot, hotshot.stats | 
 |         except ImportError: | 
 |             print >>sys.stderr, "Could not import profile module!" | 
 |             return | 
 |         try: | 
 |             prof = hotshot.Profile('hotshot.out') | 
 |             prof = prof.runctx('main(*parse_arguments())', globals(), {}) | 
 |         except SystemExit: | 
 |             pass | 
 |         prof.close() | 
 |         # Convert profile.hotshot -> profile.out | 
 |         print 'Consolidating hotshot profiling info...' | 
 |         hotshot.stats.load('hotshot.out').dump_stats('profile.out') | 
 |  | 
 |     # Standard 'profile' profiler. | 
 |     elif PROFILER == 'profile': | 
 |         # cProfile module was added in Python 2.5 -- use it if its' | 
 |         # available, since it's faster. | 
 |         try: from cProfile import Profile | 
 |         except ImportError: | 
 |             try: from profile import Profile | 
 |             except ImportError: | 
 |                 print >>sys.stderr, "Could not import profile module!" | 
 |                 return | 
 |  | 
 |         # There was a bug in Python 2.4's profiler.  Check if it's | 
 |         # present, and if so, fix it.  (Bug was fixed in 2.4maint: | 
 |         # <http://mail.python.org/pipermail/python-checkins/ | 
 |         #                         2005-September/047099.html>) | 
 |         if (hasattr(Profile, 'dispatch') and | 
 |             Profile.dispatch['c_exception'] is | 
 |             Profile.trace_dispatch_exception.im_func): | 
 |             trace_dispatch_return = Profile.trace_dispatch_return.im_func | 
 |             Profile.dispatch['c_exception'] = trace_dispatch_return | 
 |         try: | 
 |             prof = Profile() | 
 |             prof = prof.runctx('main(*parse_arguments())', globals(), {}) | 
 |         except SystemExit: | 
 |             pass | 
 |         prof.dump_stats('profile.out') | 
 |  | 
 |     else: | 
 |         print >>sys.stderr, 'Unknown profiler %s' % PROFILER | 
 |         return | 
 |      | 
 | ###################################################################### | 
 | #{ Logging | 
 | ###################################################################### | 
 |      | 
 | class TerminalController: | 
 |     """ | 
 |     A class that can be used to portably generate formatted output to | 
 |     a terminal.  See | 
 |     U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116} | 
 |     for documentation.  (This is a somewhat stripped-down version.) | 
 |     """ | 
 |     BOL = ''             #: Move the cursor to the beginning of the line | 
 |     UP = ''              #: Move the cursor up one line | 
 |     DOWN = ''            #: Move the cursor down one line | 
 |     LEFT = ''            #: Move the cursor left one char | 
 |     RIGHT = ''           #: Move the cursor right one char | 
 |     CLEAR_EOL = ''       #: Clear to the end of the line. | 
 |     CLEAR_LINE = ''      #: Clear the current line; cursor to BOL. | 
 |     BOLD = ''            #: Turn on bold mode | 
 |     NORMAL = ''          #: Turn off all modes | 
 |     COLS = 75            #: Width of the terminal (default to 75) | 
 |     BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' | 
 |      | 
 |     _STRING_CAPABILITIES = """ | 
 |     BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 | 
 |     CLEAR_EOL=el BOLD=bold UNDERLINE=smul NORMAL=sgr0""".split() | 
 |     _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() | 
 |     _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() | 
 |  | 
 |     #: If this is set to true, then new TerminalControllers will | 
 |     #: assume that the terminal is not capable of doing manipulation | 
 |     #: of any kind. | 
 |     FORCE_SIMPLE_TERM = False | 
 |  | 
 |     def __init__(self, term_stream=sys.stdout): | 
 |         # If the stream isn't a tty, then assume it has no capabilities. | 
 |         if not term_stream.isatty(): return | 
 |         if self.FORCE_SIMPLE_TERM: return | 
 |  | 
 |         # Curses isn't available on all platforms | 
 |         try: import curses | 
 |         except: | 
 |             # If it's not available, then try faking enough to get a | 
 |             # simple progress bar. | 
 |             self.BOL = '\r' | 
 |             self.CLEAR_LINE = '\r' + ' '*self.COLS + '\r' | 
 |              | 
 |         # Check the terminal type.  If we fail, then assume that the | 
 |         # terminal has no capabilities. | 
 |         try: curses.setupterm() | 
 |         except: return | 
 |  | 
 |         # Look up numeric capabilities. | 
 |         self.COLS = curses.tigetnum('cols') | 
 |          | 
 |         # Look up string capabilities. | 
 |         for capability in self._STRING_CAPABILITIES: | 
 |             (attrib, cap_name) = capability.split('=') | 
 |             setattr(self, attrib, self._tigetstr(cap_name) or '') | 
 |         if self.BOL and self.CLEAR_EOL: | 
 |             self.CLEAR_LINE = self.BOL+self.CLEAR_EOL | 
 |  | 
 |         # Colors | 
 |         set_fg = self._tigetstr('setf') | 
 |         if set_fg: | 
 |             for i,color in zip(range(len(self._COLORS)), self._COLORS): | 
 |                 setattr(self, color, curses.tparm(set_fg, i) or '') | 
 |         set_fg_ansi = self._tigetstr('setaf') | 
 |         if set_fg_ansi: | 
 |             for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): | 
 |                 setattr(self, color, curses.tparm(set_fg_ansi, i) or '') | 
 |  | 
 |     def _tigetstr(self, cap_name): | 
 |         # String capabilities can include "delays" of the form "$<2>". | 
 |         # For any modern terminal, we should be able to just ignore | 
 |         # these, so strip them out. | 
 |         import curses | 
 |         cap = curses.tigetstr(cap_name) or '' | 
 |         return re.sub(r'\$<\d+>[/*]?', '', cap) | 
 |  | 
 | class ConsoleLogger(log.Logger): | 
 |     def __init__(self, verbosity, progress_mode=None): | 
 |         self._verbosity = verbosity | 
 |         self._progress = None | 
 |         self._message_blocks = [] | 
 |         # For ETA display: | 
 |         self._progress_start_time = None | 
 |         # For per-task times: | 
 |         self._task_times = [] | 
 |         self._progress_header = None | 
 |  | 
 |         self.reported_message_levels = set() | 
 |         """This set contains all the message levels (WARNING, ERROR, | 
 |         etc) that have been reported.  It is used by the options | 
 |         --fail-on-warning etc to determine the return value.""" | 
 |          | 
 |         self.suppressed_docstring_warning = 0 | 
 |         """This variable will be incremented once every time a | 
 |         docstring warning is reported tothe logger, but the verbosity | 
 |         level is too low for it to be displayed.""" | 
 |  | 
 |         self.term = TerminalController() | 
 |  | 
 |         # Set the progress bar mode. | 
 |         if verbosity >= 2: self._progress_mode = 'list' | 
 |         elif verbosity >= 0: | 
 |             if progress_mode is not None: | 
 |                 self._progress_mode = progress_mode | 
 |             elif self.term.COLS < 15: | 
 |                 self._progress_mode = 'simple-bar' | 
 |             elif self.term.BOL and self.term.CLEAR_EOL and self.term.UP: | 
 |                 self._progress_mode = 'multiline-bar' | 
 |             elif self.term.BOL and self.term.CLEAR_LINE: | 
 |                 self._progress_mode = 'bar' | 
 |             else: | 
 |                 self._progress_mode = 'simple-bar' | 
 |         else: self._progress_mode = 'hide' | 
 |  | 
 |     def start_block(self, header): | 
 |         self._message_blocks.append( (header, []) ) | 
 |  | 
 |     def end_block(self): | 
 |         header, messages = self._message_blocks.pop() | 
 |         if messages: | 
 |             width = self.term.COLS - 5 - 2*len(self._message_blocks) | 
 |             prefix = self.term.CYAN+self.term.BOLD+'| '+self.term.NORMAL | 
 |             divider = (self.term.CYAN+self.term.BOLD+'+'+'-'*(width-1)+ | 
 |                        self.term.NORMAL) | 
 |             # Mark up the header: | 
 |             header = wordwrap(header, right=width-2, splitchars='\\/').rstrip() | 
 |             header = '\n'.join([prefix+self.term.CYAN+l+self.term.NORMAL | 
 |                                 for l in header.split('\n')]) | 
 |             # Construct the body: | 
 |             body = '' | 
 |             for message in messages: | 
 |                 if message.endswith('\n'): body += message | 
 |                 else: body += message+'\n' | 
 |             # Indent the body: | 
 |             body = '\n'.join([prefix+'  '+l for l in body.split('\n')]) | 
 |             # Put it all together: | 
 |             message = divider + '\n' + header + '\n' + body + '\n' | 
 |             self._report(message) | 
 |              | 
 |     def _format(self, prefix, message, color): | 
 |         """ | 
 |         Rewrap the message; but preserve newlines, and don't touch any | 
 |         lines that begin with spaces. | 
 |         """ | 
 |         lines = message.split('\n') | 
 |         startindex = indent = len(prefix) | 
 |         for i in range(len(lines)): | 
 |             if lines[i].startswith(' '): | 
 |                 lines[i] = ' '*(indent-startindex) + lines[i] + '\n' | 
 |             else: | 
 |                 width = self.term.COLS - 5 - 4*len(self._message_blocks) | 
 |                 lines[i] = wordwrap(lines[i], indent, width, startindex, '\\/') | 
 |             startindex = 0 | 
 |         return color+prefix+self.term.NORMAL+''.join(lines) | 
 |  | 
 |     def log(self, level, message): | 
 |         self.reported_message_levels.add(level) | 
 |         if self._verbosity >= -2 and level >= log.ERROR: | 
 |             message = self._format('  Error: ', message, self.term.RED) | 
 |         elif self._verbosity >= -1 and level >= log.WARNING: | 
 |             message = self._format('Warning: ', message, self.term.YELLOW) | 
 |         elif self._verbosity >= 1 and level >= log.DOCSTRING_WARNING: | 
 |             message = self._format('Warning: ', message, self.term.YELLOW) | 
 |         elif self._verbosity >= 3 and level >= log.INFO: | 
 |             message = self._format('   Info: ', message, self.term.NORMAL) | 
 |         elif epydoc.DEBUG and level == log.DEBUG: | 
 |             message = self._format('  Debug: ', message, self.term.CYAN) | 
 |         else: | 
 |             if level >= log.DOCSTRING_WARNING: | 
 |                 self.suppressed_docstring_warning += 1 | 
 |             return | 
 |              | 
 |         self._report(message) | 
 |  | 
 |     def _report(self, message): | 
 |         if not message.endswith('\n'): message += '\n' | 
 |          | 
 |         if self._message_blocks: | 
 |             self._message_blocks[-1][-1].append(message) | 
 |         else: | 
 |             # If we're in the middle of displaying a progress bar, | 
 |             # then make room for the message. | 
 |             if self._progress_mode == 'simple-bar': | 
 |                 if self._progress is not None: | 
 |                     print | 
 |                     self._progress = None | 
 |             if self._progress_mode == 'bar': | 
 |                 sys.stdout.write(self.term.CLEAR_LINE) | 
 |             if self._progress_mode == 'multiline-bar': | 
 |                 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 + | 
 |                                  self.term.CLEAR_EOL + self.term.UP*2) | 
 |  | 
 |             # Display the message message. | 
 |             sys.stdout.write(message) | 
 |             sys.stdout.flush() | 
 |                  | 
 |     def progress(self, percent, message=''): | 
 |         percent = min(1.0, percent) | 
 |         message = '%s' % message | 
 |          | 
 |         if self._progress_mode == 'list': | 
 |             if message: | 
 |                 print '[%3d%%] %s' % (100*percent, message) | 
 |                 sys.stdout.flush() | 
 |                  | 
 |         elif self._progress_mode == 'bar': | 
 |             dots = int((self.term.COLS/2-8)*percent) | 
 |             background = '-'*(self.term.COLS/2-8) | 
 |             if len(message) > self.term.COLS/2: | 
 |                 message = message[:self.term.COLS/2-3]+'...' | 
 |             sys.stdout.write(self.term.CLEAR_LINE + '%3d%% '%(100*percent) + | 
 |                              self.term.GREEN + '[' + self.term.BOLD + | 
 |                              '='*dots + background[dots:] + self.term.NORMAL + | 
 |                              self.term.GREEN + '] ' + self.term.NORMAL + | 
 |                              message + self.term.BOL) | 
 |             sys.stdout.flush() | 
 |             self._progress = percent | 
 |         elif self._progress_mode == 'multiline-bar': | 
 |             dots = int((self.term.COLS-10)*percent) | 
 |             background = '-'*(self.term.COLS-10) | 
 |              | 
 |             if len(message) > self.term.COLS-10: | 
 |                 message = message[:self.term.COLS-10-3]+'...' | 
 |             else: | 
 |                 message = message.center(self.term.COLS-10) | 
 |  | 
 |             time_elapsed = time.time()-self._progress_start_time | 
 |             if percent > 0: | 
 |                 time_remain = (time_elapsed / percent) * (1-percent) | 
 |             else: | 
 |                 time_remain = 0 | 
 |  | 
 |             sys.stdout.write( | 
 |                 # Line 1: | 
 |                 self.term.CLEAR_EOL + '      ' + | 
 |                 '%-8s' % self._timestr(time_elapsed) + | 
 |                 self.term.BOLD + 'Progress:'.center(self.term.COLS-26) + | 
 |                 self.term.NORMAL + '%8s' % self._timestr(time_remain) + '\n' + | 
 |                 # Line 2: | 
 |                 self.term.CLEAR_EOL + ('%3d%% ' % (100*percent)) + | 
 |                 self.term.GREEN + '[' +  self.term.BOLD + '='*dots + | 
 |                 background[dots:] + self.term.NORMAL + self.term.GREEN + | 
 |                 ']' + self.term.NORMAL + '\n' + | 
 |                 # Line 3: | 
 |                 self.term.CLEAR_EOL + '      ' + message + self.term.BOL + | 
 |                 self.term.UP + self.term.UP) | 
 |              | 
 |             sys.stdout.flush() | 
 |             self._progress = percent | 
 |         elif self._progress_mode == 'simple-bar': | 
 |             if self._progress is None: | 
 |                 sys.stdout.write('  [') | 
 |                 self._progress = 0.0 | 
 |             dots = int((self.term.COLS-2)*percent) | 
 |             progress_dots = int((self.term.COLS-2)*self._progress) | 
 |             if dots > progress_dots: | 
 |                 sys.stdout.write('.'*(dots-progress_dots)) | 
 |                 sys.stdout.flush() | 
 |                 self._progress = percent | 
 |  | 
 |     def _timestr(self, dt): | 
 |         dt = int(dt) | 
 |         if dt >= 3600: | 
 |             return '%d:%02d:%02d' % (dt/3600, dt%3600/60, dt%60) | 
 |         else: | 
 |             return '%02d:%02d' % (dt/60, dt%60) | 
 |  | 
 |     def start_progress(self, header=None): | 
 |         if self._progress is not None: | 
 |             raise ValueError | 
 |         self._progress = None | 
 |         self._progress_start_time = time.time() | 
 |         self._progress_header = header | 
 |         if self._progress_mode != 'hide' and header: | 
 |             print self.term.BOLD + header + self.term.NORMAL | 
 |  | 
 |     def end_progress(self): | 
 |         self.progress(1.) | 
 |         if self._progress_mode == 'bar': | 
 |             sys.stdout.write(self.term.CLEAR_LINE) | 
 |         if self._progress_mode == 'multiline-bar': | 
 |                 sys.stdout.write((self.term.CLEAR_EOL + '\n')*2 + | 
 |                                  self.term.CLEAR_EOL + self.term.UP*2) | 
 |         if self._progress_mode == 'simple-bar': | 
 |             print ']' | 
 |         self._progress = None | 
 |         self._task_times.append( (time.time()-self._progress_start_time, | 
 |                                   self._progress_header) ) | 
 |  | 
 |     def print_times(self): | 
 |         print | 
 |         print 'Timing summary:' | 
 |         total = sum([time for (time, task) in self._task_times]) | 
 |         max_t = max([time for (time, task) in self._task_times]) | 
 |         for (time, task) in self._task_times: | 
 |             task = task[:31] | 
 |             print '  %s%s %7.1fs' % (task, '.'*(35-len(task)), time), | 
 |             if self.term.COLS > 55: | 
 |                 print '|'+'=' * int((self.term.COLS-53) * time / max_t) | 
 |             else: | 
 |                 print | 
 |         print | 
 |  | 
 | class UnifiedProgressConsoleLogger(ConsoleLogger): | 
 |     def __init__(self, verbosity, stages, progress_mode=None): | 
 |         self.stage = 0 | 
 |         self.stages = stages | 
 |         self.task = None | 
 |         ConsoleLogger.__init__(self, verbosity, progress_mode) | 
 |          | 
 |     def progress(self, percent, message=''): | 
 |         #p = float(self.stage-1+percent)/self.stages | 
 |         i = self.stage-1 | 
 |         p = ((sum(self.stages[:i]) + percent*self.stages[i]) / | 
 |              float(sum(self.stages))) | 
 |  | 
 |         if message is UNKNOWN: message = None | 
 |         if message: message = '%s: %s' % (self.task, message) | 
 |         ConsoleLogger.progress(self, p, message) | 
 |  | 
 |     def start_progress(self, header=None): | 
 |         self.task = header | 
 |         if self.stage == 0: | 
 |             ConsoleLogger.start_progress(self) | 
 |         self.stage += 1 | 
 |  | 
 |     def end_progress(self): | 
 |         if self.stage == len(self.stages): | 
 |             ConsoleLogger.end_progress(self) | 
 |  | 
 |     def print_times(self): | 
 |         pass | 
 |  | 
 | class HTMLLogger(log.Logger): | 
 |     """ | 
 |     A logger used to generate a log of all warnings and messages to an | 
 |     HTML file. | 
 |     """ | 
 |      | 
 |     FILENAME = "epydoc-log.html" | 
 |     HEADER = textwrap.dedent('''\ | 
 |         <?xml version="1.0" encoding="ascii"?> | 
 |         <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | 
 |                   "DTD/xhtml1-transitional.dtd"> | 
 |         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | 
 |         <head> | 
 |           <title>Epydoc Log</title> | 
 |           <link rel="stylesheet" href="epydoc.css" type="text/css" /> | 
 |         </head> | 
 |          | 
 |         <body bgcolor="white" text="black" link="blue" vlink="#204080" | 
 |               alink="#204080"> | 
 |         <h1 class="epydoc">Epydoc Log</h1> | 
 |         <p class="log">Epydoc started at %s</p>''') | 
 |     START_BLOCK = '<div class="log-block"><h2 class="log-hdr">%s</h2>' | 
 |     MESSAGE = ('<div class="log-%s"><b>%s</b>: \n' | 
 |                '%s</div>\n') | 
 |     END_BLOCK = '</div>' | 
 |     FOOTER = "</body>\n</html>\n" | 
 |      | 
 |     def __init__(self, directory, options): | 
 |         self.start_time = time.time() | 
 |         self.out = open(os.path.join(directory, self.FILENAME), 'w') | 
 |         self.out.write(self.HEADER % time.ctime(self.start_time)) | 
 |         self.is_empty = True | 
 |         self.options = options | 
 |  | 
 |     def write_options(self, options): | 
 |         self.out.write(self.START_BLOCK % 'Epydoc Options') | 
 |         msg = '<table border="0" cellpadding="0" cellspacing="0">\n' | 
 |         opts = [(key, getattr(options, key)) for key in dir(options) | 
 |                 if key not in dir(optparse.Values)] | 
 |         opts = [(val==OPTION_DEFAULTS.get(key), key, val) | 
 |                 for (key, val) in opts] | 
 |         for is_default, key, val in sorted(opts): | 
 |             css = is_default and 'opt-default' or 'opt-changed' | 
 |             msg += ('<tr valign="top" class="%s"><td valign="top">%s</td>' | 
 |                     '<td valign="top"><tt> = </tt></td>' | 
 |                     '<td valign="top"><tt>%s</tt></td></tr>' % | 
 |                     (css, key, plaintext_to_html(repr(val)))) | 
 |         msg += '</table>\n' | 
 |         self.out.write('<div class="log-info">\n%s</div>\n' % msg) | 
 |         self.out.write(self.END_BLOCK) | 
 |  | 
 |     def start_block(self, header): | 
 |         self.out.write(self.START_BLOCK % header) | 
 |  | 
 |     def end_block(self): | 
 |         self.out.write(self.END_BLOCK) | 
 |  | 
 |     def log(self, level, message): | 
 |         if message.endswith("(-v) to display markup errors."): return | 
 |         if level >= log.ERROR: | 
 |             self.out.write(self._message('error', message)) | 
 |         elif level >= log.WARNING: | 
 |             self.out.write(self._message('warning', message)) | 
 |         elif level >= log.DOCSTRING_WARNING: | 
 |             self.out.write(self._message('docstring warning', message)) | 
 |  | 
 |     def _message(self, level, message): | 
 |         self.is_empty = False | 
 |         message = plaintext_to_html(message) | 
 |         if '\n' in message: | 
 |             message = '<pre class="log">%s</pre>' % message | 
 |         hdr = ' '.join([w.capitalize() for w in level.split()]) | 
 |         return self.MESSAGE % (level.split()[-1], hdr, message) | 
 |  | 
 |     def close(self): | 
 |         if self.is_empty: | 
 |             self.out.write('<div class="log-info">' | 
 |                            'No warnings or errors!</div>') | 
 |         self.write_options(self.options) | 
 |         self.out.write('<p class="log">Epydoc finished at %s</p>\n' | 
 |                        '<p class="log">(Elapsed time: %s)</p>' % | 
 |                        (time.ctime(), self._elapsed_time())) | 
 |         self.out.write(self.FOOTER) | 
 |         self.out.close() | 
 |  | 
 |     def _elapsed_time(self): | 
 |         secs = int(time.time()-self.start_time) | 
 |         if secs < 60: | 
 |             return '%d seconds' % secs | 
 |         if secs < 3600: | 
 |             return '%d minutes, %d seconds' % (secs/60, secs%60) | 
 |         else: | 
 |             return '%d hours, %d minutes' % (secs/3600, secs%3600) | 
 |              | 
 |  | 
 | ###################################################################### | 
 | ## main | 
 | ###################################################################### | 
 |  | 
 | if __name__ == '__main__': | 
 |     cli() | 
 |  |