1
# objdoc: epydoc command-line interface
4
# Created [03/15/02 10:31 PM]
5
# $Id: cli.py,v 1.61 2004/02/09 20:56:07 edloper Exp $
1
# epydoc -- Command line interface
3
# Copyright (C) 2005 Edward Loper
4
# Author: Edward Loper <edloper@loper.org>
5
# URL: <http://epydoc.sf.net>
7
# $Id: cli.py 1678 2008-01-29 17:21:29Z edloper $
8
# Note: if you change this docstring, check that you didn't break
10
# Note: As it is, the usage message fits in an 80x24 window, but if it
11
# gets any bigger, it won't.
13
Command-line interface for epydoc.
17
epydoc [OPTIONS] MODULES...
10
Command-line interface for epydoc. Abbreviated Usage::
12
epydoc [options] NAMES...
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).
36
See the epydoc(1) man page for a complete list of options.
38
@var PROFILE: Whether or not to run the profiler.
39
@var TESTS: The lists of tests that can be run with
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.
23
Run \"epydoc --help\" for a complete option list. See the epydoc(1)
24
man page for more information.
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
36
module modules value values object objects
38
A simple example of a config file is::
41
modules: sys, os, os.path, re, %(MYSANDBOXPATH)/utilities.py
46
All ConfigParser interpolations are done using local values and the
47
environment variables.
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::
56
Progress Markup warnings Warnings Errors
60
0 (default) bar no yes yes
46
64
__docformat__ = 'epytext en'
48
##################################################
50
##################################################
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
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')
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'
68
## This is a more verbose version of LATEX_COMMAND.
69
DEBUG_LATEX_COMMAND = r"echo x | latex %(tex)s"
73
# What tests can we run?
74
TESTS=('basic', 'types', 'vars', 'private', 'authors', 'versions', 'all')
76
##################################################
77
## Command-Line Interface
78
##################################################
79
import sys, os.path, re, getopt
81
# Include support for Zope, if it's available.
87
Command line interface for epydoc.
91
# Parse the command line arguments.
92
options = _parse_args()
94
# Import all the specified modules.
95
modules = _import(options['modules'], options['verbosity'])
97
# Record the order of the modules in options.
98
from epydoc.uid import make_uid
99
options['modules'] = muids = []
102
muids.append(make_uid(m))
104
if sys.stderr.softspace: print >>sys.stderr
105
print >>sys.stderr, 'Failed to create a UID for %s' % m
107
# Build their documentation
108
docmap = _make_docmap(modules, options)
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'])
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' "+
124
print >>sys.stderr, '\n'+'!'*70
125
print >>sys.stderr, estr
126
print >>sys.stderr, '!'*70+'\n'
128
_encountered_internal_error = 0
129
def _internal_error(e=None):
131
Print a warning message about an internal error.
132
@return: The return value from calling C{func}
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
68
from optparse import OptionParser, OptionGroup, SUPPRESS_HELP
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 *
77
from epydoc.docwriter.html_css import STYLESHEETS as CSS_STYLESHEETS
79
# This module is only available if Docutils are in the system
81
from epydoc.docwriter import xlink
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'
91
######################################################################
93
######################################################################
95
DOCFORMATS = ('epytext', 'plaintext', 'restructuredtext', 'javadoc')
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('''\
141
def _usage(exit_code=1):
143
Display a usage message.
145
@param exit_code: An exit status that will be passed to
147
@type exit_code: C{int}
150
if exit_code == 0: stream = sys.stdout
151
else: stream = sys.stderr
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'
161
def _usage_error(estr, exit_code=1):
163
Issue an error message, and exit.
165
#progname = os.path.basename(sys.argv[0])
168
estr = '\n%s\nRun "%s -h" for usage.\n' % (estr.strip(), progname)
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
178
Display a speficied help message, and exit.
180
@param arg: The name of the help message to display. Currently,
181
only C{"css"} and C{"usage"} are recognized.
185
arg = arg.strip().lower()
187
from epydoc.css import STYLESHEETS
188
print '\nThe following built-in CSS stylesheets are available:'
189
names = STYLESHEETS.keys()
191
maxlen = max(*[len(name) for name in names])
192
format = ' %'+`-maxlen-1`+'s %s'
194
print format % (name, STYLESHEETS[name][1])
196
elif arg == '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:'
223
for format in epydoc.objdoc.KNOWN_DOCFORMATS:
224
print ' - %s' % format
232
Display the version information, and exit.
237
print "Epydoc version %s" % epydoc.__version__
240
def _check_css(cssname):
242
If C{cssname} is not valid, then issue an error and exit.
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
249
# We couldn't find it.
250
print >>sys.stderr, '\nError: CSS file %s not found\n' % cssname
255
Process the command line arguments; return a dictionary containing
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}.
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}
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()
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])))
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()]))
127
######################################################################
128
#{ Argument & Config File Parsing
129
######################################################################
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)
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)
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."))
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.")
158
optparser.add_option("--quiet", "-q",
159
action="count", dest="quiet",
160
help="Decrease the verbosity.")
162
optparser.add_option("--verbose", "-v",
163
action="count", dest="verbose",
164
help="Increase the verbosity.")
166
optparser.add_option("--debug",
167
action="store_true", dest="debug",
168
help="Show full tracebacks for internal errors.")
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.")
176
action_group = OptionGroup(optparser, 'Actions')
177
optparser.add_option_group(action_group)
179
action_group.add_option("--html",
180
action="store_const", dest="action", const="html",
181
help="Write HTML output.")
183
action_group.add_option("--text",
184
action="store_const", dest="action", const="text",
185
help="Write plaintext output. (not implemented yet)")
187
action_group.add_option("--latex",
188
action="store_const", dest="action", const="latex",
189
help="Write LaTeX output.")
191
action_group.add_option("--dvi",
192
action="store_const", dest="action", const="dvi",
193
help="Write DVI output.")
195
action_group.add_option("--ps",
196
action="store_const", dest="action", const="ps",
197
help="Write Postscript output.")
199
action_group.add_option("--pdf",
200
action="store_const", dest="action", const="pdf",
201
help="Write PDF output.")
203
action_group.add_option("--check",
204
action="store_const", dest="action", const="check",
205
help="Check completeness of docs.")
207
action_group.add_option("--pickle",
208
action="store_const", dest="action", const="pickle",
209
help="Write the documentation to a pickle file.")
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.")
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")
223
generation_group = OptionGroup(optparser, 'Generation Options')
224
optparser.add_option_group(generation_group)
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)
231
generation_group.add_option("--parse-only",
232
action="store_false", dest="introspect",
233
help="Get all information from parsing (don't introspect)")
235
generation_group.add_option("--introspect-only",
236
action="store_false", dest="parse",
237
help="Get all information from introspecting (don't parse)")
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")
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")
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")
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))
259
generation_group.add_option("--show-private",
260
action="store_true", dest="show_private",
261
help="Include private variables in the output. (default)")
263
generation_group.add_option("--no-private",
264
action="store_false", dest="show_private",
265
help="Do not include private variables in the output.")
267
generation_group.add_option("--show-imports",
268
action="store_true", dest="show_imports",
269
help="List each module's imports.")
271
generation_group.add_option("--no-imports",
272
action="store_false", dest="show_imports",
273
help="Do not list each module's imports. (default)")
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)"))
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 "
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)"))
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."))
295
output_group = OptionGroup(optparser, 'Output Options')
296
optparser.add_option_group(output_group)
298
output_group.add_option("--name", "-n",
299
dest="prj_name", metavar="NAME",
300
help="The documented project's name (for the navigation bar).")
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.")
307
output_group.add_option("--url", "-u",
308
dest="prj_url", metavar="URL",
309
help="The documented project's URL (for the navigation bar).")
311
output_group.add_option("--navlink",
312
dest="prj_link", metavar="HTML",
313
help="HTML code for a navigation link to place in the "
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\"")
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.")
327
output_group.add_option("--show-frames",
328
action="store_true", dest="show_frames",
329
help="Include frames in the HTML output. (default)")
331
output_group.add_option("--no-frames",
332
action="store_false", dest="show_frames",
333
help="Do not include frames in the HTML output.")
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."))
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."))
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)
353
for help, names, opts in xlink.ApiLinkReader.settings_spec[2]:
356
link_group.add_option(*names, **opts)
358
graph_group = OptionGroup(optparser, 'Graph Options')
359
optparser.add_option_group(graph_group)
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)))
370
graph_group.add_option("--dotpath",
371
dest="dotpath", metavar='PATH',
372
help="The path to the Graphviz 'dot' executable.")
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)."))
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, "
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.")
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."))
396
return_group = OptionGroup(optparser, 'Return Value Options')
397
optparser.add_option_group(return_group)
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.")
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 "
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 "
416
# Set the option parser's defaults.
417
optparser.set_defaults(**OPTION_DEFAULTS)
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',):
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()
355
#//////////////////////////////
357
#//////////////////////////////
358
# This section deals with default values that depend on what
359
# action we're performing.
361
# Pick a default target directory, if none was specified.
362
if options['target'] is None:
363
options['target'] = options['action']
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
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'
375
#//////////////////////////////
377
#//////////////////////////////
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'
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
392
# Check that the options all preceed the filenames.
396
_usage_error('options must preceed modules')
398
# Check the CSS file(s)
399
_check_css(options.get('css'))
400
_check_css(options.get('private_css'))
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
410
def _import(module_names, verbosity):
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}
420
from epydoc.imports import import_module, find_modules
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)
429
module_names[index:index+1] = new_modules
431
if sys.stderr.softspace: print >>sys.stderr
432
print >>sys.stderr, 'Error: %r is not a pacakge' % name
435
print >>sys.stderr, 'Importing %s modules.' % len(module_names)
437
progress = _Progress('Importing', verbosity, len(module_names))
439
for name in module_names:
440
progress.report(name)
441
# Import the module, and add it to the list.
443
module = import_module(name)
444
if module not in modules: modules.append(module)
446
if sys.stderr.softspace: print >>sys.stderr
447
print >>sys.stderr, ' (duplicate)'
448
except ImportError, e:
450
if sys.stderr.softspace: print >>sys.stderr
451
print >>sys.stderr, e
453
if len(modules) == 0:
454
print >>sys.stderr, '\nError: no modules successfully loaded!'
458
def _make_docmap(modules, options):
460
Construct the documentation map for the given modules.
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}
467
from epydoc.objdoc import DocMap, report_param_mismatches
469
verbosity = options['verbosity']
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.'
479
progress = _Progress('Building docs for', verbosity, len(modules))
481
for module in modules:
482
progress.report(module.__name__)
483
# Add the module. Catch any exceptions that get generated.
486
if options['debug']: raise
487
else: _internal_error(e)
489
if options['debug']: raise
490
else: _internal_error()
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
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()
510
exitcode = os.system(cmd)
512
raise OSError('%s failed: exitcode=%s' % (name, exitcode))
514
def _latex(docmap, options, format):
516
Create the LaTeX documentation for the objects in the given
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'}.
525
from epydoc.latex import LatexFormatter
527
# Create the documenter, and figure out how many files it will
529
latex_doc = LatexFormatter(docmap, **options)
530
num_files = latex_doc.num_files()
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)
539
# Run latex, makeindex, dvi, ps, and pdf, as appropriate.
420
options, names = optparser.parse_args()
422
# Print help message, if requested. We also provide support for
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'
430
optparser.print_help()
433
# Print version message, if requested.
434
if options.action == 'version':
438
# Process any config files.
439
if options.configfiles:
441
parse_configfiles(options.configfiles, options, names)
442
except (KeyboardInterrupt,SystemExit): raise
444
if len(options.configfiles) == 1:
445
cf_name = 'config file %s' % options.configfiles[0]
447
cf_name = 'config files %s' % ', '.join(options.configfiles)
448
optparser.error('Error reading %s:\n %s' % (cf_name, e))
450
# Check if the input file is a pickle file.
452
if name.endswith('.pickle'):
454
optparser.error("When a pickle file is specified, no other "
455
"input files may be specified.")
456
options.load_pickle = True
458
# Check to make sure all options are valid.
460
optparser.error("No names specified.")
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)
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.")
476
# Check the list of requested graph types to make sure they're
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
489
options.graphs = [g for g in GRAPH_TYPES if g != 'callgraph']
491
elif graph_type not in GRAPH_TYPES:
492
optparser.error("Invalid graph type %s." % graph_type)
494
# Calculate verbosity.
495
verbosity = getattr(options, 'verbosity', 0)
496
options.verbosity = verbosity + options.verbose - options.quiet
498
# The target default depends on the action.
499
if options.target is None:
500
options.target = options.action
502
# Return parsed args.
503
options.names = names
504
return options, names
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)
514
for optname in configparser.options('epydoc'):
515
val = configparser.get('epydoc', optname, vars=os.environ).strip()
516
optname = optname.lower().strip()
518
if optname in ('modules', 'objects', 'values',
519
'module', 'object', 'value'):
520
names.extend(_str_to_list(val))
521
elif optname == 'target':
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)
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)
565
elif optname == 'name':
566
options.prj_name = val
567
elif optname == 'css':
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)
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))
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))
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
619
raise ValueError("%r expected one of: error, warning, "
620
"docstring_warning" % optname)
622
raise ValueError('Unknown option %s' % optname)
624
def _str_to_bool(val, optname):
625
if val.lower() in ('0', 'no', 'false', 'n', 'f', 'hide'):
627
elif val.lower() in ('1', 'yes', 'true', 'y', 't', 'show'):
630
raise ValueError('"%s" option expected a boolean' % optname)
632
def _str_to_int(val, optname):
636
raise ValueError('"%s" option expected an int' % optname)
638
def _str_to_list(val):
639
return val.replace(',', ' ').split()
641
######################################################################
643
######################################################################
645
def main(options, names):
646
# Set the debug flag, if '--debug' was specified.
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
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)
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)
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)
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))
705
log.warning("--include-log requires --html")
707
# Set the default docformat
708
from epydoc import docstringparser
709
docstringparser.DEFAULT_DOCFORMAT = options.docformat
711
# Configure the external API linking
712
if xlink is not None:
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))
721
from epydoc.docwriter import dotgraph
722
dotgraph.DOT_COMMAND = options.dotpath
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
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])
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))
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+
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)
760
if log.ERROR in logger.reported_message_levels:
763
return # docbuilder already logged an error.
765
# Load profile information, if it was given.
766
if options.pstat_files:
769
log.error("Could not import pstats -- ignoring pstat files.")
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
776
log.error("Error reading pstat file: %s" % e)
778
if profile_stats is not None:
779
docindex.read_profiling_info(profile_stats)
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)
793
print >>sys.stderr, '\nUnsupported action %s!' % options.action
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'
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)
805
# Basic timing breakdown:
806
if options.verbosity >= 2 and logger is not None:
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:
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)
822
log.start_progress('Writing HTML docs')
823
html_writer.write(options.target)
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'
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)
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'
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'
857
_RERUN_LATEX_RE = re.compile(r'(?im)^LaTeX\s+Warning:\s+Label\(s\)\s+may'
858
r'\s+have\s+changed.\s+Rerun')
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)
866
# If we're just generating the latex, and not any output format,
868
if format == 'latex': return
870
if format == 'dvi': steps = 4
871
elif format == 'ps': steps = 5
872
elif format == 'pdf': steps = 6
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.
543
# Filenames (used by the external commands)
544
filenames = {'tex': 'api.tex', 'idx': 'api.idx',
545
'dvi': 'api.dvi', 'ps': 'api.ps',
549
if format in ('dvi', 'ps', 'pdf'):
550
# Go into the output directory.
551
os.chdir(options['target'])
553
if options['debug']: latex_command = DEBUG_LATEX_COMMAND
554
else: latex_command = LATEX_COMMAND
556
_run(latex_command % filenames, options)
557
_run(MAKEINDEX_COMMAND % filenames, options)
558
_run(latex_command % filenames, options)
559
_run(latex_command % filenames, options)
879
os.chdir(options.target)
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)
886
# The first pass generates index files.
888
log.progress(0./steps, 'LaTeX: First pass')
889
run_subprocess('latex api.tex')
892
running = 'makeindex'
893
log.progress(1./steps, 'LaTeX: Build index')
894
run_subprocess('makeindex api.idx')
896
# The second pass generates our output.
898
log.progress(2./steps, 'LaTeX: Second pass')
899
out, err = run_subprocess('latex api.tex')
901
# The third pass is only necessary if the second pass
902
# changed what page some things are on.
904
if _RERUN_LATEX_RE.match(out):
905
log.progress(3./steps, 'LaTeX: Third pass')
906
out, err = run_subprocess('latex api.tex')
908
# A fourth path should (almost?) never be necessary.
910
if _RERUN_LATEX_RE.match(out):
911
log.progress(3./steps, 'LaTeX: Fourth pass')
912
run_subprocess('latex api.tex')
914
# If requested, convert to postscript.
562
915
if format in ('ps', 'pdf'):
563
_run(DVIPS_COMMAND % filenames, options)
566
if format in ('pdf',):
567
_run(PS2PDF_COMMAND % filenames, options)
917
log.progress(4./steps, 'dvips')
918
run_subprocess('dvips api.dvi -o api.ps -G0 -Ppdf')
920
# If requested, convert to pdf.
921
if format in ('pdf'):
923
log.progress(5./steps, 'ps2pdf')
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.'
576
if options['debug']: raise
577
else: _internal_error(e)
579
if options['debug']: raise
580
else: _internal_error()
935
log.error("%s failed: %s" % (running, e))
582
937
os.chdir(oldpath)
584
def _html(docmap, options):
586
Create the HTML documentation for the objects in the given
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}
594
from epydoc.html import HTMLFormatter
596
# Create the documenter, and figure out how many files it will
598
html_doc = HTMLFormatter(docmap, **options)
599
num_files = html_doc.num_files()
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)
608
print >>sys.stderr, '\nError writing docs:\n%s\n' % e
610
print >>sys.stderr, '\nError writing docs:\n%s\n' % e
612
if options['debug']: raise
613
else: _internal_error(e)
615
if options['debug']: raise
616
else: _internal_error()
618
def _check(docmap, options):
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:
625
- C{private}: Also checks private objects.
626
- C{vars}: Also checks variables, parameters, and return values.
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}
940
def write_text(docindex, options):
941
log.start_progress('Writing output')
942
from epydoc.docwriter.plaintext import PlaintextWriter
943
plaintext_writer = PlaintextWriter()
945
for apidoc in docindex.root:
946
s += plaintext_writer.write(apidoc)
948
if isinstance(s, unicode):
949
s = s.encode('ascii', 'backslashreplace')
952
def check_docs(docindex, options):
633
953
from epydoc.checker import DocChecker
635
# Run completeness checks.
636
if options['verbosity'] > 0:
637
print >>sys.stderr, 'Performing completeness checks...'
638
checker = DocChecker(docmap)
640
if options['tests'].get('all'):
641
for test in TESTS: options['tests'][test] = 1
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
656
passed_checks = checker.check(checks)
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
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
668
if passed_checks and options['verbosity'] > 0:
669
print >>sys.stderr, ' All checks passed!'
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.
678
To update the progress meter, call C{report} with the name of the
679
object that is about to be processed.
681
def __init__(self, action, verbosity, total_items, html_file=0):
683
Create a new progress meter.
685
@param action: A string indicating what action is performed on
686
each objcet. Examples are C{"writing"} and C{"building
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.
696
self._action = action
954
DocChecker(docindex).check()
957
# Parse command-line arguments.
958
options, names = parse_arguments()
970
except KeyboardInterrupt:
972
print >>sys.stderr, 'Keyboard interrupt.'
974
if options.debug: raise
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.'
986
if PROFILER == 'hotshot':
987
try: import hotshot, hotshot.stats
989
print >>sys.stderr, "Could not import profile module!"
992
prof = hotshot.Profile('hotshot.out')
993
prof = prof.runctx('main(*parse_arguments())', globals(), {})
997
# Convert profile.hotshot -> profile.out
998
print 'Consolidating hotshot profiling info...'
999
hotshot.stats.load('hotshot.out').dump_stats('profile.out')
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
1007
try: from profile import Profile
1009
print >>sys.stderr, "Could not import profile module!"
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
1023
prof = prof.runctx('main(*parse_arguments())', globals(), {})
1026
prof.dump_stats('profile.out')
1029
print >>sys.stderr, 'Unknown profiler %s' % PROFILER
1032
######################################################################
1034
######################################################################
1036
class TerminalController:
1038
A class that can be used to portably generate formatted output to
1040
U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116}
1041
for documentation. (This is a somewhat stripped-down version.)
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 = ''
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()
1061
#: If this is set to true, then new TerminalControllers will
1062
#: assume that the terminal is not capable of doing manipulation
1064
FORCE_SIMPLE_TERM = False
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
1071
# Curses isn't available on all platforms
1074
# If it's not available, then try faking enough to get a
1075
# simple progress bar.
1077
self.CLEAR_LINE = '\r' + ' '*self.COLS + '\r'
1079
# Check the terminal type. If we fail, then assume that the
1080
# terminal has no capabilities.
1081
try: curses.setupterm()
1084
# Look up numeric capabilities.
1085
self.COLS = curses.tigetnum('cols')
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
1095
set_fg = self._tigetstr('setf')
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')
1101
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
1102
setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
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.
1109
cap = curses.tigetstr(cap_name) or ''
1110
return re.sub(r'\$<\d+>[/*]?', '', cap)
1112
class ConsoleLogger(log.Logger):
1113
def __init__(self, verbosity, progress_mode=None):
697
1114
self._verbosity = verbosity
698
self._total_items = total_items
702
def report(self, argument):
704
Update the progress meter.
705
@param argument: The object that is about to be processed.
707
if self._verbosity <= 0: return
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:
719
if self._item_num == self._total_items:
720
if self._total_items <= 70: sys.stderr.write(']')
722
elif self._verbosity>1:
723
TRACE_FORMAT = ((' [%%%dd/%d]' % (len(`self._total_items`),
725
' %s %%s' % self._action)
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)
735
print >>sys.stderr, TRACE_FORMAT % (self._item_num, argument)
1115
self._progress = None
1116
self._message_blocks = []
1118
self._progress_start_time = None
1119
# For per-task times:
1120
self._task_times = []
1121
self._progress_header = None
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."""
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."""
1133
self.term = TerminalController()
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'
1147
self._progress_mode = 'simple-bar'
1148
else: self._progress_mode = 'hide'
1150
def start_block(self, header):
1151
self._message_blocks.append( (header, []) )
1153
def end_block(self):
1154
header, messages = self._message_blocks.pop()
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)+
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:
1166
for message in messages:
1167
if message.endswith('\n'): body += message
1168
else: body += message+'\n'
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)
1175
def _format(self, prefix, message, color):
1177
Rewrap the message; but preserve newlines, and don't touch any
1178
lines that begin with spaces.
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'
1186
width = self.term.COLS - 5 - 4*len(self._message_blocks)
1187
lines[i] = wordwrap(lines[i], indent, width, startindex, '\\/')
1189
return color+prefix+self.term.NORMAL+''.join(lines)
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)
1204
if level >= log.DOCSTRING_WARNING:
1205
self.suppressed_docstring_warning += 1
1208
self._report(message)
1210
def _report(self, message):
1211
if not message.endswith('\n'): message += '\n'
1213
if self._message_blocks:
1214
self._message_blocks[-1][-1].append(message)
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:
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)
1228
# Display the message message.
1229
sys.stdout.write(message)
1232
def progress(self, percent, message=''):
1233
percent = min(1.0, percent)
1234
message = '%s' % message
1236
if self._progress_mode == 'list':
1238
print '[%3d%%] %s' % (100*percent, message)
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)
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)
1257
if len(message) > self.term.COLS-10:
1258
message = message[:self.term.COLS-10-3]+'...'
1260
message = message.center(self.term.COLS-10)
1262
time_elapsed = time.time()-self._progress_start_time
1264
time_remain = (time_elapsed / percent) * (1-percent)
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' +
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' +
1280
self.term.CLEAR_EOL + ' ' + message + self.term.BOL +
1281
self.term.UP + self.term.UP)
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))
1294
self._progress = percent
1296
def _timestr(self, dt):
1299
return '%d:%02d:%02d' % (dt/3600, dt%3600/60, dt%60)
1301
return '%02d:%02d' % (dt/60, dt%60)
1303
def start_progress(self, header=None):
1304
if self._progress is not None:
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
1312
def end_progress(self):
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':
1321
self._progress = None
1322
self._task_times.append( (time.time()-self._progress_start_time,
1323
self._progress_header) )
1325
def print_times(self):
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:
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)
1339
class UnifiedProgressConsoleLogger(ConsoleLogger):
1340
def __init__(self, verbosity, stages, progress_mode=None):
1342
self.stages = stages
1344
ConsoleLogger.__init__(self, verbosity, progress_mode)
1346
def progress(self, percent, message=''):
1347
#p = float(self.stage-1+percent)/self.stages
1349
p = ((sum(self.stages[:i]) + percent*self.stages[i]) /
1350
float(sum(self.stages)))
1352
if message is UNKNOWN: message = None
1353
if message: message = '%s: %s' % (self.task, message)
1354
ConsoleLogger.progress(self, p, message)
1356
def start_progress(self, header=None):
1359
ConsoleLogger.start_progress(self)
1362
def end_progress(self):
1363
if self.stage == len(self.stages):
1364
ConsoleLogger.end_progress(self)
1366
def print_times(self):
1369
class HTMLLogger(log.Logger):
1371
A logger used to generate a log of all warnings and messages to an
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">
1382
<title>Epydoc Log</title>
1383
<link rel="stylesheet" href="epydoc.css" type="text/css" />
1386
<body bgcolor="white" text="black" link="blue" vlink="#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'
1393
END_BLOCK = '</div>'
1394
FOOTER = "</body>\n</html>\n"
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
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> = </tt></td>'
1414
'<td valign="top"><tt>%s</tt></td></tr>' %
1415
(css, key, plaintext_to_html(repr(val))))
1417
self.out.write('<div class="log-info">\n%s</div>\n' % msg)
1418
self.out.write(self.END_BLOCK)
1420
def start_block(self, header):
1421
self.out.write(self.START_BLOCK % header)
1423
def end_block(self):
1424
self.out.write(self.END_BLOCK)
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))
1435
def _message(self, level, message):
1436
self.is_empty = False
1437
message = plaintext_to_html(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)
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)
1454
def _elapsed_time(self):
1455
secs = int(time.time()-self.start_time)
1457
return '%d seconds' % secs
1459
return '%d minutes, %d seconds' % (secs/60, secs%60)
1461
return '%d hours, %d minutes' % (secs/3600, secs%3600)
1464
######################################################################
1466
######################################################################
738
1468
if __name__ == '__main__':
741
profile.run('cli()', '/tmp/profile.out')
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)