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é <benjamin@zephyrproject.org>
diff --git a/doc/_extensions/zephyr/domain/__init__.py b/doc/_extensions/zephyr/domain/__init__.py
index 8f45b45..4814e3c 100644
--- a/doc/_extensions/zephyr/domain/__init__.py
+++ b/doc/_extensions/zephyr/domain/__init__.py
@@ -15,12 +15,14 @@
- ``zephyr:code-sample-category::`` - Defines a category for grouping code samples.
- ``zephyr:code-sample-listing::`` - Shows a listing of code samples found in a given category.
- ``zephyr:board-catalog::`` - Shows a listing of boards supported by Zephyr.
+- ``zephyr:board::`` - Flags a document as being the documentation page for a board.
Roles
-----
- ``:zephyr:code-sample:`` - References a code sample.
- ``:zephyr:code-sample-category:`` - References a code sample category.
+- ``:zephyr:board:`` - References a board.
"""
@@ -85,6 +87,10 @@
pass
+class BoardNode(nodes.Element):
+ pass
+
+
class ConvertCodeSampleNode(SphinxTransform):
default_priority = 100
@@ -213,6 +219,65 @@
node.replace_self(node.children[0])
+class ConvertBoardNode(SphinxTransform):
+ default_priority = 100
+
+ def apply(self):
+ matcher = NodeMatcher(BoardNode)
+ for node in self.document.traverse(matcher):
+ self.convert_node(node)
+
+ def convert_node(self, node):
+ parent = node.parent
+ siblings_to_move = []
+ if parent is not None:
+ index = parent.index(node)
+ siblings_to_move = parent.children[index + 1 :]
+
+ new_section = nodes.section(ids=[node["id"]])
+ new_section += nodes.title(text=node["full_name"])
+
+ # create a sidebar with all the board details
+ sidebar = nodes.sidebar(classes=["board-overview"])
+ new_section += sidebar
+ sidebar += nodes.title(text="Board Overview")
+
+ if node["image"] is not None:
+ figure = nodes.figure()
+ # set a scale of 100% to indicate we want a link to the full-size image
+ figure += nodes.image(uri=f"/{node['image']}", scale=100)
+ figure += nodes.caption(text=node["full_name"])
+ sidebar += figure
+
+ field_list = nodes.field_list()
+ sidebar += field_list
+
+ details = [
+ ("Vendor", node["vendor"]),
+ ("Architecture", ", ".join(node["archs"])),
+ ("SoC", ", ".join(node["socs"])),
+ ]
+
+ for property_name, value in details:
+ field = nodes.field()
+ field_name = nodes.field_name(text=property_name)
+ field_body = nodes.field_body()
+ field_body += nodes.paragraph(text=value)
+ field += field_name
+ field += field_body
+ field_list += field
+
+ # Move the sibling nodes under the new section
+ new_section.extend(siblings_to_move)
+
+ # Replace the custom node with the new section
+ node.replace_self(new_section)
+
+ # Remove the moved siblings from their original parent
+ for sibling in siblings_to_move:
+ parent.remove(sibling)
+
+
class CodeSampleCategoriesTocPatching(SphinxPostTransform):
default_priority = 5 # needs to run *before* ReferencesResolver
@@ -569,6 +634,45 @@
return [code_sample_listing_node]
+class BoardDirective(SphinxDirective):
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+
+ def run(self):
+ # board_name is passed as the directive argument
+ board_name = self.arguments[0]
+
+ boards = self.env.domaindata["zephyr"]["boards"]
+ vendors = self.env.domaindata["zephyr"]["vendors"]
+
+ if board_name not in boards:
+ logger.warning(
+ f"Board {board_name} does not seem to be a valid board name.",
+ location=(self.env.docname, self.lineno),
+ )
+ return []
+ elif "docname" in boards[board_name]:
+ logger.warning(
+ f"Board {board_name} is already documented in {boards[board_name]['docname']}.",
+ location=(self.env.docname, self.lineno),
+ )
+ return []
+ else:
+ board = boards[board_name]
+ # flag board in the domain data as now having a documentation page so that it can be
+ # cross-referenced etc.
+ board["docname"] = self.env.docname
+
+ board_node = BoardNode(id=board_name)
+ board_node["full_name"] = board["full_name"]
+ board_node["vendor"] = vendors.get(board["vendor"], board["vendor"])
+ board_node["archs"] = board["archs"]
+ board_node["socs"] = board["socs"]
+ board_node["image"] = board["image"]
+ return [board_node]
+
+
class BoardCatalogDirective(SphinxDirective):
has_content = False
required_arguments = 0
@@ -602,6 +706,7 @@
roles = {
"code-sample": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
"code-sample-category": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
+ "board": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
}
directives = {
@@ -609,11 +714,13 @@
"code-sample-listing": CodeSampleListingDirective,
"code-sample-category": CodeSampleCategoryDirective,
"board-catalog": BoardCatalogDirective,
+ "board": BoardDirective,
}
object_types: Dict[str, ObjType] = {
"code-sample": ObjType("code sample", "code-sample"),
"code-sample-category": ObjType("code sample category", "code-sample-category"),
+ "board": ObjType("board", "board"),
}
initial_data: Dict[str, Any] = {
@@ -647,6 +754,12 @@
self.data["code-samples"].update(otherdata["code-samples"])
self.data["code-samples-categories"].update(otherdata["code-samples-categories"])
+ # 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
+ # indicate the board is documented in a specific document.
+ for board_name, board in otherdata["boards"].items():
+ if "docname" in board:
+ self.data["boards"][board_name]["docname"] = board["docname"]
+
# merge category trees by adding all the categories found in the "other" tree that to
# self tree
other_tree = otherdata["code-samples-categories-tree"]
@@ -689,6 +802,18 @@
1,
)
+ for _, board in self.data["boards"].items():
+ # only boards that do have a documentation page are to be considered as valid objects
+ if "docname" in board:
+ yield (
+ board["name"],
+ board["full_name"],
+ "board",
+ board["docname"],
+ board["name"],
+ 1,
+ )
+
# used by Sphinx Immaterial theme
def get_object_synopses(self) -> Iterator[Tuple[Tuple[str, str], str]]:
for _, code_sample in self.data["code-samples"].items():
@@ -702,18 +827,20 @@
elem = self.data["code-samples"].get(target)
elif type == "code-sample-category":
elem = self.data["code-samples-categories"].get(target)
+ elif type == "board":
+ elem = self.data["boards"].get(target)
else:
return
if elem:
if not node.get("refexplicit"):
- contnode = [nodes.Text(elem["name"])]
+ contnode = [nodes.Text(elem["name"] if type != "board" else elem["full_name"])]
return make_refnode(
builder,
fromdocname,
elem["docname"],
- elem["id"],
+ elem["id"] if type != "board" else elem["name"],
contnode,
elem["description"].astext() if type == "code-sample" else None,
)
@@ -821,6 +948,7 @@
app.add_transform(ConvertCodeSampleNode)
app.add_transform(ConvertCodeSampleCategoryNode)
+ app.add_transform(ConvertBoardNode)
app.add_post_transform(ProcessCodeSampleListingNode)
app.add_post_transform(CodeSampleCategoriesTocPatching)
diff --git a/doc/_static/css/custom.css b/doc/_static/css/custom.css
index eccda8f..3713d09 100644
--- a/doc/_static/css/custom.css
+++ b/doc/_static/css/custom.css
@@ -1110,4 +1110,57 @@
li>a.code-sample-link.reference.internal.current {
text-decoration: underline;
-}
\ No newline at end of file
+}
+
+/* Board overview "card" on board documentation pages */
+.sidebar.board-overview {
+ border-radius: 12px;
+ padding: 0px;
+ background: var(--admonition-note-background-color);
+ color: var(--admonition-note-title-color);
+ border-color: var(--admonition-note-title-background-color);
+}
+
+@media screen and (max-width: 480px) {
+ .sidebar.board-overview {
+ float: none;
+ margin-left: 0;
+ }
+}
+
+.sidebar.board-overview .sidebar-title {
+ font-family: var(--header-font-family);
+ background: var(--admonition-note-title-background-color);
+ color: var(--admonition-note-title-color);
+ border-radius: 12px 12px 0px 0px;
+ margin: 0px;
+ text-align: center;
+}
+
+.sidebar.board-overview * {
+ color: var(--admonition-note-color);
+}
+
+.sidebar.board-overview figure {
+ padding: 1rem;
+ margin-bottom: -1rem;
+}
+
+.sidebar.board-overview figure img {
+ height: auto !important;
+}
+
+.sidebar.board-overview figure figcaption p {
+ margin-bottom: 0px;
+}
+
+.sidebar.board-overview dl.field-list {
+ align-items: center;
+ margin-top: 12px !important;
+ margin-bottom: 12px !important;
+ grid-template-columns: auto 1fr !important;
+}
+
+.sidebar.board-overview dl.field-list > dt {
+ background: transparent !important;
+}
diff --git a/doc/contribute/documentation/guidelines.rst b/doc/contribute/documentation/guidelines.rst
index 6012d32..3c5471b 100644
--- a/doc/contribute/documentation/guidelines.rst
+++ b/doc/contribute/documentation/guidelines.rst
@@ -1184,6 +1184,23 @@
Boards
======
+.. rst:directive:: .. zephyr:board:: name
+
+ This directive is used at the beginning of a document to indicate it is the main documentation
+ page for a board whose name is given as the directive argument.
+
+ For example::
+
+ .. zephyr:board:: wio_terminal
+
+ The metadata for the board is read from various config files and used to automatically populate
+ some sections of the board documentation. A board documentation page that uses this directive
+ can be linked to using the :rst:role:`zephyr:board` role.
+
+.. rst:role:: zephyr:board
+
+ This role is used to reference a board documented using :rst:dir:`zephyr:board`.
+
.. rst:directive:: .. zephyr:board-catalog::
This directive is used to generate a catalog of Zephyr-supported boards that can be used to
diff --git a/doc/templates/board.tmpl b/doc/templates/board.tmpl
index 3650699..9620109 100644
--- a/doc/templates/board.tmpl
+++ b/doc/templates/board.tmpl
@@ -1,20 +1,16 @@
-.. _boardname_linkname:
+.. zephyr:board:: board_name
-[Board Name]
-#############
+.. To ensure the board documentation page displays correctly, it is highly
+ recommended to include a picture alongside the documentation page.
+
+ The picture should be named after the board (e.g., "board_name.webp")
+ and preferably be in webp format. Alternatively, png or jpg formats
+ are also accepted.
Overview
********
[A short description about the board, its main features and availability]
-
-.. figure:: board_name.png
- :width: 800px
- :align: center
- :alt: Board Name
-
- Board Name (Credit: <owner>)
-
Hardware
********
[General Hardware information]