Skip to content

Commit f8c7d5e

Browse files
authored
Merge branch 'main' into md_spectral_max
2 parents ee53ed0 + 64b6804 commit f8c7d5e

File tree

86 files changed

+3272
-385
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+3272
-385
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
*.pyc
22
*__pycache__
3-
*.DS_store
3+
*.DS_Store
44
*.swp
55
*.swo
66
build/pdf-intermediate.md
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .directives import setup # re-export for Sphinx
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
from docutils.parsers.rst import Directive, directives
2+
from docutils import nodes
3+
from .nodes import plr_card_grid_placeholder
4+
5+
6+
def _split_tags(raw):
7+
if not raw:
8+
return []
9+
return [t.strip() for t in raw.split(",") if t.strip()]
10+
11+
12+
def _ensure_env_map(env):
13+
if not hasattr(env, "plr_cards"):
14+
env.plr_cards = {}
15+
return env.plr_cards
16+
17+
18+
class _BaseCardDirective(Directive):
19+
has_content = False
20+
required_arguments = 0
21+
optional_arguments = 0
22+
final_argument_whitespace = False
23+
option_spec = {
24+
"header": directives.unchanged_required,
25+
"card_description": directives.unchanged,
26+
"image": directives.unchanged,
27+
"image_hover": directives.unchanged,
28+
"link": directives.unchanged_required,
29+
"tags": directives.unchanged,
30+
}
31+
32+
def run(self):
33+
env = self.state.document.settings.env
34+
docname = env.docname
35+
cards_map = _ensure_env_map(env)
36+
cards = cards_map.setdefault(docname, [])
37+
cards.append({
38+
"header": self.options.get("header", ""),
39+
"desc": self.options.get("card_description", ""),
40+
"image": self.options.get("image", ""),
41+
"image_hover": self.options.get("image_hover", ""),
42+
"link": self.options.get("link", ""),
43+
"tags": _split_tags(self.options.get("tags", "")),
44+
})
45+
# No visible node; actual rendering happens in the grid placeholder.
46+
return []
47+
48+
49+
class PyLabRobotCard(_BaseCardDirective):
50+
"""
51+
Add a card. Aliases:
52+
- .. customcarditem:: (compat)
53+
- .. plrcard:: (PyLabRobot)
54+
Options:
55+
:header: Title (required)
56+
:card_description: Short text
57+
:image: path/to/image.png
58+
:link: path/to/page.html (required)
59+
:tags: Tag1, Tag2
60+
"""
61+
62+
63+
class _BaseGridDirective(Directive):
64+
has_content = False
65+
66+
def run(self):
67+
return [plr_card_grid_placeholder("")]
68+
69+
70+
class PyLabRobotCardGrid(_BaseGridDirective):
71+
"""
72+
Insert a grid of the cards collected in this page. Aliases:
73+
- .. cardgrid:: (compat)
74+
- .. plrcardgrid:: (PyLabRobot)
75+
"""
76+
77+
78+
def _purge(app, env, docname):
79+
if hasattr(env, "plr_cards") and docname in env.plr_cards:
80+
del env.plr_cards[docname]
81+
82+
83+
def _merge(app, env, docnames, other):
84+
if not hasattr(other, "plr_cards"):
85+
return
86+
if not hasattr(env, "plr_cards"):
87+
env.plr_cards = {}
88+
env.plr_cards.update(other.plr_cards)
89+
90+
91+
def _page_ctx(app, pagename, templatename, context, doctree):
92+
env = app.builder.env
93+
per_page = getattr(env, "plr_cards", {}).get(pagename, [])
94+
all_tags = []
95+
for cards in getattr(env, "plr_cards", {}).values():
96+
for c in cards:
97+
all_tags.extend(c.get("tags", []))
98+
all_tags = sorted(set(all_tags), key=str.lower)
99+
context["plr_cards"] = per_page
100+
context["plr_cards_all_tags"] = all_tags
101+
102+
import os
103+
from docutils import nodes
104+
105+
106+
def _replace_placeholders(app, doctree, fromdocname):
107+
env = app.builder.env
108+
per_page = getattr(env, "plr_cards", {}).get(fromdocname, [])
109+
110+
# prefix like "", "../", "../../" depending on nesting of fromdocname
111+
docdir = os.path.dirname(fromdocname).replace("\\", "/")
112+
depth = 0 if docdir == "" else docdir.count("/") + 1
113+
prefix = "../" * depth
114+
115+
def is_url(p):
116+
return p.startswith(("http://", "https://"))
117+
118+
cards_render = []
119+
for c in per_page:
120+
img = (c.get("image") or "").replace("\\", "/")
121+
img_hover = (c.get("image_hover") or "").replace("\\", "/")
122+
123+
image_url = ""
124+
image_hover_url = ""
125+
126+
# Resolve main image
127+
if img:
128+
if is_url(img) or img.startswith("/"):
129+
image_url = img # absolute http(s) or site-root path
130+
else:
131+
if img.startswith("_static/"):
132+
image_url = prefix + img
133+
else:
134+
image_url = prefix + img
135+
136+
# Resolve hover image
137+
if img_hover:
138+
if is_url(img_hover) or img_hover.startswith("/"):
139+
image_hover_url = img_hover
140+
else:
141+
if img_hover.startswith("_static/"):
142+
image_hover_url = prefix + img_hover
143+
else:
144+
image_hover_url = prefix + img_hover
145+
146+
cards_render.append({
147+
**c,
148+
"image_url": image_url,
149+
"image_hover_url": image_hover_url,
150+
})
151+
152+
page_tags = sorted({t for c in per_page for t in c.get("tags", [])}, key=str.lower)
153+
154+
for node in doctree.traverse(plr_card_grid_placeholder):
155+
html = app.builder.templates.render("plr_card_grid.html", {
156+
"cards": cards_render,
157+
"all_tags": page_tags,
158+
})
159+
raw = nodes.raw("", html, format="html")
160+
node.replace_self(raw)
161+
162+
163+
def setup(app):
164+
from sphinx.application import Sphinx # noqa: F401
165+
166+
# Register directives (compat + PLR names)
167+
app.add_directive("customcarditem", PyLabRobotCard) # compat
168+
app.add_directive("plrcard", PyLabRobotCard) # PLR
169+
app.add_directive("cardgrid", PyLabRobotCardGrid) # compat
170+
app.add_directive("plrcardgrid", PyLabRobotCardGrid) # PLR
171+
172+
# Events
173+
app.connect("env-purge-doc", _purge)
174+
app.connect("env-merge-info", _merge)
175+
app.connect("html-page-context", _page_ctx)
176+
app.connect("doctree-resolved", _replace_placeholders)
177+
178+
return {
179+
"version": "1.0",
180+
"parallel_read_safe": True,
181+
"parallel_write_safe": True,
182+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from docutils import nodes
2+
3+
4+
class plr_card_grid_placeholder(nodes.General, nodes.Element):
5+
"""Placeholder node replaced by rendered card grid HTML."""
6+
pass

0 commit comments

Comments
 (0)