Skip to content
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Bugs fixed
Patch by Bénédikt Tran.
* #11697: HTML Search: add 'noindex' meta robots tag.
Patch by James Addison.
* #11673: html builder: Allow :rst:role:`manpage` markup inside an RST title.
Patch by Bénédikt Tran.

Testing
-------
Expand Down
4 changes: 2 additions & 2 deletions sphinx/builders/html/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from sphinx.theming import HTMLThemeFactory
from sphinx.util import isurl, logging
from sphinx.util.display import progress_message, status_iterator
from sphinx.util.docutils import new_document
from sphinx.util.docutils import new_document, new_partial_document
from sphinx.util.fileutil import copy_asset
from sphinx.util.i18n import format_date
from sphinx.util.inventory import InventoryFile
Expand Down Expand Up @@ -425,7 +425,7 @@ def render_partial(self, node: Node | None) -> dict[str, str]:
if node is None:
return {'fragment': ''}

doc = new_document('<partial node>')
doc = new_partial_document()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just setting an attribute?

Suggested change
doc = new_partial_document()
doc = new_document('<partial node>')
doc['is_partial'] = True

and then doc.get('is_partial', False) to test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep the old behaviour in case there was someone somewhere who relied on that. Also, it's to remind us that we have possible partial documents somewhere and that we always need to be aware of.

On the other hand, using an attribute may lead false positive where a user would define this specific attribute. When using the source + special name we may avoid these cases (is_partial may be a "simple" attribute name that may be used in some projects).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm averse to introducing new infrastructure around this if it isn't something we want to keep long-term. So I would prefer the attribute approach (keeping the document name the same), we could use _is_partial or some mangled name if we need to.

A

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could use _is_partial or some mangled name if we need to.

That's fine by me then! Actually I don't know whether we'll keep it long-term or not since I don't know if we will be able to fix this issue on partial documents.

I'll do the update next week (I'me quite busy these days).

doc.append(node)
self._publisher.set_source(doc)
self._publisher.publish()
Expand Down
25 changes: 25 additions & 0 deletions sphinx/util/docutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,13 @@ def __init__(self, document: nodes.document, builder: Builder) -> None:
self.builder = builder
self.config = builder.config
self.settings = document.settings
#: Indicate whether this translator is acting on a partial document
#: or not. This is typically useful when, in some visitor methods,
#: it is known that the method could be called from either a regular
#: document or a partial one which may not necessarily contain nodes
#: that were transformed (e.g., titles nodes stored by the environment
#: are not processed by Sphinx transformations).
self.in_partial_document = is_partial_document(document)

def dispatch_visit(self, node: Node) -> None:
"""
Expand Down Expand Up @@ -633,3 +640,21 @@ def new_document(source_path: str, settings: Any = None) -> nodes.document:
document = addnodes.document(settings, reporter, source=source_path)
document.note_source(source_path, -1)
return document


def new_partial_document(settings: Any = None) -> nodes.document:
"""Return a new empty partial document.

A partial document is a document with minimal capabilities and which
contains nodes that may not have been transformed by the docutils or
Sphinx transformations.

This function is typically used to render partial fragments of a doctree
using non-transformed information, e.g., the HTML ``<title>`` tag.
"""
return new_document('<partial node>', settings)


def is_partial_document(document: nodes.document) -> bool:
"""Detect whether *document* is a partial document or not."""
return document.get('source') == '<partial node>'
4 changes: 2 additions & 2 deletions sphinx/writers/html5.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,12 +846,12 @@ def depart_abbreviation(self, node: Element) -> None:

def visit_manpage(self, node: Element) -> None:
self.visit_literal_emphasis(node)
if self.manpages_url:
if not self.in_partial_document and self.manpages_url:
node['refuri'] = self.manpages_url.format(**node.attributes)
self.visit_reference(node)

def depart_manpage(self, node: Element) -> None:
if self.manpages_url:
if not self.in_partial_document and self.manpages_url:
self.depart_reference(node)
self.depart_literal_emphasis(node)

Expand Down
8 changes: 5 additions & 3 deletions tests/roots/test-manpage_url/index.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* :manpage:`man(1)`
* :manpage:`ls.1`
* :manpage:`sphinx`
The :manpage:`cp(1)`
--------------------
* :manpage:`man(1)`
* :manpage:`ls.1`
* :manpage:`sphinx`
3 changes: 2 additions & 1 deletion tests/test_build_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -1492,7 +1492,8 @@ def test_html_sidebar(app, status, warning):


@pytest.mark.parametrize(("fname", "expect"), flat_dict({
'index.html': [(".//em/a[@href='https://example.com/man.1']", "", True),
'index.html': [(".//h1/em/a[@href='https://example.com/cp.1']", "", True),
(".//em/a[@href='https://example.com/man.1']", "", True),
(".//em/a[@href='https://example.com/ls.1']", "", True),
(".//em/a[@href='https://example.com/sphinx.']", "", True)],

Expand Down