summaryrefslogtreecommitdiff
path: root/sphinx/cmd/build.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/cmd/build.py')
-rw-r--r--sphinx/cmd/build.py303
1 files changed, 291 insertions, 12 deletions
diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py
index c0c31ae67..53e93fb99 100644
--- a/sphinx/cmd/build.py
+++ b/sphinx/cmd/build.py
@@ -8,30 +8,309 @@
:copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
+from __future__ import print_function
+import argparse
+import locale
+import multiprocessing
+import os
import sys
+import traceback
+
+from docutils.utils import SystemMessage
+from six import text_type, binary_type
+
+import sphinx.locale
+from sphinx import __display_version__, package_dir
+from sphinx.application import Sphinx
+from sphinx.errors import SphinxError
+from sphinx.locale import __
+from sphinx.util import Tee, format_exception_cut_frames, save_traceback
+from sphinx.util.console import red, nocolor, color_terminal # type: ignore
+from sphinx.util.docutils import docutils_namespace, patch_docutils
+from sphinx.util.pycompat import terminal_safe
if False:
# For type annotation
- from typing import List # NOQA
+ from typing import Any, IO, List, Union # NOQA
-def build_main(argv=sys.argv[1:]):
- # type: (List[str]) -> int
- """Sphinx build "main" command-line entry."""
- from sphinx import cmdline
- return cmdline.main(argv) # type: ignore
+def handle_exception(app, args, exception, stderr=sys.stderr):
+ # type: (Sphinx, Any, Union[Exception, KeyboardInterrupt], IO) -> None
+ if args.pdb:
+ import pdb
+ print(red(__('Exception occurred while building, starting debugger:')),
+ file=stderr)
+ traceback.print_exc()
+ pdb.post_mortem(sys.exc_info()[2])
+ else:
+ print(file=stderr)
+ if args.verbosity or args.traceback:
+ traceback.print_exc(None, stderr)
+ print(file=stderr)
+ if isinstance(exception, KeyboardInterrupt):
+ print(__('interrupted!'), file=stderr)
+ elif isinstance(exception, SystemMessage):
+ print(red(__('reST markup error:')), file=stderr)
+ print(terminal_safe(exception.args[0]), file=stderr)
+ elif isinstance(exception, SphinxError):
+ print(red('%s:' % exception.category), file=stderr)
+ print(terminal_safe(text_type(exception)), file=stderr)
+ elif isinstance(exception, UnicodeError):
+ print(red(__('Encoding error:')), file=stderr)
+ print(terminal_safe(text_type(exception)), file=stderr)
+ tbpath = save_traceback(app)
+ print(red(__('The full traceback has been saved in %s, if you want '
+ 'to report the issue to the developers.') % tbpath),
+ file=stderr)
+ elif isinstance(exception, RuntimeError) and 'recursion depth' in str(exception):
+ print(red(__('Recursion error:')), file=stderr)
+ print(terminal_safe(text_type(exception)), file=stderr)
+ print(file=stderr)
+ print(__('This can happen with very large or deeply nested source '
+ 'files. You can carefully increase the default Python '
+ 'recursion limit of 1000 in conf.py with e.g.:'), file=stderr)
+ print(__(' import sys; sys.setrecursionlimit(1500)'), file=stderr)
+ else:
+ print(red(__('Exception occurred:')), file=stderr)
+ print(format_exception_cut_frames().rstrip(), file=stderr)
+ tbpath = save_traceback(app)
+ print(red(__('The full traceback has been saved in %s, if you '
+ 'want to report the issue to the developers.') % tbpath),
+ file=stderr)
+ print(__('Please also report this if it was a user error, so '
+ 'that a better error message can be provided next time.'),
+ file=stderr)
+ print(__('A bug report can be filed in the tracker at '
+ '<https://github.com/sphinx-doc/sphinx/issues>. Thanks!'),
+ file=stderr)
+
+
+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='%(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'
-def make_main(argv=sys.argv[1:]):
- # type: (List[str]) -> int
+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 make_main(argv=sys.argv[1:]): # type: ignore
+ # type: (List[unicode]) -> int
"""Sphinx build "make mode" entry."""
from sphinx import make_mode
- return make_mode.run_make_mode(argv[1:]) # type: ignore
+ return make_mode.run_make_mode(argv[1:])
+
+
+def build_main(argv=sys.argv[1:]): # type: ignore
+ # type: (List[unicode]) -> int
+ """Sphinx build "main" command-line entry."""
+
+ parser = get_parser()
+ args = parser.parse_args(argv)
+
+ if args.noconfig:
+ args.confdir = None
+ elif not args.confdir:
+ args.confdir = args.sourcedir
+
+ if not args.doctreedir:
+ args.doctreedir = os.path.join(args.sourcedir, '.doctrees')
+
+ # handle remaining filename arguments
+ filenames = args.filenames
+ missing_files = []
+ for filename in filenames:
+ if not os.path.isfile(filename):
+ missing_files.append(filename)
+ if missing_files:
+ parser.error(__('cannot find files %r') % missing_files)
+
+ # likely encoding used for command-line arguments
+ try:
+ locale = __import__('locale') # due to submodule of the same name
+ likely_encoding = locale.getpreferredencoding()
+ except Exception:
+ likely_encoding = None
+
+ if args.force_all and filenames:
+ parser.error(__('cannot combine -a option and filenames'))
+
+ if args.color == 'no' or (args.color == 'auto' and not color_terminal()):
+ nocolor()
+
+ status = sys.stdout
+ warning = sys.stderr
+ error = sys.stderr
+
+ if args.quiet:
+ status = None
+
+ if args.really_quiet:
+ status = warning = None
+
+ if warning and args.warnfile:
+ try:
+ warnfp = open(args.warnfile, 'w')
+ except Exception as exc:
+ parser.error(__('cannot open warning file %r: %s') % (
+ args.warnfile, exc))
+ warning = Tee(warning, warnfp) # type: ignore
+ error = warning
+
+ confoverrides = {}
+ for val in args.define:
+ try:
+ key, val = val.split('=', 1)
+ except ValueError:
+ 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)
+ except UnicodeError:
+ pass
+ confoverrides[key] = val
+
+ for val in args.htmldefine:
+ try:
+ key, val = val.split('=')
+ except ValueError:
+ parser.error(__('-A option argument must be in the form name=value'))
+ try:
+ val = int(val)
+ except ValueError:
+ if likely_encoding and isinstance(val, binary_type):
+ try:
+ val = val.decode(likely_encoding)
+ except UnicodeError:
+ pass
+ confoverrides['html_context.%s' % key] = val
+
+ if args.nitpicky:
+ confoverrides['nitpicky'] = True
+
+ app = None
+ try:
+ confdir = args.confdir or args.sourcedir
+ with patch_docutils(confdir), docutils_namespace():
+ app = Sphinx(args.sourcedir, args.confdir, args.outputdir,
+ args.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, args, exc, error)
+ return 2
+
+def main(argv=sys.argv[1:]): # type: ignore
+ # type: (List[unicode]) -> int
+ locale.setlocale(locale.LC_ALL, '')
+ sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx')
-def main(argv=sys.argv[1:]):
- # type: (List[str]) -> int
if sys.argv[1:2] == ['-M']:
return make_main(argv)
else:
@@ -39,4 +318,4 @@ def main(argv=sys.argv[1:]):
if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
+ sys.exit(main(sys.argv[1:])) # type: ignore