Skip to content

Commit f544348

Browse files
committed
feat: add tests for validating RST syntax, code blocks, and internal links in DataFrame API documentation
1 parent 584afd7 commit f544348

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

docs/source/api/test_dataframe.rst

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# docs/source/api/test_dataframe.py
2+
"""Tests for the DataFrame API documentation in RST format.
3+
4+
This script validates the RST syntax, links, and structure of the DataFrame
5+
API documentation files.
6+
"""
7+
8+
import os
9+
import sys
10+
from pathlib import Path
11+
from typing import List, Tuple
12+
13+
from docutils.core import publish_doctree, publish_parts
14+
from docutils.parsers.rst import Parser
15+
from docutils.utils import new_document, SystemMessage
16+
17+
def test_rst_syntax(file_path: str) -> List[SystemMessage]:
18+
"""Test if the RST file has valid syntax.
19+
20+
Args:
21+
file_path: Path to the RST file to test
22+
23+
Returns:
24+
List of error messages, empty if no errors
25+
"""
26+
with open(file_path, "r", encoding="utf-8") as rst_file:
27+
content = rst_file.read()
28+
29+
parser = Parser()
30+
settings = {}
31+
document = new_document("test", settings)
32+
33+
# Parse the document and capture any errors/warnings
34+
parser.parse(content, document)
35+
36+
return [msg for msg in document.traverse(condition=SystemMessage)]
37+
38+
39+
def test_build_rst(file_path: str) -> Tuple[bool, List[str]]:
40+
"""Test if the RST file can be built into HTML without errors.
41+
42+
Args:
43+
file_path: Path to the RST file to test
44+
45+
Returns:
46+
Tuple containing (success status, list of error messages)
47+
"""
48+
with open(file_path, "r", encoding="utf-8") as rst_file:
49+
content = rst_file.read()
50+
51+
try:
52+
# Try to build the document to HTML
53+
publish_parts(
54+
source=content,
55+
writer_name="html5",
56+
settings_overrides={"halt_level": 2} # Stop at warning level
57+
)
58+
return True, []
59+
except Exception as e:
60+
return False, [str(e)]
61+
62+
63+
def test_code_blocks(file_path: str) -> List[str]:
64+
"""Test if code blocks in the RST file are properly formatted.
65+
66+
Args:
67+
file_path: Path to the RST file to test
68+
69+
Returns:
70+
List of error messages, empty if no errors
71+
"""
72+
with open(file_path, "r", encoding="utf-8") as rst_file:
73+
content = rst_file.read()
74+
75+
errors = []
76+
lines = content.split("\n")
77+
in_code_block = False
78+
code_block_indent = 0
79+
80+
for i, line in enumerate(lines, 1):
81+
if ".. code-block::" in line:
82+
in_code_block = True
83+
code_block_indent = len(line) - len(line.lstrip())
84+
elif in_code_block and line.strip() and not line.startswith(" " * (code_block_indent + 4)):
85+
# Code block content should be indented by at least 4 spaces
86+
if not line.strip().startswith(".. "): # Skip RST directives
87+
errors.append(f"Line {i}: Code block not properly indented")
88+
in_code_block = False
89+
elif in_code_block and not line.strip():
90+
# Empty line within code block, still in code block
91+
pass
92+
elif in_code_block:
93+
# If line doesn't start with proper indentation, we're out of the code block
94+
if not line.startswith(" " * (code_block_indent + 4)):
95+
in_code_block = False
96+
97+
return errors
98+
99+
100+
def test_internal_links(file_path: str) -> List[str]:
101+
"""Test if internal links in the RST file point to valid sections.
102+
103+
Args:
104+
file_path: Path to the RST file to test
105+
106+
Returns:
107+
List of error messages, empty if no errors
108+
"""
109+
with open(file_path, "r", encoding="utf-8") as rst_file:
110+
content = rst_file.read()
111+
112+
errors = []
113+
114+
# Extract section titles
115+
section_titles = []
116+
lines = content.split("\n")
117+
for i, line in enumerate(lines):
118+
if i > 0 and len(lines[i-1].strip()) > 0:
119+
if all(c == "=" for c in line.strip()) or all(c == "-" for c in line.strip()) or all(c == "~" for c in line.strip()):
120+
section_titles.append(lines[i-1].strip())
121+
122+
# Check if internal links point to valid sections
123+
tree = publish_doctree(content)
124+
for node in tree.traverse():
125+
if node.tagname == "reference" and "refuri" in node.attributes:
126+
ref_uri = node.attributes["refuri"]
127+
if ref_uri.startswith("#"):
128+
link_target = ref_uri[1:]
129+
# Normalize target by removing spaces and converting to lowercase
130+
normalized_target = link_target.lower().replace(" ", "-")
131+
# Check if target exists in section titles
132+
found = False
133+
for title in section_titles:
134+
if normalized_target == title.lower().replace(" ", "-"):
135+
found = True
136+
break
137+
if not found:
138+
errors.append(f"Internal link to '#{link_target}' does not match any section title")
139+
140+
return errors
141+
142+
143+
def main():
144+
"""Run all tests on the DataFrame RST documentation."""
145+
# Get the path to the RST file
146+
current_dir = Path(os.path.dirname(os.path.abspath(__file__)))
147+
dataframe_rst_path = current_dir / "dataframe.rst"
148+
149+
if not dataframe_rst_path.exists():
150+
print(f"Error: File not found: {dataframe_rst_path}")
151+
return 1
152+
153+
# Run tests
154+
print(f"Testing {dataframe_rst_path}...")
155+
156+
syntax_errors = test_rst_syntax(str(dataframe_rst_path))
157+
if syntax_errors:
158+
print("RST syntax errors found:")
159+
for error in syntax_errors:
160+
print(f" - {error}")
161+
else:
162+
print("✓ RST syntax is valid")
163+
164+
code_block_errors = test_code_blocks(str(dataframe_rst_path))
165+
if code_block_errors:
166+
print("Code block errors found:")
167+
for error in code_block_errors:
168+
print(f" - {error}")
169+
else:
170+
print("✓ Code blocks are valid")
171+
172+
link_errors = test_internal_links(str(dataframe_rst_path))
173+
if link_errors:
174+
print("Internal link errors found:")
175+
for error in link_errors:
176+
print(f" - {error}")
177+
else:
178+
print("✓ Internal links are valid")
179+
180+
build_success, build_errors = test_build_rst(str(dataframe_rst_path))
181+
if not build_success:
182+
print("Build errors found:")
183+
for error in build_errors:
184+
print(f" - {error}")
185+
else:
186+
print("✓ Document builds successfully")
187+
188+
# Overall result
189+
if syntax_errors or code_block_errors or link_errors or not build_success:
190+
print("\n❌ Tests failed")
191+
return 1
192+
else:
193+
print("\n✅ All tests passed")
194+
return 0
195+
196+
197+
if __name__ == "__main__":
198+
sys.exit(main())

0 commit comments

Comments
 (0)