Skip to content

Commit 565cc4f

Browse files
committed
test of tabs extension
1 parent e6cba0f commit 565cc4f

File tree

3 files changed

+173
-35
lines changed

3 files changed

+173
-35
lines changed

docs/_ext/tabs.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
from typing import List
2+
from uuid import uuid4
3+
4+
from docutils import nodes
5+
from docutils.parsers.rst import directives
6+
from sphinx.application import Sphinx
7+
from sphinx.transforms.post_transforms import SphinxPostTransform
8+
from sphinx.util.docutils import SphinxDirective
9+
from sphinx.util.logging import getLogger
10+
11+
LOGGER = getLogger(__name__)
12+
13+
14+
def setup(app: Sphinx) -> None:
15+
app.add_directive("tabs", TabSetDirective)
16+
app.add_directive("tab-item", TabItemDirective)
17+
app.add_post_transform(TabSetHtmlTransform)
18+
app.add_node(compas_tab_input, html=(visit_tab_input, depart_tab_input))
19+
app.add_node(compas_tab_label, html=(visit_tab_label, depart_tab_label))
20+
app.add_node(nodes.container, override=True, html=(visit_container, depart_container))
21+
22+
23+
def visit_container(self, node: nodes.Node):
24+
classes = "docutils container"
25+
attrs = {}
26+
if node.get("is_div", False):
27+
classes = "docutils"
28+
self.body.append(self.starttag(node, "div", CLASS=classes, **attrs))
29+
30+
31+
def depart_container(self, node: nodes.Node):
32+
self.body.append("</div>\n")
33+
34+
35+
class TabSetDirective(SphinxDirective):
36+
"""A container for a set of tab items."""
37+
38+
has_content = True
39+
40+
def run(self) -> List[nodes.Node]:
41+
"""Run the directive."""
42+
self.assert_has_content()
43+
tabs = nodes.container("", is_div=True, component="tabs", classes=["compas-tabs"])
44+
self.set_source_info(tabs)
45+
self.state.nested_parse(self.content, self.content_offset, tabs)
46+
return [tabs]
47+
48+
49+
class TabItemDirective(SphinxDirective):
50+
51+
required_arguments = 1
52+
final_argument_whitespace = True
53+
has_content = True
54+
option_spec = {
55+
"active": directives.flag,
56+
"name": directives.unchanged,
57+
}
58+
59+
def run(self) -> List[nodes.Node]:
60+
"""Run the directive."""
61+
self.assert_has_content()
62+
tab_item = nodes.container("", component="tab-item", is_div=True, classes=["compas-tab-item"], active=("active" in self.options))
63+
64+
# add tab label
65+
textnodes, _ = self.state.inline_text(self.arguments[0], self.lineno)
66+
tab_label = nodes.rubric(self.arguments[0], *textnodes, classes=["compas-tab-item-label"])
67+
self.add_name(tab_label)
68+
tab_item += tab_label
69+
70+
# add tab content
71+
tab_content = nodes.container("", component="tab-content", is_div=True, classes=["compas-tab-item-content"])
72+
self.state.nested_parse(self.content, self.content_offset, tab_content)
73+
tab_item += tab_content
74+
75+
return [tab_item]
76+
77+
78+
class compas_tab_input(nodes.Element, nodes.General):
79+
pass
80+
81+
82+
class compas_tab_label(nodes.TextElement, nodes.General):
83+
pass
84+
85+
86+
def visit_tab_input(self, node):
87+
attributes = {"ids": [node["id"]], "type": node["type"], "name": node["set_id"]}
88+
if node["checked"]:
89+
attributes["checked"] = "checked"
90+
self.body.append(self.starttag(node, "input", **attributes))
91+
92+
93+
def depart_tab_input(self, node):
94+
self.body.append("</input>")
95+
96+
97+
def visit_tab_label(self, node):
98+
attributes = {"for": node["input_id"]}
99+
if "sync_id" in node:
100+
attributes["data-sync-id"] = node["sync_id"]
101+
self.body.append(self.starttag(node, "label", **attributes))
102+
103+
104+
def depart_tab_label(self, node):
105+
self.body.append("</label>")
106+
107+
108+
def is_component(node: nodes.Node, name: str):
109+
"""Check if a node is a certain design component."""
110+
try:
111+
return node.get("component") == name
112+
except AttributeError:
113+
return False
114+
115+
116+
class TabSetHtmlTransform(SphinxPostTransform):
117+
"""Transform tabs to HTML specific AST structure."""
118+
119+
default_priority = 200
120+
formats = ("html",)
121+
122+
def get_unique_key(self):
123+
return str(uuid4())
124+
125+
def run(self) -> None:
126+
"""Run the transform."""
127+
for tabs in self.document.traverse(lambda node: is_component(node, "tabs")):
128+
tabs_identity = self.get_unique_key()
129+
children = []
130+
131+
# get the first selected node
132+
active_index = None
133+
for index, tab_item in enumerate(tabs.children):
134+
if tab_item.get("active", False):
135+
if active_index is None:
136+
active_index = index
137+
active_index = 0 if active_index is None else active_index
138+
139+
for index, tab_item in enumerate(tabs.children):
140+
try:
141+
tab_label, tab_content = tab_item.children
142+
except ValueError:
143+
print(tab_item)
144+
raise
145+
tab_item_identity = self.get_unique_key()
146+
147+
# create: <input checked="checked" id="id" type="radio">
148+
input_node = compas_tab_input("", id=tab_item_identity, set_id=tabs_identity, type="radio", checked=(index == active_index))
149+
input_node.source, input_node.line = tab_item.source, tab_item.line
150+
children.append(input_node)
151+
152+
# create: <label for="id">...</label>
153+
label_node = compas_tab_label("", *tab_label.children, input_id=tab_item_identity, classes=tab_label["classes"])
154+
label_node.source, label_node.line = tab_item.source, tab_item.line
155+
children.append(label_node)
156+
157+
# add content
158+
children.append(tab_content)
159+
160+
print(tabs['classes'])
161+
tabs['classes'] = [value for value in tabs['classes'] if value != 'container']
162+
tabs.children = children

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from sphinx.ext.napoleon.docstring import NumpyDocstring
1515

1616
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
17+
sys.path.append(os.path.join(os.path.dirname(__file__), '_ext'))
1718

1819
# -- General configuration ------------------------------------------------
1920

@@ -48,6 +49,7 @@
4849
"sphinx.ext.napoleon",
4950
"sphinx.ext.githubpages",
5051
"sphinx.ext.autodoc.typehints",
52+
"tabs"
5153
]
5254

5355
# autodoc options

docs/installation.rst

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,48 +43,22 @@ First, clone the :mod:`compas_cgal` repo.
4343
4444
Create an environment with all the required dependencies.
4545

46-
.. raw:: html
47-
48-
<div class="card">
49-
<div class="card-header">
50-
<ul class="nav nav-tabs card-header-tabs">
51-
<li class="nav-item">
52-
<a class="nav-link active" data-toggle="tab" href="#dev_windows">Windows</a>
53-
</li>
54-
<li class="nav-item">
55-
<a class="nav-link" data-toggle="tab" href="#dev_unix">OSX / Linux</a>
56-
</li>
57-
</ul>
58-
</div>
59-
<div class="card-body">
60-
<div class="tab-content">
61-
62-
.. raw:: html
63-
64-
<div class="tab-pane active" id="dev_windows">
46+
.. tabs::
6547

66-
.. code-block:: bash
67-
68-
conda create -n cgal-dev python=3.8 mpir mpfr boost-cpp eigen=3.3 cgal-cpp=5.2 pybind11 compas compas_view2 --yes
69-
70-
.. raw:: html
48+
.. tab-item:: Windows
49+
:active:
7150

72-
</div>
73-
<div class="tab-pane" id="dev_unix">
74-
75-
.. code-block:: bash
51+
.. code-block:: bash
7652
77-
conda create -n cgal-dev python=3.8 mpfr gmp boost-cpp eigen=3.3 cgal-cpp=5.2 pybind11 compas compas_view2 --yes
53+
conda create -n cgal-dev python=3.8 mpir mpfr boost-cpp eigen=3.3 cgal-cpp=5.2 pybind11 compas compas_view2 --yes
7854
79-
.. raw:: html
55+
.. tab-item:: OSX / Linux
56+
:active:
8057

81-
</div>
58+
.. code-block:: bash
8259
83-
.. raw:: html
60+
conda create -n cgal-dev python=3.8 mpfr gmp boost-cpp eigen=3.3 cgal-cpp=5.2 pybind11 compas compas_view2 --yes
8461
85-
</div>
86-
</div>
87-
</div>
8862
8963
Activate the environment.
9064

0 commit comments

Comments
 (0)