Message ID | 20231103135622.250314-1-leitao@debian.org (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | Documentation: Document the Netlink spec | expand |
On Fri, 3 Nov 2023 06:56:22 -0700 Breno Leitao wrote: > This is a Sphinx extension that parses the Netlink YAML spec files > (Documentation/netlink/specs/), and generates a rst file to be > displayed into Documentation pages. > > Create a new Documentation/networking/netlink_spec page, and a sub-page > for each Netlink spec that needs to be documented, such as ethtool, > devlink, netdev, etc. > > Create a Sphinx directive extension that reads the YAML spec > (located under Documentation/netlink/specs), parses it and returns a RST > string that is inserted where the Sphinx directive was called. > +======================================= > +Family ``fou`` netlink specification > +======================================= nit: bad length of the ==== marker lines > +def parse_attributes_set(entries: List[Dict[str, Any]]) -> str: I'd rename to parse_attr_sets (plural sets). > + """Parse attribute from attribute-set""" > + preprocessed = ["name", "type"] > + ignored = ["checks"] > + lines = [] > + > + for entry in entries: > + lines.append(rst_bullet(bold(entry["name"]))) This would be better as subsubtitle. > + for attr in entry["attributes"]: > + type_ = attr.get("type") > + attr_line = bold(attr["name"]) > + if type_: > + # Add the attribute type in the same line > + attr_line += f" ({inline(type_)})" > + > + lines.append(rst_bullet(attr_line, 2)) And this actually, probably also a sub^3-title, because we'll want to link to the specific attributes sooner or later. And linking to bullet points isn't a thing (AFAIU?)
Breno Leitao <leitao@debian.org> writes: > This is a Sphinx extension that parses the Netlink YAML spec files > (Documentation/netlink/specs/), and generates a rst file to be > displayed into Documentation pages. > > Create a new Documentation/networking/netlink_spec page, and a sub-page > for each Netlink spec that needs to be documented, such as ethtool, > devlink, netdev, etc. > > Create a Sphinx directive extension that reads the YAML spec > (located under Documentation/netlink/specs), parses it and returns a RST > string that is inserted where the Sphinx directive was called. This is great! Looks like I need to fill in some missing docs in the specs I have contributed. I wonder if the generated .rst content can be adjusted to improve the resulting HTML. There are a couple of places where paragraph text is indented and I don't think it needs to be, e.g. the 'Summary' doc. A lot of the .rst content seems to be over-indented which causes blockquote tags to be generated in the HTML. That combined with a mixture of bullets and definition lists at the same indentation level seems to produce HTML with inconsistent indentation. I quickly hacked the diff below to see if it would improve the HTML rendering. I think the HTML has fewer odd constructs and the indentation seems better to my eye. My main aim was to ensure that for a given section, each indentation level uses the same construct, whether it be a definition list or a bullet list. It would be great to generate links from e.g. an attribute-set to its definition. Did you intentionally leave out the protocol values? It looks like parse_entries will need to be extended to include the type information for struct members, similar to how attribute sets are shown. I'd be happy to look at this as a follow up patch, unless you get there first. Thanks, Donald. diff --git a/Documentation/sphinx/netlink_spec.py b/Documentation/sphinx/netlink_spec.py index 80756e72ed4f..66ba9106b4ea 100755 --- a/Documentation/sphinx/netlink_spec.py +++ b/Documentation/sphinx/netlink_spec.py @@ -92,7 +92,7 @@ def parse_mcast_group(mcast_group: List[Dict[str, Any]]) -> str: """Parse 'multicast' group list and return a formatted string""" lines = [] for group in mcast_group: - lines.append(rst_paragraph(group["name"], 1)) + lines.append(rst_bullet(group["name"])) return "\n".join(lines) @@ -101,7 +101,7 @@ def parse_do(do_dict: Dict[str, Any], level: int = 0) -> str: """Parse 'do' section and return a formatted string""" lines = [] for key in do_dict.keys(): - lines.append(rst_bullet(bold(key), level + 1)) + lines.append(" " + bold(key)) lines.append(parse_do_attributes(do_dict[key], level + 1) + "\n") return "\n".join(lines) @@ -124,18 +124,19 @@ def parse_operations(operations: List[Dict[str, Any]]) -> str: for operation in operations: lines.append(rst_subsubtitle(operation["name"])) lines.append(rst_paragraph(operation["doc"]) + "\n") - if "do" in operation: - lines.append(rst_paragraph(bold("do"), 1)) - lines.append(parse_do(operation["do"], 1)) - if "dump" in operation: - lines.append(rst_paragraph(bold("dump"), 1)) - lines.append(parse_do(operation["dump"], 1)) for key in operation.keys(): if key in preprocessed: # Skip the special fields continue - lines.append(rst_fields(key, operation[key], 1)) + lines.append(rst_fields(key, operation[key], 0)) + + if "do" in operation: + lines.append(rst_paragraph(":do:", 0)) + lines.append(parse_do(operation["do"], 0)) + if "dump" in operation: + lines.append(rst_paragraph(":dump:", 0)) + lines.append(parse_do(operation["dump"], 0)) # New line after fields lines.append("\n") @@ -150,7 +151,7 @@ def parse_entries(entries: List[Dict[str, Any]], level: int) -> str: if isinstance(entry, dict): # entries could be a list or a dictionary lines.append( - rst_fields(entry.get("name"), sanitize(entry.get("doc")), level) + rst_fields(entry.get("name"), sanitize(entry.get("doc") or ""), level) ) elif isinstance(entry, list): lines.append(rst_list_inline(entry, level)) @@ -172,16 +173,16 @@ def parse_definitions(defs: Dict[str, Any]) -> str: for k in definition.keys(): if k in preprocessed + ignored: continue - lines.append(rst_fields(k, sanitize(definition[k]), 1)) + lines.append(rst_fields(k, sanitize(definition[k]), 0)) # Field list needs to finish with a new line lines.append("\n") if "entries" in definition: - lines.append(rst_paragraph(bold("Entries"), 1)) - lines.append(parse_entries(definition["entries"], 2)) + lines.append(rst_paragraph(":entries:", 0)) + lines.append(parse_entries(definition["entries"], 1)) if "members" in definition: - lines.append(rst_paragraph(bold("members"), 1)) - lines.append(parse_entries(definition["members"], 2)) + lines.append(rst_paragraph(":members:", 0)) + lines.append(parse_entries(definition["members"], 1)) return "\n".join(lines) @@ -201,12 +202,12 @@ def parse_attributes_set(entries: List[Dict[str, Any]]) -> str: # Add the attribute type in the same line attr_line += f" ({inline(type_)})" - lines.append(rst_bullet(attr_line, 2)) + lines.append(rst_bullet(attr_line, 1)) for k in attr.keys(): if k in preprocessed + ignored: continue - lines.append(rst_fields(k, sanitize(attr[k]), 3)) + lines.append(rst_fields(k, sanitize(attr[k]), 2)) lines.append("\n") return "\n".join(lines) @@ -218,7 +219,7 @@ def parse_yaml(obj: Dict[str, Any]) -> str: # This is coming from the RST lines.append(rst_subtitle("Summary")) - lines.append(rst_paragraph(obj["doc"], 1)) + lines.append(rst_paragraph(obj["doc"], 0)) # Operations lines.append(rst_subtitle("Operations"))
Donald Hunter <donald.hunter@gmail.com> writes: > > I quickly hacked the diff below to see if it would improve the HTML > rendering. I think the HTML has fewer odd constructs and the indentation > seems better to my eye. My main aim was to ensure that for a given > section, each indentation level uses the same construct, whether it be a > definition list or a bullet list. Or what Jakub said, use more (sub)+titles and fewer bullet lists.
Breno Leitao <leitao@debian.org> writes: > This is a Sphinx extension that parses the Netlink YAML spec files > (Documentation/netlink/specs/), and generates a rst file to be > displayed into Documentation pages. > > Create a new Documentation/networking/netlink_spec page, and a sub-page > for each Netlink spec that needs to be documented, such as ethtool, > devlink, netdev, etc. > > Create a Sphinx directive extension that reads the YAML spec > (located under Documentation/netlink/specs), parses it and returns a RST > string that is inserted where the Sphinx directive was called. So I finally had a chance to look a bit at this; I have a few impressions. First of all, if you put something silly into one of the YAML files, it kills the whole docs build, which is ... not desirable: > Exception occurred: > File "/usr/lib64/python3.11/site-packages/yaml/scanner.py", line 577, in fetch_value > raise ScannerError(None, None, > yaml.scanner.ScannerError: mapping values are not allowed here > in "/stuff/k/git/kernel/Documentation/netlink/specs/ovs_datapath.yaml", line 14, column 9 > That error needs to be caught and handled in some more graceful way. I do have to wonder, though, whether a sphinx extension is the right way to solve this problem. You're essentially implementing a filter that turns one YAML file into one RST file; might it be better to keep that outside of sphinx as a standalone script, invoked by the Makefile? Note that I'm asking because I wonder, I'm not saying I would block an extension-based implementation. Thanks, jon
On Wed, 08 Nov 2023 13:27:28 -0700 Jonathan Corbet wrote: > I do have to wonder, though, whether a sphinx extension is the right way > to solve this problem. You're essentially implementing a filter that > turns one YAML file into one RST file; might it be better to keep that > outside of sphinx as a standalone script, invoked by the Makefile? If we're considering other ways of generating the files - I'd also like to voice a weak preference towards removing the need for the "stub" files. Get all the docs rendered under Documentation/netlink/ with an auto-generated index. This way newcomers won't have to remember to add a stub to get the doc rendered. One fewer thing to worry about during review.
Jonathan Corbet <corbet@lwn.net> writes: > Breno Leitao <leitao@debian.org> writes: > >> This is a Sphinx extension that parses the Netlink YAML spec files >> (Documentation/netlink/specs/), and generates a rst file to be >> displayed into Documentation pages. >> >> Create a new Documentation/networking/netlink_spec page, and a sub-page >> for each Netlink spec that needs to be documented, such as ethtool, >> devlink, netdev, etc. >> >> Create a Sphinx directive extension that reads the YAML spec >> (located under Documentation/netlink/specs), parses it and returns a RST >> string that is inserted where the Sphinx directive was called. > > So I finally had a chance to look a bit at this; I have a few > impressions. > > First of all, if you put something silly into one of the YAML files, it > kills the whole docs build, which is ... not desirable: > >> Exception occurred: >> File "/usr/lib64/python3.11/site-packages/yaml/scanner.py", line 577, in fetch_value >> raise ScannerError(None, None, >> yaml.scanner.ScannerError: mapping values are not allowed here >> in "/stuff/k/git/kernel/Documentation/netlink/specs/ovs_datapath.yaml", line 14, column 9 >> > > That error needs to be caught and handled in some more graceful way. > > I do have to wonder, though, whether a sphinx extension is the right way > to solve this problem. You're essentially implementing a filter that > turns one YAML file into one RST file; might it be better to keep that > outside of sphinx as a standalone script, invoked by the Makefile? > > Note that I'm asking because I wonder, I'm not saying I would block an > extension-based implementation. +1 to this. The .rst generation can then be easily tested independently of the doc build and the stub files could be avoided. Just a note that last year you offered the opposite guidance: https://lore.kernel.org/linux-doc/87tu4zsfse.fsf@meer.lwn.net/ If the preference now is for standalone scripts invoked by the Makefile then this previous patch might be useful: https://lore.kernel.org/linux-doc/20220922115257.99815-2-donald.hunter@gmail.com/ It would be good to document the preferred approach to this kind of doc extension and I'd be happy to contribute an 'Extensions' section for contributing.rst in the doc-guide. > Thanks, > > jon
Donald Hunter <donald.hunter@gmail.com> writes: > Jonathan Corbet <corbet@lwn.net> writes: >> I do have to wonder, though, whether a sphinx extension is the right way >> to solve this problem. You're essentially implementing a filter that >> turns one YAML file into one RST file; might it be better to keep that >> outside of sphinx as a standalone script, invoked by the Makefile? >> >> Note that I'm asking because I wonder, I'm not saying I would block an >> extension-based implementation. > > +1 to this. The .rst generation can then be easily tested independently > of the doc build and the stub files could be avoided. > > Just a note that last year you offered the opposite guidance: > > https://lore.kernel.org/linux-doc/87tu4zsfse.fsf@meer.lwn.net/ Heh ... I totally forgot about that whole discussion ... > If the preference now is for standalone scripts invoked by the Makefile > then this previous patch might be useful: > > https://lore.kernel.org/linux-doc/20220922115257.99815-2-donald.hunter@gmail.com/ > > It would be good to document the preferred approach to this kind of doc > extension and I'd be happy to contribute an 'Extensions' section for > contributing.rst in the doc-guide. I think it will vary depending on what we're trying to do, and I think we're still working it out - part of why I expressed some uncertainty this time around. For something like the kernel-doc or automarkup, where we are modifying existing documents, an extension is the only way to go. In this case, where we are creating new RST files from whole cloth, it's not so clear to me. My feeling (this week at least ;) is that doing it as an extension makes things more complicated without a lot of benefit. FWIW, if something like this is done as a makefile change, I'd do it a bit differently than your linked patch above. Rather than replicate the command through the file, I'd just add a new target: netlink_specs: .../scripts/gen-netlink-rst htmldocs: netlink_specs existing stuff here But that's a detail. Thanks, jon
On Thu, 09 Nov 2023 07:12:38 -0700 Jonathan Corbet wrote: > netlink_specs: > .../scripts/gen-netlink-rst FWIW if we go down that route we probably want to put the script under tools/net/ynl/ and reuse tools/net/ynl/lib/nlspec.py ? It "abstracts away" some basic parsing of the spec, fills in implied attributes etc.
On Thu, Nov 09, 2023 at 07:12:38AM -0700, Jonathan Corbet wrote: > Donald Hunter <donald.hunter@gmail.com> writes: > > > Jonathan Corbet <corbet@lwn.net> writes: > >> I do have to wonder, though, whether a sphinx extension is the right way > >> to solve this problem. You're essentially implementing a filter that > >> turns one YAML file into one RST file; might it be better to keep that > >> outside of sphinx as a standalone script, invoked by the Makefile? > >> > >> Note that I'm asking because I wonder, I'm not saying I would block an > >> extension-based implementation. > > > > +1 to this. The .rst generation can then be easily tested independently > > of the doc build and the stub files could be avoided. > > > > Just a note that last year you offered the opposite guidance: > > > > https://lore.kernel.org/linux-doc/87tu4zsfse.fsf@meer.lwn.net/ > > Heh ... I totally forgot about that whole discussion ... > > > If the preference now is for standalone scripts invoked by the Makefile > > then this previous patch might be useful: > > > > https://lore.kernel.org/linux-doc/20220922115257.99815-2-donald.hunter@gmail.com/ > > > > It would be good to document the preferred approach to this kind of doc > > extension and I'd be happy to contribute an 'Extensions' section for > > contributing.rst in the doc-guide. > > I think it will vary depending on what we're trying to do, and I think > we're still working it out - part of why I expressed some uncertainty > this time around. > > For something like the kernel-doc or automarkup, where we are modifying > existing documents, an extension is the only way to go. In this case, > where we are creating new RST files from whole cloth, it's not so clear > to me. My feeling (this week at least ;) is that doing it as an > extension makes things more complicated without a lot of benefit. One way or another works for me. Given my experience with both ways, let me share the advantages of boths so we can understand the trade-offs better: Sphinx extension advantages: =========================== 1) Keep "extensions" uniform and organized, written in the same framework/language (python), using the same enry point, output, etc. 2) Easy to cross reference objects in the whole documentation (not done in this patchset) 3) Same dependencies for all "documentation". I.e, you don't need a dependency (as in python pip requirements.txt) for every "parser" script. 4) Already being used in our infrastructure. One-off parser advantages: ========================= 1) Total flexibility. You can write the parser in any language. 2) Easier and faster to interate and debug. 3) No need to have a rst stub for every file (as in this patchset). This is a sphinx limitation right now, and *might* go away in the future. 4) Less dependent of sphinx project. > FWIW, if something like this is done as a makefile change, I'd do it a > bit differently than your linked patch above. Rather than replicate the > command through the file, I'd just add a new target: > > netlink_specs: > .../scripts/gen-netlink-rst > > htmldocs: netlink_specs > existing stuff here > > But that's a detail. For that, we can't use a sphinx extension, since there is no way (as I understood) from one rst to generate multiple rst.
On Wed, Nov 08, 2023 at 02:03:34PM +0000, Donald Hunter wrote: > Breno Leitao <leitao@debian.org> writes: > > > This is a Sphinx extension that parses the Netlink YAML spec files > > (Documentation/netlink/specs/), and generates a rst file to be > > displayed into Documentation pages. > > > > Create a new Documentation/networking/netlink_spec page, and a sub-page > > for each Netlink spec that needs to be documented, such as ethtool, > > devlink, netdev, etc. > > > > Create a Sphinx directive extension that reads the YAML spec > > (located under Documentation/netlink/specs), parses it and returns a RST > > string that is inserted where the Sphinx directive was called. > > This is great! Looks like I need to fill in some missing docs in the > specs I have contributed. > > I wonder if the generated .rst content can be adjusted to improve the > resulting HTML. > > There are a couple of places where paragraph text is indented and I > don't think it needs to be, e.g. the 'Summary' doc. > > A lot of the .rst content seems to be over-indented which causes > blockquote tags to be generated in the HTML. That combined with a > mixture of bullets and definition lists at the same indentation level > seems to produce HTML with inconsistent indentation. > > I quickly hacked the diff below to see if it would improve the HTML > rendering. I think the HTML has fewer odd constructs and the indentation > seems better to my eye. My main aim was to ensure that for a given > section, each indentation level uses the same construct, whether it be a > definition list or a bullet list. Thanks for the diff. That makes total sense and I will integrate it in the updated version. > It would be great to generate links from e.g. an attribute-set to its > definition. > > Did you intentionally leave out the protocol values? Yes. This could be done in a follow up patch if necessary. > > It looks like parse_entries will need to be extended to include the type > information for struct members, similar to how attribute sets are shown. > I'd be happy to look at this as a follow up patch, unless you get there > first. Awesome. That would be appreciate.
Jakub Kicinski <kuba@kernel.org> writes: > On Wed, 08 Nov 2023 13:27:28 -0700 Jonathan Corbet wrote: >> I do have to wonder, though, whether a sphinx extension is the right way >> to solve this problem. You're essentially implementing a filter that >> turns one YAML file into one RST file; might it be better to keep that >> outside of sphinx as a standalone script, invoked by the Makefile? > > If we're considering other ways of generating the files - I'd also like > to voice a weak preference towards removing the need for the "stub" > files. > > Get all the docs rendered under Documentation/netlink/ with an > auto-generated index. FWIW the index could use a toctree glob pattern like we do in Documentation/bpf/maps.rst then it wouldn't need to be auto-generated. > This way newcomers won't have to remember to add a stub to get the doc > rendered. One fewer thing to worry about during review.
diff --git a/Documentation/conf.py b/Documentation/conf.py index d4fdf6a3875a..10ce47d1a7df 100644 --- a/Documentation/conf.py +++ b/Documentation/conf.py @@ -55,7 +55,7 @@ needs_sphinx = '1.7' extensions = ['kerneldoc', 'rstFlatTable', 'kernel_include', 'kfigure', 'sphinx.ext.ifconfig', 'automarkup', 'maintainers_include', 'sphinx.ext.autosectionlabel', - 'kernel_abi', 'kernel_feat'] + 'kernel_abi', 'kernel_feat', 'netlink_spec'] if major >= 3: if (major > 3) or (minor > 0 or patch >= 2): diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst index 5b75c3f7a137..ee3a2085af71 100644 --- a/Documentation/networking/index.rst +++ b/Documentation/networking/index.rst @@ -55,6 +55,7 @@ Contents: filter generic-hdlc generic_netlink + netlink_spec/index gen_stats gtp ila diff --git a/Documentation/networking/netlink_spec/devlink.rst b/Documentation/networking/netlink_spec/devlink.rst new file mode 100644 index 000000000000..ca4b98e29690 --- /dev/null +++ b/Documentation/networking/netlink_spec/devlink.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================== +Family ``devlink`` netlink specification +======================================== + +.. contents:: + +.. netlink-spec:: devlink.yaml diff --git a/Documentation/networking/netlink_spec/ethtool.rst b/Documentation/networking/netlink_spec/ethtool.rst new file mode 100644 index 000000000000..017d5dff427b --- /dev/null +++ b/Documentation/networking/netlink_spec/ethtool.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================== +Family ``ethtool`` netlink specification +======================================== + +.. contents:: + +.. netlink-spec:: ethtool.yaml diff --git a/Documentation/networking/netlink_spec/fou.rst b/Documentation/networking/netlink_spec/fou.rst new file mode 100644 index 000000000000..4db939091f67 --- /dev/null +++ b/Documentation/networking/netlink_spec/fou.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================= +Family ``fou`` netlink specification +======================================= + +.. contents:: + +.. netlink-spec:: fou.yaml diff --git a/Documentation/networking/netlink_spec/handshake.rst b/Documentation/networking/netlink_spec/handshake.rst new file mode 100644 index 000000000000..ed3d79843602 --- /dev/null +++ b/Documentation/networking/netlink_spec/handshake.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================================== +Family ``handshake`` netlink specification +========================================== + +.. contents:: + +.. netlink-spec:: handshake.yaml diff --git a/Documentation/networking/netlink_spec/index.rst b/Documentation/networking/netlink_spec/index.rst new file mode 100644 index 000000000000..b330bda0ea21 --- /dev/null +++ b/Documentation/networking/netlink_spec/index.rst @@ -0,0 +1,21 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================== +Netlink Specifications +====================== + +.. toctree:: + :maxdepth: 2 + + devlink + ethtool + fou + handshake + netdev + ovs_datapath + ovs_flow + ovs_vport + rt_addr + rt_link + rt_route + diff --git a/Documentation/networking/netlink_spec/netdev.rst b/Documentation/networking/netlink_spec/netdev.rst new file mode 100644 index 000000000000..4f43c31805dd --- /dev/null +++ b/Documentation/networking/netlink_spec/netdev.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================= +Family ``netdev`` netlink specification +======================================= + +.. contents:: + +.. netlink-spec:: netdev.yaml diff --git a/Documentation/networking/netlink_spec/ovs_datapath.rst b/Documentation/networking/netlink_spec/ovs_datapath.rst new file mode 100644 index 000000000000..8045a5c93001 --- /dev/null +++ b/Documentation/networking/netlink_spec/ovs_datapath.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================= +Family ``ovs_datapath`` netlink specification +============================================= + +.. contents:: + +.. netlink-spec:: ovs_datapath.yaml diff --git a/Documentation/networking/netlink_spec/ovs_flow.rst b/Documentation/networking/netlink_spec/ovs_flow.rst new file mode 100644 index 000000000000..3a60d75b79b4 --- /dev/null +++ b/Documentation/networking/netlink_spec/ovs_flow.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================================= +Family ``ovs_flow`` netlink specification +========================================= + +.. contents:: + +.. netlink-spec:: ovs_flow.yaml diff --git a/Documentation/networking/netlink_spec/ovs_vport.rst b/Documentation/networking/netlink_spec/ovs_vport.rst new file mode 100644 index 000000000000..2be013c0b524 --- /dev/null +++ b/Documentation/networking/netlink_spec/ovs_vport.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================================== +Family ``ovs_vport`` netlink specification +========================================== + +.. contents:: + +.. netlink-spec:: ovs_vport.yaml diff --git a/Documentation/networking/netlink_spec/rt_addr.rst b/Documentation/networking/netlink_spec/rt_addr.rst new file mode 100644 index 000000000000..ca002646fa5c --- /dev/null +++ b/Documentation/networking/netlink_spec/rt_addr.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================== +Family ``rt_addr`` netlink specification +======================================== + +.. contents:: + +.. netlink-spec:: rt_addr.yaml diff --git a/Documentation/networking/netlink_spec/rt_link.rst b/Documentation/networking/netlink_spec/rt_link.rst new file mode 100644 index 000000000000..e07481a34880 --- /dev/null +++ b/Documentation/networking/netlink_spec/rt_link.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================== +Family ``rt_link`` netlink specification +======================================== + +.. contents:: + +.. netlink-spec:: rt_link.yaml diff --git a/Documentation/networking/netlink_spec/rt_route.rst b/Documentation/networking/netlink_spec/rt_route.rst new file mode 100644 index 000000000000..7fe674dc098e --- /dev/null +++ b/Documentation/networking/netlink_spec/rt_route.rst @@ -0,0 +1,9 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================================= +Family ``rt_route`` netlink specification +========================================= + +.. contents:: + +.. netlink-spec:: rt_route.yaml diff --git a/Documentation/sphinx/netlink_spec.py b/Documentation/sphinx/netlink_spec.py new file mode 100755 index 000000000000..80756e72ed4f --- /dev/null +++ b/Documentation/sphinx/netlink_spec.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# -*- coding: utf-8; mode: python -*- + +""" + netlink-spec + ~~~~~~~~~~~~~~~~~~~ + + Implementation of the ``netlink-spec`` ReST-directive. + + :copyright: Copyright (C) 2023 Breno Leitao <leitao@debian.org> + :license: GPL Version 2, June 1991 see linux/COPYING for details. + + The ``netlink-spec`` reST-directive performs extensive parsing + specific to the Linux kernel's standard netlink specs, in an + effort to avoid needing to heavily mark up the original YAML file. + + This code is split in three big parts: + 1) RST formatters: Use to convert a string to a RST output + 2) Parser helpers: Helper functions to parse the YAML data + 3) NetlinkSpec Directive: The actual directive class +""" + +from typing import Any, Dict, List +import os.path +from docutils.parsers.rst import Directive +from docutils import statemachine +import yaml + +__version__ = "1.0" +SPACE_PER_LEVEL = 4 + +# RST Formatters +def rst_definition(key: str, value: Any, level: int = 0) -> str: + """Format a single rst definition""" + return headroom(level) + key + "\n" + headroom(level + 1) + str(value) + + +def rst_paragraph(paragraph: str, level: int = 0) -> str: + """Return a formatted paragraph""" + return headroom(level) + paragraph + + +def headroom(level: int) -> str: + """Return space to format""" + return " " * (level * SPACE_PER_LEVEL) + + +def rst_bullet(item: str, level: int = 0) -> str: + """Return a formatted a bullet""" + return headroom(level) + f" - {item}" + +def rst_subsubtitle(title: str) -> str: + """Add a sub-sub-title to the document""" + return f"{title}\n" + "~" * len(title) + + +def rst_fields(key: str, value: str, level: int = 0) -> str: + """Return a RST formatted field""" + return headroom(level) + f":{key}: {value}" + + +def rst_subtitle(title: str, level: int = 0) -> str: + """Add a subtitle to the document""" + return headroom(level) + f"\n{title}\n" + "-" * len(title) + + +def rst_list_inline(list_: List[str], level: int = 0) -> str: + """Format a list using inlines""" + return headroom(level) + "[" + ", ".join(inline(i) for i in list_) + "]" + + +def bold(text: str) -> str: + """Format bold text""" + return f"**{text}**" + + +def inline(text: str) -> str: + """Format inline text""" + return f"``{text}``" + + +def sanitize(text: str) -> str: + """Remove newlines and multiple spaces""" + # This is useful for some fields that are spread in multiple lines + return str(text).replace("\n", "").strip() + + +# Parser helpers +# ============== +def parse_mcast_group(mcast_group: List[Dict[str, Any]]) -> str: + """Parse 'multicast' group list and return a formatted string""" + lines = [] + for group in mcast_group: + lines.append(rst_paragraph(group["name"], 1)) + + return "\n".join(lines) + + +def parse_do(do_dict: Dict[str, Any], level: int = 0) -> str: + """Parse 'do' section and return a formatted string""" + lines = [] + for key in do_dict.keys(): + lines.append(rst_bullet(bold(key), level + 1)) + lines.append(parse_do_attributes(do_dict[key], level + 1) + "\n") + + return "\n".join(lines) + + +def parse_do_attributes(attrs: Dict[str, Any], level: int = 0) -> str: + """Parse 'attributes' section""" + if "attributes" not in attrs: + return "" + lines = [rst_fields("attributes", rst_list_inline(attrs["attributes"]), level + 1)] + + return "\n".join(lines) + + +def parse_operations(operations: List[Dict[str, Any]]) -> str: + """Parse operations block""" + preprocessed = ["name", "doc", "title", "do", "dump"] + lines = [] + + for operation in operations: + lines.append(rst_subsubtitle(operation["name"])) + lines.append(rst_paragraph(operation["doc"]) + "\n") + if "do" in operation: + lines.append(rst_paragraph(bold("do"), 1)) + lines.append(parse_do(operation["do"], 1)) + if "dump" in operation: + lines.append(rst_paragraph(bold("dump"), 1)) + lines.append(parse_do(operation["dump"], 1)) + + for key in operation.keys(): + if key in preprocessed: + # Skip the special fields + continue + lines.append(rst_fields(key, operation[key], 1)) + + # New line after fields + lines.append("\n") + + return "\n".join(lines) + + +def parse_entries(entries: List[Dict[str, Any]], level: int) -> str: + """Parse a list of entries""" + lines = [] + for entry in entries: + if isinstance(entry, dict): + # entries could be a list or a dictionary + lines.append( + rst_fields(entry.get("name"), sanitize(entry.get("doc")), level) + ) + elif isinstance(entry, list): + lines.append(rst_list_inline(entry, level)) + else: + lines.append(rst_bullet(inline(sanitize(entry)), level)) + + lines.append("\n") + return "\n".join(lines) + + +def parse_definitions(defs: Dict[str, Any]) -> str: + """Parse definitions section""" + preprocessed = ["name", "entries", "members"] + ignored = ["render-max"] # This is not printed + lines = [] + + for definition in defs: + lines.append(rst_subsubtitle(definition["name"])) + for k in definition.keys(): + if k in preprocessed + ignored: + continue + lines.append(rst_fields(k, sanitize(definition[k]), 1)) + + # Field list needs to finish with a new line + lines.append("\n") + if "entries" in definition: + lines.append(rst_paragraph(bold("Entries"), 1)) + lines.append(parse_entries(definition["entries"], 2)) + if "members" in definition: + lines.append(rst_paragraph(bold("members"), 1)) + lines.append(parse_entries(definition["members"], 2)) + + return "\n".join(lines) + + +def parse_attributes_set(entries: List[Dict[str, Any]]) -> str: + """Parse attribute from attribute-set""" + preprocessed = ["name", "type"] + ignored = ["checks"] + lines = [] + + for entry in entries: + lines.append(rst_bullet(bold(entry["name"]))) + for attr in entry["attributes"]: + type_ = attr.get("type") + attr_line = bold(attr["name"]) + if type_: + # Add the attribute type in the same line + attr_line += f" ({inline(type_)})" + + lines.append(rst_bullet(attr_line, 2)) + + for k in attr.keys(): + if k in preprocessed + ignored: + continue + lines.append(rst_fields(k, sanitize(attr[k]), 3)) + lines.append("\n") + + return "\n".join(lines) + + +def parse_yaml(obj: Dict[str, Any]) -> str: + """Format the whole yaml into a RST string""" + lines = [] + + # This is coming from the RST + lines.append(rst_subtitle("Summary")) + lines.append(rst_paragraph(obj["doc"], 1)) + + # Operations + lines.append(rst_subtitle("Operations")) + lines.append(parse_operations(obj["operations"]["list"])) + + # Multicast groups + if "mcast-groups" in obj: + lines.append(rst_subtitle("Multicast groups")) + lines.append(parse_mcast_group(obj["mcast-groups"]["list"])) + + # Definitions + lines.append(rst_subtitle("Definitions")) + lines.append(parse_definitions(obj["definitions"])) + + # Attributes set + lines.append(rst_subtitle("Attribute sets")) + lines.append(parse_attributes_set(obj["attribute-sets"])) + + return "\n".join(lines) + + +def parse_yaml_file(filename: str, debug: bool = False) -> str: + """Transform the yaml specified by filename into a rst-formmated string""" + with open(filename, "r") as file: + yaml_data = yaml.safe_load(file) + content = parse_yaml(yaml_data) + + if debug: + # Save the rst for inspection + print(content, file=open(f"/tmp/{filename.split('/')[-1]}.rst", "w")) + + return content + + +# Main Sphinx Extension class +def setup(app): + """Sphinx-build register function for 'netlink-spec' directive""" + app.add_directive("netlink-spec", NetlinkSpec) + return dict(version=__version__, parallel_read_safe=True, parallel_write_safe=True) + + +class NetlinkSpec(Directive): + """NetlinkSpec (``netlink-spec``) directive class""" + has_content = True + # Argument is the filename to process + required_arguments = 1 + + def run(self): + srctree = os.path.abspath(os.environ["srctree"]) + yaml_file = os.path.join( + srctree, "Documentation/netlink/specs", self.arguments[0] + ) + self.state.document.settings.record_dependencies.add(yaml_file) + + try: + content = parse_yaml_file(yaml_file) + except FileNotFoundError as exception: + raise self.severe(str(exception)) + + self.state_machine.insert_input(statemachine.string2lines(content), yaml_file) + + return [] diff --git a/Documentation/sphinx/requirements.txt b/Documentation/sphinx/requirements.txt index 335b53df35e2..a8a1aff6445e 100644 --- a/Documentation/sphinx/requirements.txt +++ b/Documentation/sphinx/requirements.txt @@ -1,3 +1,4 @@ # jinja2>=3.1 is not compatible with Sphinx<4.0 jinja2<3.1 Sphinx==2.4.4 +pyyaml
This is a Sphinx extension that parses the Netlink YAML spec files (Documentation/netlink/specs/), and generates a rst file to be displayed into Documentation pages. Create a new Documentation/networking/netlink_spec page, and a sub-page for each Netlink spec that needs to be documented, such as ethtool, devlink, netdev, etc. Create a Sphinx directive extension that reads the YAML spec (located under Documentation/netlink/specs), parses it and returns a RST string that is inserted where the Sphinx directive was called. Suggested-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Breno Leitao <leitao@debian.org> --- Documentation/conf.py | 2 +- Documentation/networking/index.rst | 1 + .../networking/netlink_spec/devlink.rst | 9 + .../networking/netlink_spec/ethtool.rst | 9 + Documentation/networking/netlink_spec/fou.rst | 9 + .../networking/netlink_spec/handshake.rst | 9 + .../networking/netlink_spec/index.rst | 21 ++ .../networking/netlink_spec/netdev.rst | 9 + .../networking/netlink_spec/ovs_datapath.rst | 9 + .../networking/netlink_spec/ovs_flow.rst | 9 + .../networking/netlink_spec/ovs_vport.rst | 9 + .../networking/netlink_spec/rt_addr.rst | 9 + .../networking/netlink_spec/rt_link.rst | 9 + .../networking/netlink_spec/rt_route.rst | 9 + Documentation/sphinx/netlink_spec.py | 283 ++++++++++++++++++ Documentation/sphinx/requirements.txt | 1 + 16 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 Documentation/networking/netlink_spec/devlink.rst create mode 100644 Documentation/networking/netlink_spec/ethtool.rst create mode 100644 Documentation/networking/netlink_spec/fou.rst create mode 100644 Documentation/networking/netlink_spec/handshake.rst create mode 100644 Documentation/networking/netlink_spec/index.rst create mode 100644 Documentation/networking/netlink_spec/netdev.rst create mode 100644 Documentation/networking/netlink_spec/ovs_datapath.rst create mode 100644 Documentation/networking/netlink_spec/ovs_flow.rst create mode 100644 Documentation/networking/netlink_spec/ovs_vport.rst create mode 100644 Documentation/networking/netlink_spec/rt_addr.rst create mode 100644 Documentation/networking/netlink_spec/rt_link.rst create mode 100644 Documentation/networking/netlink_spec/rt_route.rst create mode 100755 Documentation/sphinx/netlink_spec.py