mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
[config] correctly synchronize `root_doc
and
master_doc
` (#12417)
This commit is contained in:
parent
d5bdabdd80
commit
581bcdd140
@ -48,6 +48,10 @@ Bugs fixed
|
|||||||
* #12380: LaTeX: Footnote mark sometimes indicates ``Page N`` where ``N`` is
|
* #12380: LaTeX: Footnote mark sometimes indicates ``Page N`` where ``N`` is
|
||||||
the current page number and the footnote does appear on that same page.
|
the current page number and the footnote does appear on that same page.
|
||||||
Patch by Jean-François B.
|
Patch by Jean-François B.
|
||||||
|
* #12416: :confval:`root_doc` is synchronized with :confval:`master_doc`
|
||||||
|
so that if either of the two values is modified, the other reflects that
|
||||||
|
modification. It is still recommended to use :confval:`root_doc`.
|
||||||
|
Patch by Bénédikt Tran.
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
-------
|
-------
|
||||||
|
@ -255,7 +255,7 @@ def convert_epub_css_files(app: Sphinx, config: Config) -> None:
|
|||||||
logger.warning(__('invalid css_file: %r, ignored'), entry)
|
logger.warning(__('invalid css_file: %r, ignored'), entry)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
config.epub_css_files = epub_css_files # type: ignore[attr-defined]
|
config.epub_css_files = epub_css_files
|
||||||
|
|
||||||
|
|
||||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
|
@ -299,9 +299,9 @@ def _gettext_compact_validator(app: Sphinx, config: Config) -> None:
|
|||||||
gettext_compact = config.gettext_compact
|
gettext_compact = config.gettext_compact
|
||||||
# Convert 0/1 from the command line to ``bool`` types
|
# Convert 0/1 from the command line to ``bool`` types
|
||||||
if gettext_compact == '0':
|
if gettext_compact == '0':
|
||||||
config.gettext_compact = False # type: ignore[attr-defined]
|
config.gettext_compact = False
|
||||||
elif gettext_compact == '1':
|
elif gettext_compact == '1':
|
||||||
config.gettext_compact = True # type: ignore[attr-defined]
|
config.gettext_compact = True
|
||||||
|
|
||||||
|
|
||||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
|
@ -1187,7 +1187,7 @@ def convert_html_css_files(app: Sphinx, config: Config) -> None:
|
|||||||
logger.warning(__('invalid css_file: %r, ignored'), entry)
|
logger.warning(__('invalid css_file: %r, ignored'), entry)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
config.html_css_files = html_css_files # type: ignore[attr-defined]
|
config.html_css_files = html_css_files
|
||||||
|
|
||||||
|
|
||||||
def _format_modified_time(timestamp: float) -> str:
|
def _format_modified_time(timestamp: float) -> str:
|
||||||
@ -1210,7 +1210,7 @@ def convert_html_js_files(app: Sphinx, config: Config) -> None:
|
|||||||
logger.warning(__('invalid js_file: %r, ignored'), entry)
|
logger.warning(__('invalid js_file: %r, ignored'), entry)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
config.html_js_files = html_js_files # type: ignore[attr-defined]
|
config.html_js_files = html_js_files
|
||||||
|
|
||||||
|
|
||||||
def setup_resource_paths(app: Sphinx, pagename: str, templatename: str,
|
def setup_resource_paths(app: Sphinx, pagename: str, templatename: str,
|
||||||
@ -1273,7 +1273,7 @@ def validate_html_logo(app: Sphinx, config: Config) -> None:
|
|||||||
not path.isfile(path.join(app.confdir, config.html_logo)) and
|
not path.isfile(path.join(app.confdir, config.html_logo)) and
|
||||||
not isurl(config.html_logo)):
|
not isurl(config.html_logo)):
|
||||||
logger.warning(__('logo file %r does not exist'), config.html_logo)
|
logger.warning(__('logo file %r does not exist'), config.html_logo)
|
||||||
config.html_logo = None # type: ignore[attr-defined]
|
config.html_logo = None
|
||||||
|
|
||||||
|
|
||||||
def validate_html_favicon(app: Sphinx, config: Config) -> None:
|
def validate_html_favicon(app: Sphinx, config: Config) -> None:
|
||||||
@ -1282,7 +1282,7 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None:
|
|||||||
not path.isfile(path.join(app.confdir, config.html_favicon)) and
|
not path.isfile(path.join(app.confdir, config.html_favicon)) and
|
||||||
not isurl(config.html_favicon)):
|
not isurl(config.html_favicon)):
|
||||||
logger.warning(__('favicon file %r does not exist'), config.html_favicon)
|
logger.warning(__('favicon file %r does not exist'), config.html_favicon)
|
||||||
config.html_favicon = None # type: ignore[attr-defined]
|
config.html_favicon = None
|
||||||
|
|
||||||
|
|
||||||
def error_on_html_4(_app: Sphinx, config: Config) -> None:
|
def error_on_html_4(_app: Sphinx, config: Config) -> None:
|
||||||
|
@ -385,6 +385,15 @@ class Config:
|
|||||||
values.append(f"{opt_name}={opt_value!r}")
|
values.append(f"{opt_name}={opt_value!r}")
|
||||||
return self.__class__.__qualname__ + '(' + ', '.join(values) + ')'
|
return self.__class__.__qualname__ + '(' + ', '.join(values) + ')'
|
||||||
|
|
||||||
|
def __setattr__(self, key: str, value: Any) -> None:
|
||||||
|
# if someone is still using 'master_doc', we need to update 'root_doc'
|
||||||
|
if key in ('master_doc', 'root_doc'):
|
||||||
|
super().__setattr__('root_doc', value)
|
||||||
|
super().__setattr__('master_doc', value)
|
||||||
|
return
|
||||||
|
|
||||||
|
super().__setattr__(key, value)
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> Any:
|
def __getattr__(self, name: str) -> Any:
|
||||||
if name in self._options:
|
if name in self._options:
|
||||||
# first check command-line overrides
|
# first check command-line overrides
|
||||||
@ -561,10 +570,10 @@ def convert_source_suffix(app: Sphinx, config: Config) -> None:
|
|||||||
#
|
#
|
||||||
# The default filetype is determined on later step.
|
# The default filetype is determined on later step.
|
||||||
# By default, it is considered as restructuredtext.
|
# By default, it is considered as restructuredtext.
|
||||||
config.source_suffix = {source_suffix: None} # type: ignore[attr-defined]
|
config.source_suffix = {source_suffix: None}
|
||||||
elif isinstance(source_suffix, (list, tuple)):
|
elif isinstance(source_suffix, (list, tuple)):
|
||||||
# if list, considers as all of them are default filetype
|
# if list, considers as all of them are default filetype
|
||||||
config.source_suffix = dict.fromkeys(source_suffix, None) # type: ignore[attr-defined]
|
config.source_suffix = dict.fromkeys(source_suffix, None)
|
||||||
elif not isinstance(source_suffix, dict):
|
elif not isinstance(source_suffix, dict):
|
||||||
logger.warning(__("The config value `source_suffix' expects "
|
logger.warning(__("The config value `source_suffix' expects "
|
||||||
"a string, list of strings, or dictionary. "
|
"a string, list of strings, or dictionary. "
|
||||||
@ -580,8 +589,7 @@ def convert_highlight_options(app: Sphinx, config: Config) -> None:
|
|||||||
options = config.highlight_options
|
options = config.highlight_options
|
||||||
if options and not all(isinstance(v, dict) for v in options.values()):
|
if options and not all(isinstance(v, dict) for v in options.values()):
|
||||||
# old styled option detected because all values are not dictionary.
|
# old styled option detected because all values are not dictionary.
|
||||||
config.highlight_options = {config.highlight_language: # type: ignore[attr-defined]
|
config.highlight_options = {config.highlight_language: options}
|
||||||
options}
|
|
||||||
|
|
||||||
|
|
||||||
def init_numfig_format(app: Sphinx, config: Config) -> None:
|
def init_numfig_format(app: Sphinx, config: Config) -> None:
|
||||||
@ -593,7 +601,7 @@ def init_numfig_format(app: Sphinx, config: Config) -> None:
|
|||||||
|
|
||||||
# override default labels by configuration
|
# override default labels by configuration
|
||||||
numfig_format.update(config.numfig_format)
|
numfig_format.update(config.numfig_format)
|
||||||
config.numfig_format = numfig_format # type: ignore[attr-defined]
|
config.numfig_format = numfig_format
|
||||||
|
|
||||||
|
|
||||||
def correct_copyright_year(_app: Sphinx, config: Config) -> None:
|
def correct_copyright_year(_app: Sphinx, config: Config) -> None:
|
||||||
@ -713,7 +721,7 @@ def check_primary_domain(app: Sphinx, config: Config) -> None:
|
|||||||
primary_domain = config.primary_domain
|
primary_domain = config.primary_domain
|
||||||
if primary_domain and not app.registry.has_domain(primary_domain):
|
if primary_domain and not app.registry.has_domain(primary_domain):
|
||||||
logger.warning(__('primary_domain %r not found, ignored.'), primary_domain)
|
logger.warning(__('primary_domain %r not found, ignored.'), primary_domain)
|
||||||
config.primary_domain = None # type: ignore[attr-defined]
|
config.primary_domain = None
|
||||||
|
|
||||||
|
|
||||||
def check_root_doc(app: Sphinx, env: BuildEnvironment, added: set[str],
|
def check_root_doc(app: Sphinx, env: BuildEnvironment, added: set[str],
|
||||||
@ -726,7 +734,7 @@ def check_root_doc(app: Sphinx, env: BuildEnvironment, added: set[str],
|
|||||||
'contents' in app.project.docnames):
|
'contents' in app.project.docnames):
|
||||||
logger.warning(__('Since v2.0, Sphinx uses "index" as root_doc by default. '
|
logger.warning(__('Since v2.0, Sphinx uses "index" as root_doc by default. '
|
||||||
'Please add "root_doc = \'contents\'" to your conf.py.'))
|
'Please add "root_doc = \'contents\'" to your conf.py.'))
|
||||||
app.config.root_doc = "contents" # type: ignore[attr-defined]
|
app.config.root_doc = "contents"
|
||||||
|
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
@ -2721,10 +2721,10 @@ class AttributeDocumenter(GenericAliasMixin, SlotsMixin, # type: ignore[misc]
|
|||||||
# a docstring from the value which descriptor returns unexpectedly.
|
# a docstring from the value which descriptor returns unexpectedly.
|
||||||
# ref: https://github.com/sphinx-doc/sphinx/issues/7805
|
# ref: https://github.com/sphinx-doc/sphinx/issues/7805
|
||||||
orig = self.config.autodoc_inherit_docstrings
|
orig = self.config.autodoc_inherit_docstrings
|
||||||
self.config.autodoc_inherit_docstrings = False # type: ignore[attr-defined]
|
self.config.autodoc_inherit_docstrings = False
|
||||||
return super().get_doc()
|
return super().get_doc()
|
||||||
finally:
|
finally:
|
||||||
self.config.autodoc_inherit_docstrings = orig # type: ignore[attr-defined]
|
self.config.autodoc_inherit_docstrings = orig
|
||||||
|
|
||||||
def add_content(self, more_content: StringList | None) -> None:
|
def add_content(self, more_content: StringList | None) -> None:
|
||||||
# Disable analyzing attribute comment on Documenter.add_content() to control it on
|
# Disable analyzing attribute comment on Documenter.add_content() to control it on
|
||||||
|
@ -740,9 +740,7 @@ def main(argv: Sequence[str] = (), /) -> None:
|
|||||||
|
|
||||||
if args.templates:
|
if args.templates:
|
||||||
app.config.templates_path.append(path.abspath(args.templates))
|
app.config.templates_path.append(path.abspath(args.templates))
|
||||||
app.config.autosummary_ignore_module_all = ( # type: ignore[attr-defined]
|
app.config.autosummary_ignore_module_all = (not args.respect_module_all)
|
||||||
not args.respect_module_all
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_autosummary_docs(args.source_file, args.output_dir,
|
generate_autosummary_docs(args.source_file, args.output_dir,
|
||||||
'.' + args.suffix,
|
'.' + args.suffix,
|
||||||
|
@ -64,7 +64,7 @@ def publish_msgstr(app: Sphinx, source: str, source_path: str, source_line: int,
|
|||||||
try:
|
try:
|
||||||
# clear rst_prolog temporarily
|
# clear rst_prolog temporarily
|
||||||
rst_prolog = config.rst_prolog
|
rst_prolog = config.rst_prolog
|
||||||
config.rst_prolog = None # type: ignore[attr-defined]
|
config.rst_prolog = None
|
||||||
|
|
||||||
from sphinx.io import SphinxI18nReader
|
from sphinx.io import SphinxI18nReader
|
||||||
reader = SphinxI18nReader()
|
reader = SphinxI18nReader()
|
||||||
@ -81,7 +81,7 @@ def publish_msgstr(app: Sphinx, source: str, source_path: str, source_line: int,
|
|||||||
return doc[0]
|
return doc[0]
|
||||||
return doc
|
return doc
|
||||||
finally:
|
finally:
|
||||||
config.rst_prolog = rst_prolog # type: ignore[attr-defined]
|
config.rst_prolog = rst_prolog
|
||||||
|
|
||||||
|
|
||||||
def parse_noqa(source: str) -> tuple[str, bool]:
|
def parse_noqa(source: str) -> tuple[str, bool]:
|
||||||
|
@ -292,7 +292,7 @@ class AnchorsIgnoreForUrlHandler(BaseHTTPRequestHandler):
|
|||||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-anchors-ignore-for-url', freshenv=True)
|
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-anchors-ignore-for-url', freshenv=True)
|
||||||
def test_anchors_ignored_for_url(app: Sphinx) -> None:
|
def test_anchors_ignored_for_url(app: Sphinx) -> None:
|
||||||
with serve_application(app, AnchorsIgnoreForUrlHandler) as address:
|
with serve_application(app, AnchorsIgnoreForUrlHandler) as address:
|
||||||
app.config.linkcheck_anchors_ignore_for_url = [ # type: ignore[attr-defined]
|
app.config.linkcheck_anchors_ignore_for_url = [
|
||||||
f'http://{address}/ignored', # existing page
|
f'http://{address}/ignored', # existing page
|
||||||
f'http://{address}/invalid', # unknown page
|
f'http://{address}/invalid', # unknown page
|
||||||
]
|
]
|
||||||
@ -402,7 +402,7 @@ def custom_handler(valid_credentials=(), success_criteria=lambda _: True):
|
|||||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
||||||
def test_auth_header_uses_first_match(app: Sphinx) -> None:
|
def test_auth_header_uses_first_match(app: Sphinx) -> None:
|
||||||
with serve_application(app, custom_handler(valid_credentials=("user1", "password"))) as address:
|
with serve_application(app, custom_handler(valid_credentials=("user1", "password"))) as address:
|
||||||
app.config.linkcheck_auth = [ # type: ignore[attr-defined]
|
app.config.linkcheck_auth = [
|
||||||
(r'^$', ('no', 'match')),
|
(r'^$', ('no', 'match')),
|
||||||
(fr'^http://{re.escape(address)}/$', ('user1', 'password')),
|
(fr'^http://{re.escape(address)}/$', ('user1', 'password')),
|
||||||
(r'.*local.*', ('user2', 'hunter2')),
|
(r'.*local.*', ('user2', 'hunter2')),
|
||||||
@ -456,7 +456,7 @@ def test_linkcheck_request_headers(app: Sphinx) -> None:
|
|||||||
return self.headers["Accept"] == "text/html"
|
return self.headers["Accept"] == "text/html"
|
||||||
|
|
||||||
with serve_application(app, custom_handler(success_criteria=check_headers)) as address:
|
with serve_application(app, custom_handler(success_criteria=check_headers)) as address:
|
||||||
app.config.linkcheck_request_headers = { # type: ignore[attr-defined]
|
app.config.linkcheck_request_headers = {
|
||||||
f"http://{address}/": {"Accept": "text/html"},
|
f"http://{address}/": {"Accept": "text/html"},
|
||||||
"*": {"X-Secret": "open sesami"},
|
"*": {"X-Secret": "open sesami"},
|
||||||
}
|
}
|
||||||
@ -476,7 +476,7 @@ def test_linkcheck_request_headers_no_slash(app: Sphinx) -> None:
|
|||||||
return self.headers["Accept"] == "application/json"
|
return self.headers["Accept"] == "application/json"
|
||||||
|
|
||||||
with serve_application(app, custom_handler(success_criteria=check_headers)) as address:
|
with serve_application(app, custom_handler(success_criteria=check_headers)) as address:
|
||||||
app.config.linkcheck_request_headers = { # type: ignore[attr-defined]
|
app.config.linkcheck_request_headers = {
|
||||||
f"http://{address}": {"Accept": "application/json"},
|
f"http://{address}": {"Accept": "application/json"},
|
||||||
"*": {"X-Secret": "open sesami"},
|
"*": {"X-Secret": "open sesami"},
|
||||||
}
|
}
|
||||||
@ -579,7 +579,7 @@ def test_follows_redirects_on_GET(app, capsys, warning):
|
|||||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-warn-redirects')
|
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-warn-redirects')
|
||||||
def test_linkcheck_allowed_redirects(app: Sphinx, warning: StringIO) -> None:
|
def test_linkcheck_allowed_redirects(app: Sphinx, warning: StringIO) -> None:
|
||||||
with serve_application(app, make_redirect_handler(support_head=False)) as address:
|
with serve_application(app, make_redirect_handler(support_head=False)) as address:
|
||||||
app.config.linkcheck_allowed_redirects = {f'http://{address}/.*1': '.*'} # type: ignore[attr-defined]
|
app.config.linkcheck_allowed_redirects = {f'http://{address}/.*1': '.*'}
|
||||||
compile_linkcheck_allowed_redirects(app, app.config)
|
compile_linkcheck_allowed_redirects(app, app.config)
|
||||||
app.build()
|
app.build()
|
||||||
|
|
||||||
|
@ -803,3 +803,19 @@ def test_gettext_compact_command_line_str():
|
|||||||
|
|
||||||
# regression test for #8549 (-D gettext_compact=spam)
|
# regression test for #8549 (-D gettext_compact=spam)
|
||||||
assert config.gettext_compact == 'spam'
|
assert config.gettext_compact == 'spam'
|
||||||
|
|
||||||
|
|
||||||
|
def test_root_doc_and_master_doc_are_synchronized():
|
||||||
|
c = Config()
|
||||||
|
assert c.master_doc == 'index'
|
||||||
|
assert c.root_doc == c.master_doc
|
||||||
|
|
||||||
|
c = Config()
|
||||||
|
c.master_doc = '1234'
|
||||||
|
assert c.master_doc == '1234'
|
||||||
|
assert c.root_doc == c.master_doc
|
||||||
|
|
||||||
|
c = Config()
|
||||||
|
c.root_doc = '1234'
|
||||||
|
assert c.master_doc == '1234'
|
||||||
|
assert c.root_doc == c.master_doc
|
||||||
|
Loading…
Reference in New Issue
Block a user