From bc658c437e2ce155ce2fb81bbbbcf172ed0aaa4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Sun, 7 Jun 2026 22:49:37 +0200 Subject: [PATCH 1/4] Clear option index for document on rebuild. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the clear_doc() method to fix warnings about duplicate object definitions when using incremental builds. The data attribute is cached in the build environment, thus the duplication warning triggers on each reprocessed document. But clear_doc() is called for each document being re-read, so it should simply remove the matching previous entries. Not important for sections, these amy simply linger after a partial rebuild if removed. No biggie. Signed-off-by: André Colomb --- _ext/syncthing_config.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/_ext/syncthing_config.py b/_ext/syncthing_config.py index 745278c7b..6086f2f90 100644 --- a/_ext/syncthing_config.py +++ b/_ext/syncthing_config.py @@ -168,6 +168,13 @@ def add_config_option(self, signature, section, option, anchor, location=None): docname=self.env.docname, anchor=anchor, priority=0) + def clear_doc(self, docname: str): + self.data['options'] = { + signature: entry + for signature, entry in self.config_options.items() + if entry.docname != docname + } + def setup(app): """Install the plugin. From f80f787c0d69f81885a5bb06861d220073ab6957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Sun, 7 Jun 2026 22:54:50 +0200 Subject: [PATCH 2/4] Fix initialization type of initial sections data. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As declared, the default value should be a set, while the initial_data template contained an empty list. Signed-off-by: André Colomb --- _ext/syncthing_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_ext/syncthing_config.py b/_ext/syncthing_config.py index 6086f2f90..67422450b 100644 --- a/_ext/syncthing_config.py +++ b/_ext/syncthing_config.py @@ -117,7 +117,7 @@ class SyncthingConfigDomain(Domain): @property def config_sections(self) -> Set[str]: - return self.data.setdefault('sections', []) + return self.data.setdefault('sections', set()) @property def config_options(self) -> Dict[str, Tuple]: From e23e1ab7926031de103931d3a1f87f0173905d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Sun, 7 Jun 2026 22:56:04 +0200 Subject: [PATCH 3/4] Implement merge_domaindata() method for safe parallel builds. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sphinx may process different documents concurrently in several runners. The resulting domain data (signatures and corresponding object entries) must be merged by the Domain code. Declare this extension safe for parallel processing with the method now implemented. Signed-off-by: André Colomb --- _ext/syncthing_config.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/_ext/syncthing_config.py b/_ext/syncthing_config.py index 67422450b..62844c62f 100644 --- a/_ext/syncthing_config.py +++ b/_ext/syncthing_config.py @@ -4,7 +4,7 @@ Modeled after the standard :directive:`cmdoption` directive. """ -from typing import Tuple, Dict, Iterator, Set, NamedTuple +from typing import Any, Tuple, Dict, Iterator, Set, NamedTuple from docutils.nodes import Element from docutils.parsers.rst import directives @@ -175,6 +175,19 @@ def clear_doc(self, docname: str): if entry.docname != docname } + def merge_domaindata(self, docnames: Set[str], otherdata: Dict[str, Any]): + self.config_sections.update(otherdata.get('sections', set())) + + for signature, entry in otherdata.get('options', {}).items(): + if entry.docname in docnames: + if signature in self.config_options: + other = self.config_options[signature] + logger.warning( + 'Duplicate object description of %s, other instance in %s', + entry.name, other.docname + ) + self.config_options[signature] = entry + def setup(app): """Install the plugin. @@ -182,4 +195,7 @@ def setup(app): :param app: Sphinx application context. """ app.add_domain(SyncthingConfigDomain) - return + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } From 9ff56b77ce0b5c309ef80bd9e70a1f841de9e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Sun, 7 Jun 2026 23:09:12 +0200 Subject: [PATCH 4/4] Modernize for Python >= 3.9, fix linter warnings. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Colomb --- _ext/syncthing_config.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/_ext/syncthing_config.py b/_ext/syncthing_config.py index 62844c62f..f4f132334 100644 --- a/_ext/syncthing_config.py +++ b/_ext/syncthing_config.py @@ -4,10 +4,11 @@ Modeled after the standard :directive:`cmdoption` directive. """ -from typing import Any, Tuple, Dict, Iterator, Set, NamedTuple +from typing import Any, Iterator, NamedTuple, Tuple from docutils.nodes import Element from docutils.parsers.rst import directives + from sphinx import addnodes from sphinx.addnodes import pending_xref from sphinx.builders import Builder @@ -15,8 +16,8 @@ from sphinx.domains import Domain, ObjType from sphinx.environment import BuildEnvironment from sphinx.roles import XRefRole -from sphinx.util.nodes import make_refnode from sphinx.util import logging +from sphinx.util.nodes import make_refnode __licence__ = 'BSD (3 clause)' @@ -116,11 +117,11 @@ class SyncthingConfigDomain(Domain): } @property - def config_sections(self) -> Set[str]: + def config_sections(self) -> set[str]: return self.data.setdefault('sections', set()) @property - def config_options(self) -> Dict[str, Tuple]: + def config_options(self) -> dict[str, Tuple]: return self.data.setdefault('options', {}) # fullname -> (docname, node_id) def get_full_qualified_name(self, node): # FIXME: what is this for?! @@ -175,7 +176,7 @@ def clear_doc(self, docname: str): if entry.docname != docname } - def merge_domaindata(self, docnames: Set[str], otherdata: Dict[str, Any]): + def merge_domaindata(self, docnames: set[str], otherdata: dict[str, Any]): self.config_sections.update(otherdata.get('sections', set())) for signature, entry in otherdata.get('options', {}).items():