Skip to content

Commit e3300c0

Browse files
committed
doc: boards: extensions: introduce zephyr:board role and directive
A new zephyr:board:: Sphinx directive allows to flag a documentation page as being the documentation for a specific board, allowing to auto-populate some of the contents, ex. by adding a board overview a la Wikipedia, and later things like supported HW features, etc. A corresponding :zephyr:board: role allows to link to a board doc page. Signed-off-by: Benjamin Cabé <[email protected]>
1 parent 88f91d7 commit e3300c0

File tree

4 files changed

+208
-14
lines changed

4 files changed

+208
-14
lines changed

doc/_extensions/zephyr/domain/__init__.py

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
- ``zephyr:code-sample-category::`` - Defines a category for grouping code samples.
1616
- ``zephyr:code-sample-listing::`` - Shows a listing of code samples found in a given category.
1717
- ``zephyr:board-catalog::`` - Shows a listing of boards supported by Zephyr.
18+
- ``zephyr:board::`` - Flags a document as being the documentation page for a board.
1819
1920
Roles
2021
-----
2122
2223
- ``:zephyr:code-sample:`` - References a code sample.
2324
- ``:zephyr:code-sample-category:`` - References a code sample category.
25+
- ``:zephyr:board:`` - References a board.
2426
2527
"""
2628

@@ -85,6 +87,10 @@ class CodeSampleListingNode(nodes.Element):
8587
pass
8688

8789

90+
class BoardNode(nodes.Element):
91+
pass
92+
93+
8894
class ConvertCodeSampleNode(SphinxTransform):
8995
default_priority = 100
9096

@@ -213,6 +219,65 @@ def convert_node(self, node):
213219
node.replace_self(node.children[0])
214220

215221

222+
class ConvertBoardNode(SphinxTransform):
223+
default_priority = 100
224+
225+
def apply(self):
226+
matcher = NodeMatcher(BoardNode)
227+
for node in self.document.traverse(matcher):
228+
self.convert_node(node)
229+
230+
def convert_node(self, node):
231+
parent = node.parent
232+
siblings_to_move = []
233+
if parent is not None:
234+
index = parent.index(node)
235+
siblings_to_move = parent.children[index + 1 :]
236+
237+
new_section = nodes.section(ids=[node["id"]])
238+
new_section += nodes.title(text=node["full_name"])
239+
240+
# create a sidebar with all the board details
241+
sidebar = nodes.sidebar(classes=["board-overview"])
242+
new_section += sidebar
243+
sidebar += nodes.title(text="Board Overview")
244+
245+
if node["image"] is not None:
246+
figure = nodes.figure()
247+
# set a scale of 100% to indicate we want a link to the full-size image
248+
figure += nodes.image(uri=f"/{node['image']}", scale=100)
249+
figure += nodes.caption(text=node["full_name"])
250+
sidebar += figure
251+
252+
field_list = nodes.field_list()
253+
sidebar += field_list
254+
255+
details = [
256+
("Vendor", node["vendor"]),
257+
("Architecture", ", ".join(node["archs"])),
258+
("SoC", ", ".join(node["socs"])),
259+
]
260+
261+
for property_name, value in details:
262+
field = nodes.field()
263+
field_name = nodes.field_name(text=property_name)
264+
field_body = nodes.field_body()
265+
field_body += nodes.paragraph(text=value)
266+
field += field_name
267+
field += field_body
268+
field_list += field
269+
270+
# Move the sibling nodes under the new section
271+
new_section.extend(siblings_to_move)
272+
273+
# Replace the custom node with the new section
274+
node.replace_self(new_section)
275+
276+
# Remove the moved siblings from their original parent
277+
for sibling in siblings_to_move:
278+
parent.remove(sibling)
279+
280+
216281
class CodeSampleCategoriesTocPatching(SphinxPostTransform):
217282
default_priority = 5 # needs to run *before* ReferencesResolver
218283

@@ -569,6 +634,45 @@ def run(self):
569634
return [code_sample_listing_node]
570635

571636

637+
class BoardDirective(SphinxDirective):
638+
has_content = False
639+
required_arguments = 1
640+
optional_arguments = 0
641+
642+
def run(self):
643+
# board_name is passed as the directive argument
644+
board_name = self.arguments[0]
645+
646+
boards = self.env.domaindata["zephyr"]["boards"]
647+
vendors = self.env.domaindata["zephyr"]["vendors"]
648+
649+
if board_name not in boards:
650+
logger.warning(
651+
f"Board {board_name} does not seem to be a valid board name.",
652+
location=(self.env.docname, self.lineno),
653+
)
654+
return []
655+
elif "docname" in boards[board_name]:
656+
logger.warning(
657+
f"Board {board_name} is already documented in {boards[board_name]['docname']}.",
658+
location=(self.env.docname, self.lineno),
659+
)
660+
return []
661+
else:
662+
board = boards[board_name]
663+
# flag board in the domain data as now having a documentation page so that it can be
664+
# cross-referenced etc.
665+
board["docname"] = self.env.docname
666+
667+
board_node = BoardNode(id=board_name)
668+
board_node["full_name"] = board["full_name"]
669+
board_node["vendor"] = vendors.get(board["vendor"], board["vendor"])
670+
board_node["archs"] = board["archs"]
671+
board_node["socs"] = board["socs"]
672+
board_node["image"] = board["image"]
673+
return [board_node]
674+
675+
572676
class BoardCatalogDirective(SphinxDirective):
573677
has_content = False
574678
required_arguments = 0
@@ -602,18 +706,21 @@ class ZephyrDomain(Domain):
602706
roles = {
603707
"code-sample": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
604708
"code-sample-category": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
709+
"board": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
605710
}
606711

607712
directives = {
608713
"code-sample": CodeSampleDirective,
609714
"code-sample-listing": CodeSampleListingDirective,
610715
"code-sample-category": CodeSampleCategoryDirective,
611716
"board-catalog": BoardCatalogDirective,
717+
"board": BoardDirective,
612718
}
613719

614720
object_types: Dict[str, ObjType] = {
615721
"code-sample": ObjType("code sample", "code-sample"),
616722
"code-sample-category": ObjType("code sample category", "code-sample-category"),
723+
"board": ObjType("board", "board"),
617724
}
618725

619726
initial_data: Dict[str, Any] = {
@@ -647,6 +754,12 @@ def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
647754
self.data["code-samples"].update(otherdata["code-samples"])
648755
self.data["code-samples-categories"].update(otherdata["code-samples-categories"])
649756

757+
# self.data["boards"] contains all the boards right from builder-inited time, but it still # potentially needs merging since a board's docname property is set by BoardDirective to
758+
# indicate the board is documented in a specific document.
759+
for board_name, board in otherdata["boards"].items():
760+
if "docname" in board:
761+
self.data["boards"][board_name]["docname"] = board["docname"]
762+
650763
# merge category trees by adding all the categories found in the "other" tree that to
651764
# self tree
652765
other_tree = otherdata["code-samples-categories-tree"]
@@ -689,6 +802,18 @@ def get_objects(self):
689802
1,
690803
)
691804

805+
for _, board in self.data["boards"].items():
806+
# only boards that do have a documentation page are to be considered as valid objects
807+
if "docname" in board:
808+
yield (
809+
board["name"],
810+
board["full_name"],
811+
"board",
812+
board["docname"],
813+
board["name"],
814+
1,
815+
)
816+
692817
# used by Sphinx Immaterial theme
693818
def get_object_synopses(self) -> Iterator[Tuple[Tuple[str, str], str]]:
694819
for _, code_sample in self.data["code-samples"].items():
@@ -702,18 +827,20 @@ def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode):
702827
elem = self.data["code-samples"].get(target)
703828
elif type == "code-sample-category":
704829
elem = self.data["code-samples-categories"].get(target)
830+
elif type == "board":
831+
elem = self.data["boards"].get(target)
705832
else:
706833
return
707834

708835
if elem:
709836
if not node.get("refexplicit"):
710-
contnode = [nodes.Text(elem["name"])]
837+
contnode = [nodes.Text(elem["name"] if type != "board" else elem["full_name"])]
711838

712839
return make_refnode(
713840
builder,
714841
fromdocname,
715842
elem["docname"],
716-
elem["id"],
843+
elem["id"] if type != "board" else elem["name"],
717844
contnode,
718845
elem["description"].astext() if type == "code-sample" else None,
719846
)
@@ -821,6 +948,7 @@ def setup(app):
821948

822949
app.add_transform(ConvertCodeSampleNode)
823950
app.add_transform(ConvertCodeSampleCategoryNode)
951+
app.add_transform(ConvertBoardNode)
824952

825953
app.add_post_transform(ProcessCodeSampleListingNode)
826954
app.add_post_transform(CodeSampleCategoriesTocPatching)

doc/_static/css/custom.css

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1060,4 +1060,57 @@ li>a.code-sample-link.reference.internal {
10601060

10611061
li>a.code-sample-link.reference.internal.current {
10621062
text-decoration: underline;
1063-
}
1063+
}
1064+
1065+
/* Board overview "card" on board documentation pages */
1066+
.sidebar.board-overview {
1067+
border-radius: 12px;
1068+
padding: 0px;
1069+
background: var(--admonition-note-background-color);
1070+
color: var(--admonition-note-title-color);
1071+
border-color: var(--admonition-note-title-background-color);
1072+
}
1073+
1074+
@media screen and (max-width: 480px) {
1075+
.sidebar.board-overview {
1076+
float: none;
1077+
margin-left: 0;
1078+
}
1079+
}
1080+
1081+
.sidebar.board-overview .sidebar-title {
1082+
font-family: var(--header-font-family);
1083+
background: var(--admonition-note-title-background-color);
1084+
color: var(--admonition-note-title-color);
1085+
border-radius: 12px 12px 0px 0px;
1086+
margin: 0px;
1087+
text-align: center;
1088+
}
1089+
1090+
.sidebar.board-overview * {
1091+
color: var(--admonition-note-color);
1092+
}
1093+
1094+
.sidebar.board-overview figure {
1095+
padding: 1rem;
1096+
margin-bottom: -1rem;
1097+
}
1098+
1099+
.sidebar.board-overview figure img {
1100+
height: auto !important;
1101+
}
1102+
1103+
.sidebar.board-overview figure figcaption p {
1104+
margin-bottom: 0px;
1105+
}
1106+
1107+
.sidebar.board-overview dl.field-list {
1108+
align-items: center;
1109+
margin-top: 12px !important;
1110+
margin-bottom: 12px !important;
1111+
grid-template-columns: auto 1fr !important;
1112+
}
1113+
1114+
.sidebar.board-overview dl.field-list > dt {
1115+
background: transparent !important;
1116+
}

doc/contribute/documentation/guidelines.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,23 @@ Code samples
11841184
Boards
11851185
======
11861186

1187+
.. rst:directive:: .. zephyr:board:: name
1188+
1189+
This directive is used at the beginning of a document to indicate it is the main documentation
1190+
page for a board whose name is given as the directive argument.
1191+
1192+
For example::
1193+
1194+
.. zephyr:board:: wio_terminal
1195+
1196+
The metadata for the board is read from various config files and used to automatically populate
1197+
some sections of the board documentation. A board documentation page that uses this directive
1198+
can be linked to using the :rst:role:`zephyr:board` role.
1199+
1200+
.. rst:role:: zephyr:board
1201+
1202+
This role is used to reference a board documented using :rst:dir:`zephyr:board`.
1203+
11871204
.. rst:directive:: .. zephyr:board-catalog::
11881205
11891206
This directive is used to generate a catalog of Zephyr-supported boards that can be used to

doc/templates/board.tmpl

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
.. _boardname_linkname:
1+
.. zephyr:board:: board_name
22

3-
[Board Name]
4-
#############
3+
.. To ensure the board documentation page displays correctly, it is highly
4+
recommended to include a picture alongside the documentation page.
5+
6+
The picture should be named after the board (e.g., "board_name.webp")
7+
and preferably be in webp format. Alternatively, png or jpg formats
8+
are also accepted.
59

610
Overview
711
********
812
[A short description about the board, its main features and availability]
913

10-
11-
.. figure:: board_name.png
12-
:width: 800px
13-
:align: center
14-
:alt: Board Name
15-
16-
Board Name (Credit: <owner>)
17-
1814
Hardware
1915
********
2016
[General Hardware information]

0 commit comments

Comments
 (0)