| # SPDX-License-Identifier: GPL-2.0 | |
| # Copyright 2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> | |
| """ | |
| Sphinx extension for processing YAML files | |
| """ | |
| import os | |
| import re | |
| import sys | |
| from pprint import pformat | |
| from docutils import statemachine | |
| from docutils.parsers.rst import Parser as RSTParser | |
| from docutils.parsers.rst import states | |
| from docutils.statemachine import ViewList | |
| from sphinx.util import logging | |
| from sphinx.parsers import Parser | |
| srctree = os.path.abspath(os.environ["srctree"]) | |
| sys.path.insert(0, os.path.join(srctree, "tools/net/ynl/pyynl/lib")) | |
| from doc_generator import YnlDocGenerator # pylint: disable=C0413 | |
| logger = logging.getLogger(__name__) | |
| class YamlParser(Parser): | |
| """ | |
| Kernel parser for YAML files. | |
| This is a simple sphinx.Parser to handle yaml files inside the | |
| Kernel tree that will be part of the built documentation. | |
| The actual parser function is not contained here: the code was | |
| written in a way that parsing yaml for different subsystems | |
| can be done from a single dispatcher. | |
| All it takes to have parse YAML patches is to have an import line: | |
| from some_parser_code import NewYamlGenerator | |
| To this module. Then add an instance of the parser with: | |
| new_parser = NewYamlGenerator() | |
| and add a logic inside parse() to handle it based on the path, | |
| like this: | |
| if "/foo" in fname: | |
| msg = self.new_parser.parse_yaml_file(fname) | |
| """ | |
| supported = ('yaml', ) | |
| netlink_parser = YnlDocGenerator() | |
| re_lineno = re.compile(r"\.\. LINENO ([0-9]+)$") | |
| tab_width = 8 | |
| def rst_parse(self, inputstring, document, msg): | |
| """ | |
| Receives a ReST content that was previously converted by the | |
| YAML parser, adding it to the document tree. | |
| """ | |
| self.setup_parse(inputstring, document) | |
| result = ViewList() | |
| self.statemachine = states.RSTStateMachine(state_classes=states.state_classes, | |
| initial_state='Body', | |
| debug=document.reporter.debug_flag) | |
| try: | |
| # Parse message with RSTParser | |
| lineoffset = 0; | |
| lines = statemachine.string2lines(msg, self.tab_width, | |
| convert_whitespace=True) | |
| for line in lines: | |
| match = self.re_lineno.match(line) | |
| if match: | |
| lineoffset = int(match.group(1)) | |
| continue | |
| result.append(line, document.current_source, lineoffset) | |
| self.statemachine.run(result, document) | |
| except Exception as e: | |
| document.reporter.error("YAML parsing error: %s" % pformat(e)) | |
| self.finish_parse() | |
| # Overrides docutils.parsers.Parser. See sphinx.parsers.RSTParser | |
| def parse(self, inputstring, document): | |
| """Check if a YAML is meant to be parsed.""" | |
| fname = document.current_source | |
| # Handle netlink yaml specs | |
| if "/netlink/specs/" in fname: | |
| msg = self.netlink_parser.parse_yaml_file(fname) | |
| self.rst_parse(inputstring, document, msg) | |
| # All other yaml files are ignored | |
| def setup(app): | |
| """Setup function for the Sphinx extension.""" | |
| # Add YAML parser | |
| app.add_source_parser(YamlParser) | |
| app.add_source_suffix('.yaml', 'yaml') | |
| return { | |
| 'version': '1.0', | |
| 'parallel_read_safe': True, | |
| 'parallel_write_safe': True, | |
| } |