[config] correctly synchronize `root_doc and master_doc` (#12417)

This commit is contained in:
Bénédikt Tran 2024-06-17 18:04:33 +02:00 committed by GitHub
parent d5bdabdd80
commit 581bcdd140
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 52 additions and 26 deletions

View File

@ -48,6 +48,10 @@ Bugs fixed
* #12380: LaTeX: Footnote mark sometimes indicates ``Page N`` where ``N`` is
the current page number and the footnote does appear on that same page.
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
-------

View File

@ -255,7 +255,7 @@ def convert_epub_css_files(app: Sphinx, config: Config) -> None:
logger.warning(__('invalid css_file: %r, ignored'), entry)
continue
config.epub_css_files = epub_css_files # type: ignore[attr-defined]
config.epub_css_files = epub_css_files
def setup(app: Sphinx) -> ExtensionMetadata:

View File

@ -299,9 +299,9 @@ def _gettext_compact_validator(app: Sphinx, config: Config) -> None:
gettext_compact = config.gettext_compact
# Convert 0/1 from the command line to ``bool`` types
if gettext_compact == '0':
config.gettext_compact = False # type: ignore[attr-defined]
config.gettext_compact = False
elif gettext_compact == '1':
config.gettext_compact = True # type: ignore[attr-defined]
config.gettext_compact = True
def setup(app: Sphinx) -> ExtensionMetadata:

View File

@ -1187,7 +1187,7 @@ def convert_html_css_files(app: Sphinx, config: Config) -> None:
logger.warning(__('invalid css_file: %r, ignored'), entry)
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:
@ -1210,7 +1210,7 @@ def convert_html_js_files(app: Sphinx, config: Config) -> None:
logger.warning(__('invalid js_file: %r, ignored'), entry)
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,
@ -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 isurl(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:
@ -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 isurl(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:

View File

@ -385,6 +385,15 @@ class Config:
values.append(f"{opt_name}={opt_value!r}")
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:
if name in self._options:
# 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.
# 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)):
# 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):
logger.warning(__("The config value `source_suffix' expects "
"a string, list of strings, or dictionary. "
@ -580,8 +589,7 @@ def convert_highlight_options(app: Sphinx, config: Config) -> None:
options = config.highlight_options
if options and not all(isinstance(v, dict) for v in options.values()):
# old styled option detected because all values are not dictionary.
config.highlight_options = {config.highlight_language: # type: ignore[attr-defined]
options}
config.highlight_options = {config.highlight_language: options}
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
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:
@ -713,7 +721,7 @@ def check_primary_domain(app: Sphinx, config: Config) -> None:
primary_domain = config.primary_domain
if primary_domain and not app.registry.has_domain(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],
@ -726,7 +734,7 @@ def check_root_doc(app: Sphinx, env: BuildEnvironment, added: set[str],
'contents' in app.project.docnames):
logger.warning(__('Since v2.0, Sphinx uses "index" as root_doc by default. '
'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

View File

@ -2721,10 +2721,10 @@ class AttributeDocumenter(GenericAliasMixin, SlotsMixin, # type: ignore[misc]
# a docstring from the value which descriptor returns unexpectedly.
# ref: https://github.com/sphinx-doc/sphinx/issues/7805
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()
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:
# Disable analyzing attribute comment on Documenter.add_content() to control it on

View File

@ -740,9 +740,7 @@ def main(argv: Sequence[str] = (), /) -> None:
if args.templates:
app.config.templates_path.append(path.abspath(args.templates))
app.config.autosummary_ignore_module_all = ( # type: ignore[attr-defined]
not args.respect_module_all
)
app.config.autosummary_ignore_module_all = (not args.respect_module_all)
generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix,

View File

@ -64,7 +64,7 @@ def publish_msgstr(app: Sphinx, source: str, source_path: str, source_line: int,
try:
# clear rst_prolog temporarily
rst_prolog = config.rst_prolog
config.rst_prolog = None # type: ignore[attr-defined]
config.rst_prolog = None
from sphinx.io import 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
finally:
config.rst_prolog = rst_prolog # type: ignore[attr-defined]
config.rst_prolog = rst_prolog
def parse_noqa(source: str) -> tuple[str, bool]:

View File

@ -292,7 +292,7 @@ class AnchorsIgnoreForUrlHandler(BaseHTTPRequestHandler):
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-anchors-ignore-for-url', freshenv=True)
def test_anchors_ignored_for_url(app: Sphinx) -> None:
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}/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)
def test_auth_header_uses_first_match(app: Sphinx) -> None:
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')),
(fr'^http://{re.escape(address)}/$', ('user1', 'password')),
(r'.*local.*', ('user2', 'hunter2')),
@ -456,7 +456,7 @@ def test_linkcheck_request_headers(app: Sphinx) -> None:
return self.headers["Accept"] == "text/html"
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"},
"*": {"X-Secret": "open sesami"},
}
@ -476,7 +476,7 @@ def test_linkcheck_request_headers_no_slash(app: Sphinx) -> None:
return self.headers["Accept"] == "application/json"
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"},
"*": {"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')
def test_linkcheck_allowed_redirects(app: Sphinx, warning: StringIO) -> None:
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)
app.build()

View File

@ -803,3 +803,19 @@ def test_gettext_compact_command_line_str():
# regression test for #8549 (-D 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