16803d6e9SPeter Maydell# coding=utf-8 26803d6e9SPeter Maydell# 36803d6e9SPeter Maydell# QEMU hxtool .hx file parsing extension 46803d6e9SPeter Maydell# 56803d6e9SPeter Maydell# Copyright (c) 2020 Linaro 66803d6e9SPeter Maydell# 76803d6e9SPeter Maydell# This work is licensed under the terms of the GNU GPLv2 or later. 86803d6e9SPeter Maydell# See the COPYING file in the top-level directory. 96803d6e9SPeter Maydell"""hxtool is a Sphinx extension that implements the hxtool-doc directive""" 106803d6e9SPeter Maydell 116803d6e9SPeter Maydell# The purpose of this extension is to read fragments of rST 126803d6e9SPeter Maydell# from .hx files, and insert them all into the current document. 136803d6e9SPeter Maydell# The rST fragments are delimited by SRST/ERST lines. 146803d6e9SPeter Maydell# The conf.py file must set the hxtool_srctree config value to 156803d6e9SPeter Maydell# the root of the QEMU source tree. 166803d6e9SPeter Maydell# Each hxtool-doc:: directive takes one argument which is the 176803d6e9SPeter Maydell# path of the .hx file to process, relative to the source tree. 186803d6e9SPeter Maydell 196803d6e9SPeter Maydellimport os 206803d6e9SPeter Maydellimport re 216803d6e9SPeter Maydellfrom enum import Enum 226803d6e9SPeter Maydell 236803d6e9SPeter Maydellfrom docutils import nodes 246803d6e9SPeter Maydellfrom docutils.statemachine import ViewList 256803d6e9SPeter Maydellfrom docutils.parsers.rst import directives, Directive 266803d6e9SPeter Maydellfrom sphinx.errors import ExtensionError 27*dd23f9ecSJohn Snowfrom sphinx.util.docutils import switch_source_input 286803d6e9SPeter Maydellfrom sphinx.util.nodes import nested_parse_with_titles 296803d6e9SPeter Maydellimport sphinx 306803d6e9SPeter Maydell 316803d6e9SPeter Maydell 326803d6e9SPeter Maydell__version__ = '1.0' 336803d6e9SPeter Maydell 3480a046c5SPeter Maydell# We parse hx files with a state machine which may be in one of two 3580a046c5SPeter Maydell# states: reading the C code fragment, or inside a rST fragment. 366803d6e9SPeter Maydellclass HxState(Enum): 376803d6e9SPeter Maydell CTEXT = 1 3880a046c5SPeter Maydell RST = 2 396803d6e9SPeter Maydell 406803d6e9SPeter Maydelldef serror(file, lnum, errtext): 416803d6e9SPeter Maydell """Raise an exception giving a user-friendly syntax error message""" 426803d6e9SPeter Maydell raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum, errtext)) 436803d6e9SPeter Maydell 446803d6e9SPeter Maydelldef parse_directive(line): 456803d6e9SPeter Maydell """Return first word of line, if any""" 46e4b6532cSPaolo Bonzini return re.split(r'\W', line)[0] 476803d6e9SPeter Maydell 486803d6e9SPeter Maydelldef parse_defheading(file, lnum, line): 496803d6e9SPeter Maydell """Handle a DEFHEADING directive""" 506803d6e9SPeter Maydell # The input should be "DEFHEADING(some string)", though note that 516803d6e9SPeter Maydell # the 'some string' could be the empty string. If the string is 526803d6e9SPeter Maydell # empty we ignore the directive -- these are used only to add 536803d6e9SPeter Maydell # blank lines in the plain-text content of the --help output. 546803d6e9SPeter Maydell # 55705f48ccSPeter Maydell # Return the heading text. We strip out any trailing ':' for 56705f48ccSPeter Maydell # consistency with other headings in the rST documentation. 57705f48ccSPeter Maydell match = re.match(r'DEFHEADING\((.*?):?\)', line) 586803d6e9SPeter Maydell if match is None: 596803d6e9SPeter Maydell serror(file, lnum, "Invalid DEFHEADING line") 606803d6e9SPeter Maydell return match.group(1) 616803d6e9SPeter Maydell 626803d6e9SPeter Maydelldef parse_archheading(file, lnum, line): 636803d6e9SPeter Maydell """Handle an ARCHHEADING directive""" 646803d6e9SPeter Maydell # The input should be "ARCHHEADING(some string, other arg)", 656803d6e9SPeter Maydell # though note that the 'some string' could be the empty string. 666803d6e9SPeter Maydell # As with DEFHEADING, empty string ARCHHEADINGs will be ignored. 676803d6e9SPeter Maydell # 68705f48ccSPeter Maydell # Return the heading text. We strip out any trailing ':' for 69705f48ccSPeter Maydell # consistency with other headings in the rST documentation. 70705f48ccSPeter Maydell match = re.match(r'ARCHHEADING\((.*?):?,.*\)', line) 716803d6e9SPeter Maydell if match is None: 726803d6e9SPeter Maydell serror(file, lnum, "Invalid ARCHHEADING line") 736803d6e9SPeter Maydell return match.group(1) 746803d6e9SPeter Maydell 751eeb432aSDavid Woodhousedef parse_srst(file, lnum, line): 761eeb432aSDavid Woodhouse """Handle an SRST directive""" 771eeb432aSDavid Woodhouse # The input should be either "SRST", or "SRST(label)". 781eeb432aSDavid Woodhouse match = re.match(r'SRST(\((.*?)\))?', line) 791eeb432aSDavid Woodhouse if match is None: 801eeb432aSDavid Woodhouse serror(file, lnum, "Invalid SRST line") 811eeb432aSDavid Woodhouse return match.group(2) 821eeb432aSDavid Woodhouse 836803d6e9SPeter Maydellclass HxtoolDocDirective(Directive): 846803d6e9SPeter Maydell """Extract rST fragments from the specified .hx file""" 856803d6e9SPeter Maydell required_argument = 1 866803d6e9SPeter Maydell optional_arguments = 1 876803d6e9SPeter Maydell option_spec = { 886803d6e9SPeter Maydell 'hxfile': directives.unchanged_required 896803d6e9SPeter Maydell } 906803d6e9SPeter Maydell has_content = False 916803d6e9SPeter Maydell 926803d6e9SPeter Maydell def run(self): 936803d6e9SPeter Maydell env = self.state.document.settings.env 946803d6e9SPeter Maydell hxfile = env.config.hxtool_srctree + '/' + self.arguments[0] 956803d6e9SPeter Maydell 966803d6e9SPeter Maydell # Tell sphinx of the dependency 976803d6e9SPeter Maydell env.note_dependency(os.path.abspath(hxfile)) 986803d6e9SPeter Maydell 996803d6e9SPeter Maydell state = HxState.CTEXT 1006803d6e9SPeter Maydell # We build up lines of rST in this ViewList, which we will 1016803d6e9SPeter Maydell # later put into a 'section' node. 1026803d6e9SPeter Maydell rstlist = ViewList() 1036803d6e9SPeter Maydell current_node = None 1046803d6e9SPeter Maydell node_list = [] 1056803d6e9SPeter Maydell 1066803d6e9SPeter Maydell with open(hxfile) as f: 1076803d6e9SPeter Maydell lines = (l.rstrip() for l in f) 1086803d6e9SPeter Maydell for lnum, line in enumerate(lines, 1): 1096803d6e9SPeter Maydell directive = parse_directive(line) 1106803d6e9SPeter Maydell 1116803d6e9SPeter Maydell if directive == 'HXCOMM': 1126803d6e9SPeter Maydell pass 1136803d6e9SPeter Maydell elif directive == 'SRST': 1146803d6e9SPeter Maydell if state == HxState.RST: 1156803d6e9SPeter Maydell serror(hxfile, lnum, 'expected ERST, found SRST') 1166803d6e9SPeter Maydell else: 1176803d6e9SPeter Maydell state = HxState.RST 1181eeb432aSDavid Woodhouse label = parse_srst(hxfile, lnum, line) 1191eeb432aSDavid Woodhouse if label: 1201eeb432aSDavid Woodhouse rstlist.append("", hxfile, lnum - 1) 1211eeb432aSDavid Woodhouse # Build label as _DOCNAME-HXNAME-LABEL 1221eeb432aSDavid Woodhouse hx = os.path.splitext(os.path.basename(hxfile))[0] 1231eeb432aSDavid Woodhouse refline = ".. _" + env.docname + "-" + hx + \ 1241eeb432aSDavid Woodhouse "-" + label + ":" 1251eeb432aSDavid Woodhouse rstlist.append(refline, hxfile, lnum - 1) 1266803d6e9SPeter Maydell elif directive == 'ERST': 12780a046c5SPeter Maydell if state == HxState.CTEXT: 1286803d6e9SPeter Maydell serror(hxfile, lnum, 'expected SRST, found ERST') 1296803d6e9SPeter Maydell else: 1306803d6e9SPeter Maydell state = HxState.CTEXT 1316803d6e9SPeter Maydell elif directive == 'DEFHEADING' or directive == 'ARCHHEADING': 1326803d6e9SPeter Maydell if directive == 'DEFHEADING': 1336803d6e9SPeter Maydell heading = parse_defheading(hxfile, lnum, line) 1346803d6e9SPeter Maydell else: 1356803d6e9SPeter Maydell heading = parse_archheading(hxfile, lnum, line) 1366803d6e9SPeter Maydell if heading == "": 1376803d6e9SPeter Maydell continue 1386803d6e9SPeter Maydell # Put the accumulated rST into the previous node, 1396803d6e9SPeter Maydell # and then start a fresh section with this heading. 1406803d6e9SPeter Maydell if len(rstlist) > 0: 1416803d6e9SPeter Maydell if current_node is None: 1426803d6e9SPeter Maydell # We had some rST fragments before the first 1436803d6e9SPeter Maydell # DEFHEADING. We don't have a section to put 1446803d6e9SPeter Maydell # these in, so rather than magicing up a section, 1456803d6e9SPeter Maydell # make it a syntax error. 1466803d6e9SPeter Maydell serror(hxfile, lnum, 1476803d6e9SPeter Maydell 'first DEFHEADING must precede all rST text') 1486803d6e9SPeter Maydell self.do_parse(rstlist, current_node) 1496803d6e9SPeter Maydell rstlist = ViewList() 1506803d6e9SPeter Maydell if current_node is not None: 1516803d6e9SPeter Maydell node_list.append(current_node) 1526803d6e9SPeter Maydell section_id = 'hxtool-%d' % env.new_serialno('hxtool') 1536803d6e9SPeter Maydell current_node = nodes.section(ids=[section_id]) 1546803d6e9SPeter Maydell current_node += nodes.title(heading, heading) 1556803d6e9SPeter Maydell else: 1566803d6e9SPeter Maydell # Not a directive: put in output if we are in rST fragment 1576803d6e9SPeter Maydell if state == HxState.RST: 1586803d6e9SPeter Maydell # Sphinx counts its lines from 0 1596803d6e9SPeter Maydell rstlist.append(line, hxfile, lnum - 1) 1606803d6e9SPeter Maydell 1616803d6e9SPeter Maydell if current_node is None: 1626803d6e9SPeter Maydell # We don't have multiple sections, so just parse the rst 1636803d6e9SPeter Maydell # fragments into a dummy node so we can return the children. 1646803d6e9SPeter Maydell current_node = nodes.section() 1656803d6e9SPeter Maydell self.do_parse(rstlist, current_node) 1666803d6e9SPeter Maydell return current_node.children 1676803d6e9SPeter Maydell else: 1686803d6e9SPeter Maydell # Put the remaining accumulated rST into the last section, and 1696803d6e9SPeter Maydell # return all the sections. 1706803d6e9SPeter Maydell if len(rstlist) > 0: 1716803d6e9SPeter Maydell self.do_parse(rstlist, current_node) 1726803d6e9SPeter Maydell node_list.append(current_node) 1736803d6e9SPeter Maydell return node_list 1746803d6e9SPeter Maydell 1756803d6e9SPeter Maydell # This is from kerneldoc.py -- it works around an API change in 1766803d6e9SPeter Maydell # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use 1776803d6e9SPeter Maydell # sphinx.util.nodes.nested_parse_with_titles() rather than the 1786803d6e9SPeter Maydell # plain self.state.nested_parse(), and so we can drop the saving 1796803d6e9SPeter Maydell # of title_styles and section_level that kerneldoc.py does, 1806803d6e9SPeter Maydell # because nested_parse_with_titles() does that for us. 1816803d6e9SPeter Maydell def do_parse(self, result, node): 1826803d6e9SPeter Maydell with switch_source_input(self.state, result): 1836803d6e9SPeter Maydell nested_parse_with_titles(self.state, result, node) 184*dd23f9ecSJohn Snow 1856803d6e9SPeter Maydell 1866803d6e9SPeter Maydelldef setup(app): 1876803d6e9SPeter Maydell """ Register hxtool-doc directive with Sphinx""" 1886803d6e9SPeter Maydell app.add_config_value('hxtool_srctree', None, 'env') 1896803d6e9SPeter Maydell app.add_directive('hxtool-doc', HxtoolDocDirective) 1906803d6e9SPeter Maydell 1916803d6e9SPeter Maydell return dict( 1926803d6e9SPeter Maydell version = __version__, 1936803d6e9SPeter Maydell parallel_read_safe = True, 1946803d6e9SPeter Maydell parallel_write_safe = True 1956803d6e9SPeter Maydell ) 196