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-category::`` - Defines a category for grouping code samples.
|
||||||
- ``zephyr:code-sample-listing::`` - Shows a listing of code samples found in a given category.
|
- ``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-catalog::`` - Shows a listing of boards supported by Zephyr.
|
||||||
|
- ``zephyr:board::`` - Flags a document as being the documentation page for a board.
|
||||||
|
|
||||||
Roles
|
Roles
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- ``:zephyr:code-sample:`` - References a code sample.
|
- ``:zephyr:code-sample:`` - References a code sample.
|
||||||
- ``:zephyr:code-sample-category:`` - References a code sample category.
|
- ``:zephyr:code-sample-category:`` - References a code sample category.
|
||||||
|
- ``:zephyr:board:`` - References a board.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -85,6 +87,10 @@ class CodeSampleListingNode(nodes.Element):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BoardNode(nodes.Element):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConvertCodeSampleNode(SphinxTransform):
|
class ConvertCodeSampleNode(SphinxTransform):
|
||||||
default_priority = 100
|
default_priority = 100
|
||||||
|
|
||||||
|
|
@ -213,6 +219,65 @@ class ConvertCodeSampleCategoryNode(SphinxTransform):
|
||||||
node.replace_self(node.children[0])
|
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):
|
class CodeSampleCategoriesTocPatching(SphinxPostTransform):
|
||||||
default_priority = 5 # needs to run *before* ReferencesResolver
|
default_priority = 5 # needs to run *before* ReferencesResolver
|
||||||
|
|
||||||
|
|
@ -569,6 +634,45 @@ class CodeSampleListingDirective(SphinxDirective):
|
||||||
return [code_sample_listing_node]
|
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):
|
class BoardCatalogDirective(SphinxDirective):
|
||||||
has_content = False
|
has_content = False
|
||||||
required_arguments = 0
|
required_arguments = 0
|
||||||
|
|
@ -602,6 +706,7 @@ class ZephyrDomain(Domain):
|
||||||
roles = {
|
roles = {
|
||||||
"code-sample": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
|
"code-sample": XRefRole(innernodeclass=nodes.inline, warn_dangling=True),
|
||||||
"code-sample-category": 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 = {
|
directives = {
|
||||||
|
|
@ -609,11 +714,13 @@ class ZephyrDomain(Domain):
|
||||||
"code-sample-listing": CodeSampleListingDirective,
|
"code-sample-listing": CodeSampleListingDirective,
|
||||||
"code-sample-category": CodeSampleCategoryDirective,
|
"code-sample-category": CodeSampleCategoryDirective,
|
||||||
"board-catalog": BoardCatalogDirective,
|
"board-catalog": BoardCatalogDirective,
|
||||||
|
"board": BoardDirective,
|
||||||
}
|
}
|
||||||
|
|
||||||
object_types: Dict[str, ObjType] = {
|
object_types: Dict[str, ObjType] = {
|
||||||
"code-sample": ObjType("code sample", "code-sample"),
|
"code-sample": ObjType("code sample", "code-sample"),
|
||||||
"code-sample-category": ObjType("code sample category", "code-sample-category"),
|
"code-sample-category": ObjType("code sample category", "code-sample-category"),
|
||||||
|
"board": ObjType("board", "board"),
|
||||||
}
|
}
|
||||||
|
|
||||||
initial_data: Dict[str, Any] = {
|
initial_data: Dict[str, Any] = {
|
||||||
|
|
@ -647,6 +754,12 @@ class ZephyrDomain(Domain):
|
||||||
self.data["code-samples"].update(otherdata["code-samples"])
|
self.data["code-samples"].update(otherdata["code-samples"])
|
||||||
self.data["code-samples-categories"].update(otherdata["code-samples-categories"])
|
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
|
# merge category trees by adding all the categories found in the "other" tree that to
|
||||||
# self tree
|
# self tree
|
||||||
other_tree = otherdata["code-samples-categories-tree"]
|
other_tree = otherdata["code-samples-categories-tree"]
|
||||||
|
|
@ -689,6 +802,18 @@ class ZephyrDomain(Domain):
|
||||||
1,
|
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
|
# used by Sphinx Immaterial theme
|
||||||
def get_object_synopses(self) -> Iterator[Tuple[Tuple[str, str], str]]:
|
def get_object_synopses(self) -> Iterator[Tuple[Tuple[str, str], str]]:
|
||||||
for _, code_sample in self.data["code-samples"].items():
|
for _, code_sample in self.data["code-samples"].items():
|
||||||
|
|
@ -702,18 +827,20 @@ class ZephyrDomain(Domain):
|
||||||
elem = self.data["code-samples"].get(target)
|
elem = self.data["code-samples"].get(target)
|
||||||
elif type == "code-sample-category":
|
elif type == "code-sample-category":
|
||||||
elem = self.data["code-samples-categories"].get(target)
|
elem = self.data["code-samples-categories"].get(target)
|
||||||
|
elif type == "board":
|
||||||
|
elem = self.data["boards"].get(target)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
if elem:
|
if elem:
|
||||||
if not node.get("refexplicit"):
|
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(
|
return make_refnode(
|
||||||
builder,
|
builder,
|
||||||
fromdocname,
|
fromdocname,
|
||||||
elem["docname"],
|
elem["docname"],
|
||||||
elem["id"],
|
elem["id"] if type != "board" else elem["name"],
|
||||||
contnode,
|
contnode,
|
||||||
elem["description"].astext() if type == "code-sample" else None,
|
elem["description"].astext() if type == "code-sample" else None,
|
||||||
)
|
)
|
||||||
|
|
@ -821,6 +948,7 @@ def setup(app):
|
||||||
|
|
||||||
app.add_transform(ConvertCodeSampleNode)
|
app.add_transform(ConvertCodeSampleNode)
|
||||||
app.add_transform(ConvertCodeSampleCategoryNode)
|
app.add_transform(ConvertCodeSampleCategoryNode)
|
||||||
|
app.add_transform(ConvertBoardNode)
|
||||||
|
|
||||||
app.add_post_transform(ProcessCodeSampleListingNode)
|
app.add_post_transform(ProcessCodeSampleListingNode)
|
||||||
app.add_post_transform(CodeSampleCategoriesTocPatching)
|
app.add_post_transform(CodeSampleCategoriesTocPatching)
|
||||||
|
|
|
||||||
53
doc/_static/css/custom.css
vendored
53
doc/_static/css/custom.css
vendored
|
|
@ -1111,3 +1111,56 @@ li>a.code-sample-link.reference.internal {
|
||||||
li>a.code-sample-link.reference.internal.current {
|
li>a.code-sample-link.reference.internal.current {
|
||||||
text-decoration: underline;
|
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
|
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::
|
.. rst:directive:: .. zephyr:board-catalog::
|
||||||
|
|
||||||
This directive is used to generate a catalog of Zephyr-supported boards that can be used to
|
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
|
Overview
|
||||||
********
|
********
|
||||||
[A short description about the board, its main features and availability]
|
[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
|
Hardware
|
||||||
********
|
********
|
||||||
[General Hardware information]
|
[General Hardware information]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue