mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-18 00:20:54 +07:00
1471 lines
58 KiB
Python
1471 lines
58 KiB
Python
# 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()
|
|
|