Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update PandasCompat.py to resolve references #15704

Merged
merged 14 commits into from
Jun 10, 2024
143 changes: 98 additions & 45 deletions docs/cudf/source/_ext/PandasCompat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
# This file is adapted from official sphinx tutorial for `todo` extension:
# https://www.sphinx-doc.org/en/master/development/tutorials/todo.html

import functools
import operator
from typing import cast

from docutils import nodes
from docutils.nodes import Element
from docutils.parsers.rst import Directive
from sphinx.locale import get_translation
from sphinx.util.docutils import SphinxDirective
from sphinx import addnodes
from sphinx.domains import Domain
from sphinx.errors import NoUri
from sphinx.locale import _, get_translation
raybellwaves marked this conversation as resolved.
Show resolved Hide resolved
from sphinx.util.docutils import SphinxDirective, new_document

translator = get_translation("sphinx")

Expand Down Expand Up @@ -46,6 +54,8 @@ def run(self):
translator("Pandas Compatibility Note"),
translator("Pandas Compatibility Note"),
)
PandasCompat_node["docname"] = self.env.docname
PandasCompat_node["target"] = targetnode
self.state.nested_parse(
self.content, self.content_offset, PandasCompat_node
)
Expand Down Expand Up @@ -84,71 +94,114 @@ def merge_PandasCompats(app, env, docnames, other):
)


def process_PandasCompat_nodes(app, doctree, fromdocname):
if not app.config.include_pandas_compat:
for node in doctree.traverse(PandasCompat):
node.parent.remove(node)
class PandasCompatdDomain(Domain):
raybellwaves marked this conversation as resolved.
Show resolved Hide resolved
name = "pandascompat"
label = "pandascompat"

# Replace all PandasCompatList nodes with a list of the collected
# PandasCompats. Augment each PandasCompat with a backlink to the
# original location.
env = app.builder.env
@property
def pandascompats(self):
return self.data.setdefault("pandascompats", {})

if not hasattr(env, "PandasCompat_all_pandas_compat"):
env.PandasCompat_all_pandas_compat = []
def clear_doc(self, docname):
self.pandascompats.pop(docname, None)

for node in doctree.traverse(PandasCompatList):
if not app.config.include_pandas_compat:
node.replace_self([])
continue
def merge_domaindata(self, docnames, otherdata):
for docname in docnames:
self.pandascompats[docname] = otherdata["pandascompats"][docname]

content = []
def process_doc(self, env, docname, document):
pandascompats = self.pandascompats.setdefault(docname, [])
for pandascompat in document.findall(PandasCompat):
env.app.emit("pandascompat-defined", pandascompat)
pandascompats.append(pandascompat)

for PandasCompat_info in env.PandasCompat_all_pandas_compat:
para = nodes.paragraph()

# Create a reference back to the original docstring
newnode = nodes.reference("", "")
innernode = nodes.emphasis(
translator("[source]"), translator("[source]")
)
newnode["refdocname"] = PandasCompat_info["docname"]
newnode["refuri"] = app.builder.get_relative_uri(
fromdocname, PandasCompat_info["docname"]
)
newnode["refuri"] += "#" + PandasCompat_info["target"]["refid"]
newnode.append(innernode)
para += newnode
class PandasCompatListProcessor:
def __init__(self, app, doctree, docname):
self.builder = app.builder
self.config = app.config
self.env = app.env
self.domain = cast(PandasCompatdDomain, app.env.get_domain("pandascompat"))
self.document = new_document("")
print("running self.process")
raybellwaves marked this conversation as resolved.
Show resolved Hide resolved
self.process(doctree, docname)

# Insert the reference node into PandasCompat node
# Note that this node is a deepcopy from the original copy
# in the docstring, so changing this does not affect that in the
# doc.
PandasCompat_info["PandasCompat"].append(para)
def process(self, doctree: nodes.document, docname: str) -> None:
pandascompats = functools.reduce(
operator.iadd, self.domain.pandascompats.values(), []
)
raybellwaves marked this conversation as resolved.
Show resolved Hide resolved
for node in list(doctree.findall(PandasCompatList)):
raybellwaves marked this conversation as resolved.
Show resolved Hide resolved
if not self.config.include_pandas_compat:
node.parent.remove(node)
continue

if node.get("ids"):
content: list[Element] = [nodes.target()]
else:
content = []
raybellwaves marked this conversation as resolved.
Show resolved Hide resolved

for pandascompat in pandascompats:
# Create a copy of the pandascompat node
new_pandascompat = pandascompat.deepcopy()
new_pandascompat["ids"].clear()

self.resolve_reference(new_pandascompat, docname)
content.append(new_pandascompat)

ref = self.create_reference(pandascompat, docname)
content.append(ref)

node.replace_self(content)

def create_reference(self, pandascompat, docname):
para = nodes.paragraph()
newnode = nodes.reference("", "")
innernode = nodes.emphasis(
_("[source]"), _("[source]")
)
newnode["refdocname"] = pandascompat["docname"]
newnode["refuri"] = self.builder.get_relative_uri(
docname, pandascompat["docname"]
)
newnode["refuri"] += "#" + pandascompat["target"]["refid"]
newnode.append(innernode)
para += newnode
return para

# Insert the PandasCompand node into the PandasCompatList Node
content.append(PandasCompat_info["PandasCompat"])
def resolve_reference(self, todo, docname: str) -> None:
"""Resolve references in the todo content."""
for node in todo.findall(addnodes.pending_xref):
if "refdoc" in node:
node["refdoc"] = docname

node.replace_self(content)
# Note: To resolve references, it is needed to wrap it with document node
self.document += todo
self.env.resolve_references(self.document, docname, self.builder)
self.document.remove(todo)


def setup(app):
print("running add_config_value")
app.add_config_value("include_pandas_compat", False, "html")

print("running add_node(PandasCompatList)")
app.add_node(PandasCompatList)
print("running add_node(PandasCompat)")
app.add_node(
PandasCompat,
html=(visit_PandasCompat_node, depart_PandasCompat_node),
latex=(visit_PandasCompat_node, depart_PandasCompat_node),
text=(visit_PandasCompat_node, depart_PandasCompat_node),
man=(visit_PandasCompat_node, depart_PandasCompat_node),
texinfo=(visit_PandasCompat_node, depart_PandasCompat_node),
)

# Sphinx directives are lower-cased
print("running add_directive('pandas-compat', PandasCompatDirective)")
app.add_directive("pandas-compat", PandasCompatDirective)
print("running add_directive('pandas-compat-list', PandasCompatListDirective)")
app.add_directive("pandas-compat-list", PandasCompatListDirective)
app.connect("doctree-resolved", process_PandasCompat_nodes)
app.connect("env-purge-doc", purge_PandasCompats)
app.connect("env-merge-info", merge_PandasCompats)
print("running add add_domain(PandasCompatdDomain)")
app.add_domain(PandasCompatdDomain)
print("running app.connect('doctree-resolved', PandasCompatListProcessor)")
app.connect("doctree-resolved", PandasCompatListProcessor)

return {
"version": "0.1",
Expand Down
Loading