~ubuntu-branches/debian/lenny/epydoc/lenny

« back to all changes in this revision

Viewing changes to epydoc/cli.py

  • Committer: Bazaar Package Importer
  • Author(s): Kenneth J. Pronovici
  • Date: 2008-02-03 13:22:12 UTC
  • mfrom: (1.2.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080203132212-u2uohl6rswmlz2ra
Tags: 3.0.1-1
* New upstream release.
* Removed #! from top of epydoc/gui.py
* Got rid of version mangling in debian/watch.

Show diffs side-by-side

added added

removed removed

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