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>
This commit is contained in:
parent
534990249d
commit
ecb7c875dd
4 changed files with 208 additions and 14 deletions
|
|
@ -15,12 +15,14 @@ Directives
|
|||
- ``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 @@ class CodeSampleListingNode(nodes.Element):
|
|||
pass
|
||||
|
||||
|
||||
class BoardNode(nodes.Element):
|
||||
pass
|
||||
|
||||
|
||||
class ConvertCodeSampleNode(SphinxTransform):
|
||||
default_priority = 100
|
||||
|
||||
|
|
@ -213,6 +219,65 @@ class ConvertCodeSampleCategoryNode(SphinxTransform):
|
|||
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 @@ class CodeSampleListingDirective(SphinxDirective):
|
|||
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 @@ class ZephyrDomain(Domain):
|
|||
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 @@ class ZephyrDomain(Domain):
|
|||
"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 @@ class ZephyrDomain(Domain):
|
|||
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 @@ class ZephyrDomain(Domain):
|
|||
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 @@ class ZephyrDomain(Domain):
|
|||
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 @@ def setup(app):
|
|||
|
||||
app.add_transform(ConvertCodeSampleNode)
|
||||
app.add_transform(ConvertCodeSampleCategoryNode)
|
||||
app.add_transform(ConvertBoardNode)
|
||||
|
||||
app.add_post_transform(ProcessCodeSampleListingNode)
|
||||
app.add_post_transform(CodeSampleCategoriesTocPatching)
|
||||
|
|
|
|||
55
doc/_static/css/custom.css
vendored
55
doc/_static/css/custom.css
vendored
|
|
@ -1110,4 +1110,57 @@ li>a.code-sample-link.reference.internal {
|
|||
|
||||
li>a.code-sample-link.reference.internal.current {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1184,6 +1184,23 @@ Code samples
|
|||
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
|
||||
|
|
|
|||
18
doc/templates/board.tmpl
vendored
18
doc/templates/board.tmpl
vendored
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in a new issue