from docutils import nodes, utils, frontend from docutils.parsers.rst import directives, Parser from sphinx.util.docutils import SphinxDirective class URLQueryParamRefNode(nodes.General, nodes.Element): """Node type for references that need URL query parameters.""" class WrapperNode(nodes.paragraph): """A wrapper node for URL query param references. Sphinx will not build if you don't wrap a reference in a paragraph. And a
is needed anyway for styling purposes. """ def visit(self, node): if "class" not in node: raise ValueError( "'class' attribute must be set on the WrapperNode for a " "URL query parameter reference." ) self.body.append(self.starttag(node, "div", CLASS=node["class"])) def depart(self, node): self.body.append("
") class URLQueryParamRefDirective(SphinxDirective): """Sphinx directive to insert a reference with query parameters.""" required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True has_content = True option_spec = { "parameters": directives.unchanged_required, "classes": directives.class_option, "ref-type": ( lambda choice: directives.choice(choice, ["any", "ref", "doc", "myst"]) ), } def run(self): ref_type = self.options.get("ref-type", "any") content = self.content.data reftarget = directives.uri(self.arguments[0]) return [ URLQueryParamRefNode( { "docname": self.env.docname, "parameters": self.options.get("parameters", None), "classes": self.options.get("classes", []), "reftarget": reftarget, "refdocname": self.env.docname, "refdomain": "std" if ref_type in {"ref", "doc"} else "", "reftype": ref_type, "refexplicit": content if content else reftarget, "refwarn": True, } ) ] def on_doctree_resolved(app, doctree, docname): """Replace URLQueryParamRefNode instances with real references. Any text that lives inside a URLQueryParamRefNode is parsed as usual. Args: app: Sphinx application doctree: Doctree which has just been resolved docname: Name of the document containing the reference nodes """ parser = Parser() for node in doctree.traverse(URLQueryParamRefNode): tmp_node = utils.new_document( "Content nested under URLQueryParamRefNode", frontend.OptionParser(components=[Parser]).get_default_values(), ) text = "\n".join(node.rawsource["refexplicit"]) # Parse all child RST as usual, then append any parsed nodes to the # reference node. parser.parse(text, tmp_node) ref_node = nodes.reference( rawsource=text, text="", ) for child in tmp_node.children: ref_node.append(child) # Pass all URLQueryParamRefNode attributes to ref node; # possibly not necessary for key, value in node.rawsource.items(): ref_node[key] = value # Need to update refuri of ref node to include URL query parameters ref_node["refuri"] = ( app.builder.get_relative_uri( docname, node.rawsource["reftarget"], ) + node.rawsource["parameters"] ) # Need to wrap the node in a paragraph for Sphinx to build wrapper = WrapperNode() wrapper["class"] = "query-param-ref-wrapper" wrapper += ref_node node.replace_self([wrapper]) def setup(app): app.add_directive("query-param-ref", URLQueryParamRefDirective) app.connect("doctree-resolved", on_doctree_resolved) app.add_node(WrapperNode, html=(WrapperNode.visit, WrapperNode.depart)) return { "version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True, }