Skip to content

Commit ca52843

Browse files
authored
Merge pull request #102 from Point72/tkp/flex
Fix config lookup: use 'is None' instead of 'or default', add get_config_flex and html_extra_path support
2 parents 6e72d52 + db97311 commit ca52843

6 files changed

Lines changed: 241 additions & 7 deletions

File tree

docs/examples/example.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Example Extra Page</title>
6+
</head>
7+
<body>
8+
<h1>Example Extra Page</h1>
9+
<p>This standalone HTML file is served via <code>html_extra_path</code>.</p>
10+
</body>
11+
</html>

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ pages = [
192192
"docs/src/api.md",
193193
]
194194
use-autoapi = false
195+
html_extra_path = ["docs/examples"]
195196

196197
[tool.yardang.breathe]
197198
projects = { calculator = "examples/cpp/xml" }

yardang/build.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from jinja2 import Environment, FileSystemLoader
1010

11-
from .utils import get_config
11+
from .utils import get_config, get_config_flex
1212

1313
__all__ = ("generate_docs_configuration", "run_doxygen_if_needed", "generate_wiki_configuration")
1414

@@ -239,16 +239,18 @@ def customize(args):
239239
root = root if root is not None else get_config(section="root", base=config_base)
240240
cname = cname if cname is not None else get_config(section="cname", base=config_base)
241241
pages = pages if pages is not None else get_config(section="pages", base=config_base) or []
242-
use_autoapi = use_autoapi if use_autoapi is not None else get_config(section="use-autoapi", base=config_base)
243-
autoapi_ignore = autoapi_ignore if autoapi_ignore is not None else get_config(section="docs.autoapi-ignore")
242+
use_autoapi = use_autoapi if use_autoapi is not None else get_config_flex(section="use-autoapi", base=config_base)
243+
autoapi_ignore = autoapi_ignore if autoapi_ignore is not None else get_config_flex(section="autoapi-ignore", base=config_base)
244244

245245
custom_css = (
246246
custom_css
247247
if custom_css is not None
248-
else Path(get_config(section="custom-css", base=config_base) or (Path(__file__).parent / "custom.css"))
248+
else Path(get_config_flex(section="custom-css", base=config_base) or (Path(__file__).parent / "custom.css"))
249249
)
250250
custom_js = (
251-
custom_js if custom_js is not None else Path(get_config(section="custom-js", base=config_base) or (Path(__file__).parent / "custom.js"))
251+
custom_js
252+
if custom_js is not None
253+
else Path(get_config_flex(section="custom-js", base=config_base) or (Path(__file__).parent / "custom.js"))
252254
)
253255

254256
# if custom_css and custom_js are strings and they exist as paths, read them as Paths
@@ -280,6 +282,7 @@ def customize(args):
280282
# sphinx generic
281283
"html_theme_options": {},
282284
"html_static_path": [],
285+
"html_extra_path": [],
283286
"html_css_files": [],
284287
"html_js_files": [],
285288
"source_suffix": [],
@@ -357,7 +360,9 @@ def customize(args):
357360
# sphinx-reredirects
358361
"redirects": {},
359362
}.items():
360-
configuration_args[config_option] = get_config(section=config_option, base=config_base) or default
363+
configuration_args[config_option] = get_config_flex(section=config_option, base=config_base)
364+
if configuration_args[config_option] is None:
365+
configuration_args[config_option] = default
361366

362367
# Load breathe/doxygen configuration from tool.yardang.breathe
363368
breathe_config_base = f"{config_base}.breathe"
@@ -397,6 +402,13 @@ def customize(args):
397402
if use_breathe and auto_run_doxygen and breathe_args["breathe_projects"]:
398403
run_doxygen_if_needed(breathe_args["breathe_projects"])
399404

405+
# Convert relative paths in html_extra_path to absolute paths
406+
# This is needed because the conf.py is generated in a temp directory
407+
if configuration_args["html_extra_path"]:
408+
configuration_args["html_extra_path"] = [
409+
str(Path(path).resolve()) if not Path(path).is_absolute() else path for path in configuration_args["html_extra_path"]
410+
]
411+
400412
# Convert relative paths in breathe_projects to absolute paths
401413
# This is needed because the conf.py is generated in a temp directory
402414
if breathe_args["breathe_projects"]:

yardang/conf.py.j2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ os.environ["SPHINX_BUILDING"] = "1"
113113
html_theme = "{{theme}}"
114114
html_theme_options = {{html_theme_options}}
115115
html_static_path = {{html_static_path}}
116+
html_extra_path = {{html_extra_path}}
116117
html_css_files = [
117118
"styles/custom.css",
118119
*{{html_css_files}},

yardang/tests/test_all.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from yardang.build import generate_docs_configuration
55
from yardang.cli import build, debug
6+
from yardang.utils import get_config_flex
67

78

89
def test_build():
@@ -82,3 +83,188 @@ def test_use_autoapi_none_falls_back_to_config(self, tmp_path):
8283
assert "use_autoapi = True" in conf_content
8384
finally:
8485
os.chdir(original_cwd)
86+
87+
88+
class TestGetConfigFlex:
89+
"""Tests for get_config_flex accepting both hyphens and underscores."""
90+
91+
def test_hyphen_key_found(self, tmp_path):
92+
"""Test that hyphenated keys are found."""
93+
pyproject_content = """
94+
[project]
95+
name = "test-project"
96+
version = "1.0.0"
97+
98+
[tool.yardang]
99+
html-extra-path = ["docs/extra"]
100+
"""
101+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
102+
original_cwd = os.getcwd()
103+
try:
104+
os.chdir(tmp_path)
105+
result = get_config_flex(section="html-extra-path", base="tool.yardang")
106+
assert result == ["docs/extra"]
107+
finally:
108+
os.chdir(original_cwd)
109+
110+
def test_underscore_key_found(self, tmp_path):
111+
"""Test that underscored keys are found."""
112+
pyproject_content = """
113+
[project]
114+
name = "test-project"
115+
version = "1.0.0"
116+
117+
[tool.yardang]
118+
html_extra_path = ["docs/extra"]
119+
"""
120+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
121+
original_cwd = os.getcwd()
122+
try:
123+
os.chdir(tmp_path)
124+
result = get_config_flex(section="html_extra_path", base="tool.yardang")
125+
assert result == ["docs/extra"]
126+
finally:
127+
os.chdir(original_cwd)
128+
129+
def test_hyphen_key_searched_when_underscore_queried(self, tmp_path):
130+
"""Test that querying with underscores finds hyphenated keys."""
131+
pyproject_content = """
132+
[project]
133+
name = "test-project"
134+
version = "1.0.0"
135+
136+
[tool.yardang]
137+
html-extra-path = ["docs/extra"]
138+
"""
139+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
140+
original_cwd = os.getcwd()
141+
try:
142+
os.chdir(tmp_path)
143+
# Query with underscores, but TOML uses hyphens
144+
result = get_config_flex(section="html_extra_path", base="tool.yardang")
145+
assert result == ["docs/extra"]
146+
finally:
147+
os.chdir(original_cwd)
148+
149+
def test_underscore_key_searched_when_hyphen_queried(self, tmp_path):
150+
"""Test that querying with hyphens finds underscored keys."""
151+
pyproject_content = """
152+
[project]
153+
name = "test-project"
154+
version = "1.0.0"
155+
156+
[tool.yardang]
157+
html_extra_path = ["docs/extra"]
158+
"""
159+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
160+
original_cwd = os.getcwd()
161+
try:
162+
os.chdir(tmp_path)
163+
# Query with hyphens, but TOML uses underscores
164+
result = get_config_flex(section="html-extra-path", base="tool.yardang")
165+
assert result == ["docs/extra"]
166+
finally:
167+
os.chdir(original_cwd)
168+
169+
def test_hyphen_takes_precedence(self, tmp_path):
170+
"""Test that hyphenated key takes precedence when both exist."""
171+
pyproject_content = """
172+
[project]
173+
name = "test-project"
174+
version = "1.0.0"
175+
176+
[tool.yardang]
177+
html-extra-path = ["from-hyphens"]
178+
html_extra_path = ["from-underscores"]
179+
"""
180+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
181+
original_cwd = os.getcwd()
182+
try:
183+
os.chdir(tmp_path)
184+
result = get_config_flex(section="html_extra_path", base="tool.yardang")
185+
assert result == ["from-hyphens"]
186+
finally:
187+
os.chdir(original_cwd)
188+
189+
def test_missing_key_returns_none(self, tmp_path):
190+
"""Test that missing keys return None."""
191+
pyproject_content = """
192+
[project]
193+
name = "test-project"
194+
version = "1.0.0"
195+
196+
[tool.yardang]
197+
title = "Test"
198+
"""
199+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
200+
original_cwd = os.getcwd()
201+
try:
202+
os.chdir(tmp_path)
203+
result = get_config_flex(section="html_extra_path", base="tool.yardang")
204+
assert result is None
205+
finally:
206+
os.chdir(original_cwd)
207+
208+
209+
class TestHtmlExtraPath:
210+
"""Tests for html_extra_path in generated conf.py."""
211+
212+
def test_html_extra_path_with_hyphens(self, tmp_path):
213+
"""Test that html-extra-path (hyphens) is picked up in generated conf.py."""
214+
extra_dir = tmp_path / "docs" / "extra"
215+
extra_dir.mkdir(parents=True)
216+
(extra_dir / "page.html").write_text("<html><body>test</body></html>")
217+
218+
pyproject_content = """
219+
[project]
220+
name = "test-project"
221+
version = "1.0.0"
222+
223+
[tool.yardang]
224+
title = "Test Project"
225+
root = "README.md"
226+
use-autoapi = false
227+
html-extra-path = ["docs/extra"]
228+
"""
229+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
230+
(tmp_path / "README.md").write_text("# Test\n\nContent.")
231+
232+
original_cwd = os.getcwd()
233+
try:
234+
os.chdir(tmp_path)
235+
with generate_docs_configuration() as conf_dir:
236+
conf_content = (Path(conf_dir) / "conf.py").read_text()
237+
assert "html_extra_path" in conf_content
238+
assert "docs/extra" in conf_content or "docs\\\\extra" in conf_content or str(extra_dir) in conf_content
239+
finally:
240+
os.chdir(original_cwd)
241+
242+
def test_html_extra_path_with_underscores(self, tmp_path):
243+
"""Test that html_extra_path (underscores) is also accepted."""
244+
extra_dir = tmp_path / "docs" / "extra"
245+
extra_dir.mkdir(parents=True)
246+
(extra_dir / "page.html").write_text("<html><body>test</body></html>")
247+
248+
pyproject_content = """
249+
[project]
250+
name = "test-project"
251+
version = "1.0.0"
252+
253+
[tool.yardang]
254+
title = "Test Project"
255+
root = "README.md"
256+
use-autoapi = false
257+
html_extra_path = ["docs/extra"]
258+
"""
259+
(tmp_path / "pyproject.toml").write_text(pyproject_content)
260+
(tmp_path / "README.md").write_text("# Test\n\nContent.")
261+
262+
original_cwd = os.getcwd()
263+
try:
264+
os.chdir(tmp_path)
265+
with generate_docs_configuration() as conf_dir:
266+
conf_content = (Path(conf_dir) / "conf.py").read_text()
267+
assert "html_extra_path" in conf_content
268+
assert "docs/extra" in conf_content or "docs\\\\extra" in conf_content or str(extra_dir) in conf_content
269+
finally:
270+
os.chdir(original_cwd)

yardang/utils.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import toml
55

6-
__all__ = ("get_config",)
6+
__all__ = ("get_config", "get_config_flex")
77

88

99
def get_pyproject_toml():
@@ -22,3 +22,26 @@ def get_config(section="", base="tool.yardang"):
2222
if config is None:
2323
return None
2424
return config
25+
26+
27+
def get_config_flex(section="", base="tool.yardang"):
28+
"""Look up a config key, accepting both hyphens and underscores.
29+
30+
Tries the hyphenated form first (TOML convention), then the
31+
underscored form (Sphinx convention). For example, looking up
32+
``html_extra_path`` will try ``html-extra-path`` first, then
33+
``html_extra_path``.
34+
"""
35+
hyphen_key = section.replace("_", "-")
36+
underscore_key = section.replace("-", "_")
37+
38+
# Prefer hyphens (TOML convention)
39+
result = get_config(section=hyphen_key, base=base)
40+
if result is not None:
41+
return result
42+
43+
# Fall back to underscores (Sphinx convention)
44+
if underscore_key != hyphen_key:
45+
return get_config(section=underscore_key, base=base)
46+
47+
return None

0 commit comments

Comments
 (0)