summaryrefslogtreecommitdiffstats
path: root/sphinx/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/cmd')
-rw-r--r--sphinx/cmd/build.py195
-rw-r--r--sphinx/cmd/make_mode.py13
-rw-r--r--sphinx/cmd/quickstart.py51
3 files changed, 147 insertions, 112 deletions
diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py
index 3ee0ceb..02fd99a 100644
--- a/sphinx/cmd/build.py
+++ b/sphinx/cmd/build.py
@@ -21,19 +21,19 @@ from sphinx import __display_version__
from sphinx.application import Sphinx
from sphinx.errors import SphinxError, SphinxParallelError
from sphinx.locale import __
-from sphinx.util import Tee
-from sphinx.util.console import ( # type: ignore[attr-defined]
- color_terminal,
- nocolor,
- red,
- terminal_safe,
-)
+from sphinx.util._io import TeeStripANSI
+from sphinx.util.console import color_terminal, nocolor, red, terminal_safe
from sphinx.util.docutils import docutils_namespace, patch_docutils
from sphinx.util.exceptions import format_exception_cut_frames, save_traceback
from sphinx.util.osutil import ensuredir
if TYPE_CHECKING:
from collections.abc import Sequence
+ from typing import Protocol
+
+ class SupportsWrite(Protocol):
+ def write(self, text: str, /) -> int | None:
+ ...
def handle_exception(
@@ -133,75 +133,79 @@ files can be built by specifying individual filenames.
"""))
parser.add_argument('--version', action='version', dest='show_version',
- version='%%(prog)s %s' % __display_version__)
+ version=f'%(prog)s {__display_version__}')
- parser.add_argument('sourcedir',
+ parser.add_argument('sourcedir', metavar='SOURCE_DIR',
help=__('path to documentation source files'))
- parser.add_argument('outputdir',
+ parser.add_argument('outputdir', metavar='OUTPUT_DIR',
help=__('path to output directory'))
parser.add_argument('filenames', nargs='*',
- help=__('a list of specific files to rebuild. Ignored '
- 'if -a is specified'))
+ help=__('(optional) a list of specific files to rebuild. '
+ 'Ignored if --write-all is specified'))
group = parser.add_argument_group(__('general options'))
- group.add_argument('-b', metavar='BUILDER', dest='builder',
+ group.add_argument('--builder', '-b', metavar='BUILDER', dest='builder',
default='html',
- help=__('builder to use (default: html)'))
- group.add_argument('-a', action='store_true', dest='force_all',
+ help=__("builder to use (default: 'html')"))
+ group.add_argument('--jobs', '-j', metavar='N', default=1, type=jobs_argument,
+ dest='jobs',
+ help=__('run in parallel with N processes, when possible. '
+ "'auto' uses the number of CPU cores"))
+ group.add_argument('--write-all', '-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',
+ group.add_argument('--fresh-env', '-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', '--jobs', 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(__('path options'))
+ group.add_argument('--doctree-dir', '-d', metavar='PATH', dest='doctreedir',
+ help=__('directory for doctree and environment files '
+ '(default: OUTPUT_DIR/.doctrees)'))
+ group.add_argument('--conf-dir', '-c', metavar='PATH', dest='confdir',
+ help=__('directory for the configuration file (conf.py) '
+ '(default: SOURCE_DIR)'))
+
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',
+ group.add_argument('--isolated', '-C', action='store_true', dest='noconfig',
+ help=__('use no configuration file, only use settings from -D options'))
+ group.add_argument('--define', '-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',
+ group.add_argument('--html-define', '-A', metavar='name=value', action='append',
dest='htmldefine', default=[],
help=__('pass a value into HTML templates'))
- group.add_argument('-t', metavar='TAG', action='append',
+ group.add_argument('--tag', '-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.add_argument('--nitpicky', '-n', action='store_true', dest='nitpicky',
+ help=__('nitpicky mode: warn about all missing references'))
group = parser.add_argument_group(__('console output options'))
- group.add_argument('-v', action='count', dest='verbosity', default=0,
+ group.add_argument('--verbose', '-v', action='count', dest='verbosity',
+ default=0,
help=__('increase verbosity (can be repeated)'))
- group.add_argument('-q', action='store_true', dest='quiet',
+ group.add_argument('--quiet', '-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',
+ group.add_argument('--silent', '-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',
+ group.add_argument('--color', action='store_const', dest='color',
+ const='yes', default='auto',
help=__('do emit colored output (default: auto-detect)'))
- group.add_argument('-N', '--no-color', dest='color', action='store_const',
+ group.add_argument('--no-color', '-N', action='store_const', dest='color',
const='no',
- help=__('do not emit colored output (default: '
- 'auto-detect)'))
- group.add_argument('-w', metavar='FILE', dest='warnfile',
+ help=__('do not emit colored output (default: auto-detect)'))
+
+ group = parser.add_argument_group(__('warning control options'))
+ group.add_argument('--warning-file', '-w', metavar='FILE', dest='warnfile',
help=__('write warnings (and errors) to given file'))
- group.add_argument('-W', action='store_true', dest='warningiserror',
+ group.add_argument('--fail-on-warning', '-W', action='store_true', dest='warningiserror',
help=__('turn warnings into errors'))
group.add_argument('--keep-going', action='store_true', dest='keep_going',
- help=__("with -W, keep going when getting warnings"))
- group.add_argument('-T', action='store_true', dest='traceback',
+ help=__("with --fail-on-warning, keep going when getting warnings"))
+ group.add_argument('--show-traceback', '-T', action='store_true', dest='traceback',
help=__('show full traceback on exception'))
- group.add_argument('-P', action='store_true', dest='pdb',
+ group.add_argument('--pdb', '-P', action='store_true', dest='pdb',
help=__('run Pdb on exception'))
return parser
@@ -213,58 +217,86 @@ def make_main(argv: Sequence[str]) -> int:
return make_mode.run_make_mode(argv[1:])
-def _parse_arguments(argv: Sequence[str]) -> argparse.Namespace:
- parser = get_parser()
+def _parse_arguments(parser: argparse.ArgumentParser,
+ argv: Sequence[str]) -> argparse.Namespace:
args = parser.parse_args(argv)
+ return args
+
- if args.noconfig:
- args.confdir = None
- elif not args.confdir:
- args.confdir = args.sourcedir
+def _parse_confdir(noconfig: bool, confdir: str, sourcedir: str) -> str | None:
+ if noconfig:
+ return None
+ elif not confdir:
+ return sourcedir
+ return confdir
- if not args.doctreedir:
- args.doctreedir = os.path.join(args.outputdir, '.doctrees')
- if args.force_all and args.filenames:
+def _parse_doctreedir(doctreedir: str, outputdir: str) -> str:
+ if doctreedir:
+ return doctreedir
+ return os.path.join(outputdir, '.doctrees')
+
+
+def _validate_filenames(
+ parser: argparse.ArgumentParser, force_all: bool, filenames: list[str],
+) -> None:
+ if force_all and filenames:
parser.error(__('cannot combine -a option and filenames'))
- if args.color == 'no' or (args.color == 'auto' and not color_terminal()):
+
+def _validate_colour_support(colour: str) -> None:
+ if colour == 'no' or (colour == 'auto' and not color_terminal()):
nocolor()
+
+def _parse_logging(
+ parser: argparse.ArgumentParser,
+ quiet: bool,
+ really_quiet: bool,
+ warnfile: str | None,
+) -> tuple[TextIO | None, TextIO | None, TextIO, TextIO | None]:
status: TextIO | None = sys.stdout
warning: TextIO | None = sys.stderr
error = sys.stderr
- if args.quiet:
+ if quiet:
status = None
- if args.really_quiet:
+ if really_quiet:
status = warning = None
- if warning and args.warnfile:
+ warnfp = None
+ if warning and warnfile:
try:
- warnfile = path.abspath(args.warnfile)
+ warnfile = path.abspath(warnfile)
ensuredir(path.dirname(warnfile))
- warnfp = open(args.warnfile, 'w', encoding="utf-8") # NoQA: SIM115
+ # the caller is responsible for closing this file descriptor
+ warnfp = open(warnfile, 'w', encoding="utf-8") # NoQA: SIM115
except Exception as exc:
parser.error(__('cannot open warning file %r: %s') % (
- args.warnfile, exc))
- warning = Tee(warning, warnfp) # type: ignore[assignment]
+ warnfile, exc))
+ warning = TeeStripANSI(warning, warnfp) # type: ignore[assignment]
error = warning
- args.status = status
- args.warning = warning
- args.error = error
+ return status, warning, error, warnfp
- confoverrides = {}
- for val in args.define:
+
+def _parse_confoverrides(
+ parser: argparse.ArgumentParser,
+ define: list[str],
+ htmldefine: list[str],
+ nitpicky: bool,
+) -> dict[str, Any]:
+ confoverrides: dict[str, Any] = {}
+ val: Any
+ for val in define:
try:
key, val = val.split('=', 1)
except ValueError:
parser.error(__('-D option argument must be in the form name=value'))
confoverrides[key] = val
- for val in args.htmldefine:
+ for val in htmldefine:
try:
key, val = val.split('=')
except ValueError:
@@ -272,19 +304,26 @@ def _parse_arguments(argv: Sequence[str]) -> argparse.Namespace:
with contextlib.suppress(ValueError):
val = int(val)
- confoverrides['html_context.%s' % key] = val
+ confoverrides[f'html_context.{key}'] = val
- if args.nitpicky:
+ if nitpicky:
confoverrides['nitpicky'] = True
- args.confoverrides = confoverrides
-
- return args
+ return confoverrides
def build_main(argv: Sequence[str]) -> int:
"""Sphinx build "main" command-line entry."""
- args = _parse_arguments(argv)
+ parser = get_parser()
+ args = _parse_arguments(parser, argv)
+ args.confdir = _parse_confdir(args.noconfig, args.confdir, args.sourcedir)
+ args.doctreedir = _parse_doctreedir(args.doctreedir, args.outputdir)
+ _validate_filenames(parser, args.force_all, args.filenames)
+ _validate_colour_support(args.color)
+ args.status, args.warning, args.error, warnfp = _parse_logging(
+ parser, args.quiet, args.really_quiet, args.warnfile)
+ args.confoverrides = _parse_confoverrides(
+ parser, args.define, args.htmldefine, args.nitpicky)
app = None
try:
@@ -300,6 +339,10 @@ def build_main(argv: Sequence[str]) -> int:
except (Exception, KeyboardInterrupt) as exc:
handle_exception(app, args, exc, args.error)
return 2
+ finally:
+ if warnfp is not None:
+ # close the file descriptor for the warnings file opened by Sphinx
+ warnfp.close()
def _bug_report_info() -> int:
diff --git a/sphinx/cmd/make_mode.py b/sphinx/cmd/make_mode.py
index 8b26d9d..0192946 100644
--- a/sphinx/cmd/make_mode.py
+++ b/sphinx/cmd/make_mode.py
@@ -17,17 +17,12 @@ from typing import TYPE_CHECKING
import sphinx
from sphinx.cmd.build import build_main
-from sphinx.util.console import ( # type: ignore[attr-defined]
- blue,
- bold,
- color_terminal,
- nocolor,
-)
+from sphinx.util.console import blue, bold, color_terminal, nocolor
from sphinx.util.osutil import rmtree
-try:
- from contextlib import chdir # type: ignore[attr-defined]
-except ImportError:
+if sys.version_info >= (3, 11):
+ from contextlib import chdir
+else:
from sphinx.util.osutil import _chdir as chdir
if TYPE_CHECKING:
diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py
index 89aec45..b75e0fa 100644
--- a/sphinx/cmd/quickstart.py
+++ b/sphinx/cmd/quickstart.py
@@ -31,13 +31,7 @@ from docutils.utils import column_width
import sphinx.locale
from sphinx import __display_version__, package_dir
from sphinx.locale import __
-from sphinx.util.console import ( # type: ignore[attr-defined]
- bold,
- color_terminal,
- colorize,
- nocolor,
- red,
-)
+from sphinx.util.console import bold, color_terminal, colorize, nocolor, red
from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxRenderer
@@ -211,7 +205,6 @@ def ask_user(d: dict[str, Any]) -> None:
* makefile: make Makefile
* batchfile: make command file
"""
-
print(bold(__('Welcome to the Sphinx %s quickstart utility.')) % __display_version__)
print()
print(__('Please enter values for the following settings (just press Enter to\n'
@@ -246,9 +239,9 @@ def ask_user(d: dict[str, Any]) -> None:
if 'dot' not in d:
print()
- print(__('Inside the root directory, two more directories will be created; "_templates"\n' # noqa: E501
- 'for custom HTML templates and "_static" for custom stylesheets and other static\n' # noqa: E501
- 'files. You can enter another prefix (such as ".") to replace the underscore.')) # noqa: E501
+ print(__('Inside the root directory, two more directories will be created; "_templates"\n' # NoQA: E501
+ 'for custom HTML templates and "_static" for custom stylesheets and other static\n' # NoQA: E501
+ 'files. You can enter another prefix (such as ".") to replace the underscore.')) # NoQA: E501
d['dot'] = do_prompt(__('Name prefix for templates and static dir'), '_', ok)
if 'project' not in d:
@@ -379,29 +372,32 @@ def generate(
if 'quiet' not in d:
print(__('File %s already exists, skipping.') % fpath)
- conf_path = os.path.join(templatedir, 'conf.py_t') if templatedir else None
+ conf_path = os.path.join(templatedir, 'conf.py.jinja') if templatedir else None
if not conf_path or not path.isfile(conf_path):
- conf_path = os.path.join(package_dir, 'templates', 'quickstart', 'conf.py_t')
+ conf_path = os.path.join(package_dir, 'templates', 'quickstart', 'conf.py.jinja')
with open(conf_path, encoding="utf-8") as f:
conf_text = f.read()
write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d))
masterfile = path.join(srcdir, d['master'] + d['suffix'])
- if template._has_custom_template('quickstart/master_doc.rst_t'):
- msg = ('A custom template `master_doc.rst_t` found. It has been renamed to '
- '`root_doc.rst_t`. Please rename it on your project too.')
+ if template._has_custom_template('quickstart/master_doc.rst.jinja'):
+ msg = ('A custom template `master_doc.rst.jinja` found. It has been renamed to '
+ '`root_doc.rst.jinja`. Please rename it on your project too.')
print(colorize('red', msg))
- write_file(masterfile, template.render('quickstart/master_doc.rst_t', d))
+ write_file(masterfile, template.render('quickstart/master_doc.rst.jinja', d))
else:
- write_file(masterfile, template.render('quickstart/root_doc.rst_t', d))
+ write_file(masterfile, template.render('quickstart/root_doc.rst.jinja', d))
- if d.get('make_mode') is True:
- makefile_template = 'quickstart/Makefile.new_t'
- batchfile_template = 'quickstart/make.bat.new_t'
+ if d.get('make_mode'):
+ makefile_template = 'quickstart/Makefile.new.jinja'
+ batchfile_template = 'quickstart/make.bat.new.jinja'
else:
- makefile_template = 'quickstart/Makefile_t'
- batchfile_template = 'quickstart/make.bat_t'
+ # xref RemovedInSphinx80Warning
+ msg = "Support for '--no-use-make-mode' will be removed in Sphinx 8."
+ print(colorize('red', msg))
+ makefile_template = 'quickstart/Makefile.jinja'
+ batchfile_template = 'quickstart/make.bat.jinja'
if d['makefile'] is True:
d['rsrcdir'] = 'source' if d['sep'] else '.'
@@ -432,6 +428,10 @@ def generate(
print(__('where "builder" is one of the supported builders, '
'e.g. html, latex or linkcheck.'))
print()
+ if not d.get('make_mode'):
+ # xref RemovedInSphinx80Warning
+ msg = "Support for '--no-use-make-mode' will be removed in Sphinx 8."
+ print(colorize('red', msg))
def valid_dir(d: dict) -> bool:
@@ -457,10 +457,7 @@ def valid_dir(d: dict) -> bool:
d['dot'] + 'templates',
d['master'] + d['suffix'],
]
- if set(reserved_names) & set(os.listdir(dir)):
- return False
-
- return True
+ return not set(reserved_names) & set(os.listdir(dir))
def get_parser() -> argparse.ArgumentParser: