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
|
||||
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
|
||||
-------
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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]:
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user