summaryrefslogtreecommitdiff
path: root/sphinx/cmdline.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/cmdline.py')
-rw-r--r--sphinx/cmdline.py324
1 files changed, 161 insertions, 163 deletions
diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py
index c9a0dd4c6..4cf5a07ef 100644
--- a/sphinx/cmdline.py
+++ b/sphinx/cmdline.py
@@ -10,14 +10,14 @@
"""
from __future__ import print_function
+import argparse
+import multiprocessing
import sys
-import optparse
import traceback
from os import path
-from six import text_type, binary_type
-
from docutils.utils import SystemMessage
+from six import text_type, binary_type
from sphinx import __display_version__
from sphinx.errors import SphinxError
@@ -33,39 +33,9 @@ if False:
from typing import Any, IO, List, Union # NOQA
-USAGE = """\
-Sphinx v%s
-Usage: %%prog [options] sourcedir outdir [filenames...]
-
-Filename arguments:
- without -a and without filenames, write new and changed files.
- with -a, write all files.
- with filenames, write these.
-""" % __display_version__
-
-EPILOG = """\
-For more information, visit <http://sphinx-doc.org/>.
-"""
-
-
-class MyFormatter(optparse.IndentedHelpFormatter):
- def format_usage(self, usage):
- # type: (Any) -> Any
- return usage
-
- def format_help(self, formatter):
- # type: (Any) -> unicode
- result = [] # type: List[unicode]
- if self.description: # type: ignore
- result.append(self.format_description(formatter))
- if self.option_list: # type: ignore
- result.append(self.format_option_help(formatter)) # type: ignore
- return "\n".join(result)
-
-
-def handle_exception(app, opts, exception, stderr=sys.stderr):
+def handle_exception(app, args, exception, stderr=sys.stderr):
# type: (Sphinx, Any, Union[Exception, KeyboardInterrupt], IO) -> None
- if opts.pdb:
+ if args.pdb:
import pdb
print(red('Exception occurred while building, starting debugger:'),
file=stderr)
@@ -73,7 +43,7 @@ def handle_exception(app, opts, exception, stderr=sys.stderr):
pdb.post_mortem(sys.exc_info()[2])
else:
print(file=stderr)
- if opts.verbosity or opts.traceback:
+ if args.verbosity or args.traceback:
traceback.print_exc(None, stderr)
print(file=stderr)
if isinstance(exception, KeyboardInterrupt):
@@ -114,119 +84,151 @@ def handle_exception(app, opts, exception, stderr=sys.stderr):
file=stderr)
-def main(argv):
+def jobs_argument(value):
+ # type: (str) -> int
+ """
+ Special type to handle 'auto' flags passed to 'sphinx-build' via -j flag. Can
+ be expanded to handle other special scaling requests, such as setting job count
+ to cpu_count.
+ """
+ if value == 'auto':
+ return multiprocessing.cpu_count()
+ else:
+ jobs = int(value)
+ if jobs <= 0:
+ raise argparse.ArgumentTypeError('job number should be a positive number')
+ else:
+ return jobs
+
+
+def get_parser():
+ # type: () -> argparse.ArgumentParser
+ parser = argparse.ArgumentParser(
+ usage='usage: %(prog)s [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]',
+ epilog='For more information, visit <http://sphinx-doc.org/>.',
+ description="""
+Generate documentation from source files.
+
+sphinx-build generates documentation from the files in SOURCEDIR and places it
+in OUTPUTDIR. It looks for 'conf.py' in SOURCEDIR for the configuration
+settings. The 'sphinx-quickstart' tool may be used to generate template files,
+including 'conf.py'
+
+sphinx-build can create documentation in different formats. A format is
+selected by specifying the builder name on the command line; it defaults to
+HTML. Builders can also perform other tasks related to documentation
+processing.
+
+By default, everything that is outdated is built. Output only for selected
+files can be built by specifying individual filenames.
+""")
+
+ parser.add_argument('--version', action='version', dest='show_version',
+ version='%%(prog)s %s' % __display_version__)
+
+ parser.add_argument('sourcedir',
+ help='path to documentation source files')
+ parser.add_argument('outputdir',
+ help='path to output directory')
+ parser.add_argument('filenames', nargs='*',
+ help='a list of specific files to rebuild. Ignored '
+ 'if -a is specified')
+
+ group = parser.add_argument_group('general options')
+ group.add_argument('-b', metavar='BUILDER', dest='builder',
+ default='html',
+ help='builder to use (default: html)')
+ group.add_argument('-a', action='store_true', dest='force_all',
+ help='write all files (default: only write new and '
+ 'changed files)')
+ group.add_argument('-E', action='store_true', dest='freshenv',
+ help='don\'t use a saved environment, always read '
+ 'all files')
+ group.add_argument('-d', metavar='PATH', dest='doctreedir',
+ help='path for the cached environment and doctree '
+ 'files (default: OUTPUTDIR/.doctrees)')
+ group.add_argument('-j', metavar='N', default=1, type=jobs_argument, dest='jobs',
+ help='build in parallel with N processes where '
+ 'possible (special value "auto" will set N to cpu-count)')
+ group = parser.add_argument_group('build configuration options')
+ group.add_argument('-c', metavar='PATH', dest='confdir',
+ help='path where configuration file (conf.py) is '
+ 'located (default: same as SOURCEDIR)')
+ group.add_argument('-C', action='store_true', dest='noconfig',
+ help='use no config file at all, only -D options')
+ group.add_argument('-D', metavar='setting=value', action='append',
+ dest='define', default=[],
+ help='override a setting in configuration file')
+ group.add_argument('-A', metavar='name=value', action='append',
+ dest='htmldefine', default=[],
+ help='pass a value into HTML templates')
+ group.add_argument('-t', metavar='TAG', action='append',
+ dest='tags', default=[],
+ help='define tag: include "only" blocks with TAG')
+ group.add_argument('-n', action='store_true', dest='nitpicky',
+ help='nit-picky mode, warn about all missing '
+ 'references')
+
+ group = parser.add_argument_group('console output options')
+ group.add_argument('-v', action='count', dest='verbosity', default=0,
+ help='increase verbosity (can be repeated)')
+ group.add_argument('-q', action='store_true', dest='quiet',
+ help='no output on stdout, just warnings on stderr')
+ group.add_argument('-Q', action='store_true', dest='really_quiet',
+ help='no output at all, not even warnings')
+ group.add_argument('--color', action='store_const', const='yes',
+ default='auto',
+ help='do emit colored output (default: auto-detect)')
+ group.add_argument('-N', '--no-color', dest='color', action='store_const',
+ const='no',
+ help='do not emit colored output (default: '
+ 'auto-detect)')
+ group.add_argument('-w', metavar='FILE', dest='warnfile',
+ help='write warnings (and errors) to given file')
+ group.add_argument('-W', action='store_true', dest='warningiserror',
+ help='turn warnings into errors')
+ group.add_argument('-T', action='store_true', dest='traceback',
+ help='show full traceback on exception')
+ group.add_argument('-P', action='store_true', dest='pdb',
+ help='run Pdb on exception')
+
+ return parser
+
+
+def main(argv=sys.argv[1:]): # type: ignore
# type: (List[unicode]) -> int
- parser = optparse.OptionParser(USAGE, epilog=EPILOG, formatter=MyFormatter())
- parser.add_option('--version', action='store_true', dest='version',
- help='show version information and exit')
-
- group = parser.add_option_group('General options')
- group.add_option('-b', metavar='BUILDER', dest='builder', default='html',
- help='builder to use; default is html')
- group.add_option('-a', action='store_true', dest='force_all',
- help='write all files; default is to only write new and '
- 'changed files')
- group.add_option('-E', action='store_true', dest='freshenv',
- help='don\'t use a saved environment, always read '
- 'all files')
- group.add_option('-d', metavar='PATH', default=None, dest='doctreedir',
- help='path for the cached environment and doctree files '
- '(default: outdir/.doctrees)')
- group.add_option('-j', metavar='N', default=1, type='int', dest='jobs',
- help='build in parallel with N processes where possible')
- # this option never gets through to this point (it is intercepted earlier)
- # group.add_option('-M', metavar='BUILDER', dest='make_mode',
- # help='"make" mode -- as used by Makefile, like '
- # '"sphinx-build -M html"')
-
- group = parser.add_option_group('Build configuration options')
- group.add_option('-c', metavar='PATH', dest='confdir',
- help='path where configuration file (conf.py) is located '
- '(default: same as sourcedir)')
- group.add_option('-C', action='store_true', dest='noconfig',
- help='use no config file at all, only -D options')
- group.add_option('-D', metavar='setting=value', action='append',
- dest='define', default=[],
- help='override a setting in configuration file')
- group.add_option('-A', metavar='name=value', action='append',
- dest='htmldefine', default=[],
- help='pass a value into HTML templates')
- group.add_option('-t', metavar='TAG', action='append',
- dest='tags', default=[],
- help='define tag: include "only" blocks with TAG')
- group.add_option('-n', action='store_true', dest='nitpicky',
- help='nit-picky mode, warn about all missing references')
-
- group = parser.add_option_group('Console output options')
- group.add_option('-v', action='count', dest='verbosity', default=0,
- help='increase verbosity (can be repeated)')
- group.add_option('-q', action='store_true', dest='quiet',
- help='no output on stdout, just warnings on stderr')
- group.add_option('-Q', action='store_true', dest='really_quiet',
- help='no output at all, not even warnings')
- group.add_option('--color', dest='color',
- action='store_const', const='yes', default='auto',
- help='Do emit colored output (default: auto-detect)')
- group.add_option('-N', '--no-color', dest='color',
- action='store_const', const='no',
- help='Do not emit colored output (default: auto-detect)')
- group.add_option('-w', metavar='FILE', dest='warnfile',
- help='write warnings (and errors) to given file')
- group.add_option('-W', action='store_true', dest='warningiserror',
- help='turn warnings into errors')
- group.add_option('-T', action='store_true', dest='traceback',
- help='show full traceback on exception')
- group.add_option('-P', action='store_true', dest='pdb',
- help='run Pdb on exception')
-
- # parse options
- try:
- opts, args = parser.parse_args(list(argv[1:]))
- except SystemExit as err:
- return err.code
- # handle basic options
- if opts.version:
- print('Sphinx (sphinx-build) %s' % __display_version__)
- return 0
+ parser = get_parser()
+ args = parser.parse_args(argv)
# get paths (first and second positional argument)
try:
- srcdir = abspath(args[0])
- confdir = abspath(opts.confdir or srcdir)
- if opts.noconfig:
+ srcdir = abspath(args.sourcedir)
+ confdir = abspath(args.confdir or srcdir)
+ if args.noconfig:
confdir = None
+
if not path.isdir(srcdir):
- print('Error: Cannot find source directory `%s\'.' % srcdir,
- file=sys.stderr)
- return 1
- if not opts.noconfig and not path.isfile(path.join(confdir, 'conf.py')):
- print('Error: Config directory doesn\'t contain a conf.py file.',
- file=sys.stderr)
- return 1
- outdir = abspath(args[1])
+ parser.error('cannot find source directory (%s)' % srcdir)
+ if not args.noconfig and not path.isfile(path.join(confdir, 'conf.py')):
+ parser.error("config directory doesn't contain a conf.py file "
+ "(%s)" % confdir)
+
+ outdir = abspath(args.outputdir)
if srcdir == outdir:
- print('Error: source directory and destination directory are same.',
- file=sys.stderr)
- return 1
- except IndexError:
- parser.print_help()
- return 1
+ parser.error('source directory and destination directory are same')
except UnicodeError:
- print(
- 'Error: Multibyte filename not supported on this filesystem '
- 'encoding (%r).' % fs_encoding, file=sys.stderr)
- return 1
+ parser.error('multibyte filename not supported on this filesystem '
+ 'encoding (%r)' % fs_encoding)
# handle remaining filename arguments
- filenames = args[2:]
- errored = False
+ filenames = args.filenames
+ missing_files = []
for filename in filenames:
if not path.isfile(filename):
- print('Error: Cannot find file %r.' % filename, file=sys.stderr)
- errored = True
- if errored:
- return 1
+ missing_files.append(filename)
+ if missing_files:
+ parser.error('cannot find files %r' % missing_files)
# likely encoding used for command-line arguments
try:
@@ -235,41 +237,39 @@ def main(argv):
except Exception:
likely_encoding = None
- if opts.force_all and filenames:
- print('Error: Cannot combine -a option and filenames.', file=sys.stderr)
- return 1
+ if args.force_all and filenames:
+ parser.error('cannot combine -a option and filenames')
- if opts.color == 'no' or (opts.color == 'auto' and not color_terminal()):
+ if args.color == 'no' or (args.color == 'auto' and not color_terminal()):
nocolor()
- doctreedir = abspath(opts.doctreedir or path.join(outdir, '.doctrees'))
+ doctreedir = abspath(args.doctreedir or path.join(outdir, '.doctrees'))
status = sys.stdout
warning = sys.stderr
error = sys.stderr
- if opts.quiet:
+ if args.quiet:
status = None
- if opts.really_quiet:
+
+ if args.really_quiet:
status = warning = None
- if warning and opts.warnfile:
+
+ if warning and args.warnfile:
try:
- warnfp = open(opts.warnfile, 'w')
+ warnfp = open(args.warnfile, 'w')
except Exception as exc:
- print('Error: Cannot open warning file %r: %s' %
- (opts.warnfile, exc), file=sys.stderr)
- sys.exit(1)
+ parser.error('cannot open warning file %r: %s' % (
+ args.warnfile, exc))
warning = Tee(warning, warnfp) # type: ignore
error = warning
confoverrides = {}
- for val in opts.define:
+ for val in args.define:
try:
key, val = val.split('=', 1)
except ValueError:
- print('Error: -D option argument must be in the form name=value.',
- file=sys.stderr)
- return 1
+ parser.error('-D option argument must be in the form name=value')
if likely_encoding and isinstance(val, binary_type):
try:
val = val.decode(likely_encoding)
@@ -277,13 +277,11 @@ def main(argv):
pass
confoverrides[key] = val
- for val in opts.htmldefine:
+ for val in args.htmldefine:
try:
key, val = val.split('=')
except ValueError:
- print('Error: -A option argument must be in the form name=value.',
- file=sys.stderr)
- return 1
+ parser.error('-A option argument must be in the form name=value')
try:
val = int(val)
except ValueError:
@@ -294,17 +292,17 @@ def main(argv):
pass
confoverrides['html_context.%s' % key] = val
- if opts.nitpicky:
+ if args.nitpicky:
confoverrides['nitpicky'] = True
app = None
try:
with patch_docutils(), docutils_namespace():
- app = Sphinx(srcdir, confdir, outdir, doctreedir, opts.builder,
- confoverrides, status, warning, opts.freshenv,
- opts.warningiserror, opts.tags, opts.verbosity, opts.jobs)
- app.build(opts.force_all, filenames)
+ app = Sphinx(srcdir, confdir, outdir, doctreedir, args.builder,
+ confoverrides, status, warning, args.freshenv,
+ args.warningiserror, args.tags, args.verbosity, args.jobs)
+ app.build(args.force_all, filenames)
return app.statuscode
except (Exception, KeyboardInterrupt) as exc:
- handle_exception(app, opts, exc, error)
- return 1
+ handle_exception(app, args, exc, error)
+ return 2