| 
									
										
										
										
											2020-01-24 16:26:01 +00:00
										 |  |  | # coding=utf-8 | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # QEMU hxtool .hx file parsing extension | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Copyright (c) 2020 Linaro | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This work is licensed under the terms of the GNU GPLv2 or later. | 
					
						
							|  |  |  | # See the COPYING file in the top-level directory. | 
					
						
							|  |  |  | """hxtool is a Sphinx extension that implements the hxtool-doc directive""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # The purpose of this extension is to read fragments of rST | 
					
						
							|  |  |  | # from .hx files, and insert them all into the current document. | 
					
						
							|  |  |  | # The rST fragments are delimited by SRST/ERST lines. | 
					
						
							|  |  |  | # The conf.py file must set the hxtool_srctree config value to | 
					
						
							|  |  |  | # the root of the QEMU source tree. | 
					
						
							|  |  |  | # Each hxtool-doc:: directive takes one argument which is the | 
					
						
							|  |  |  | # path of the .hx file to process, relative to the source tree. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | from enum import Enum | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from docutils import nodes | 
					
						
							|  |  |  | from docutils.statemachine import ViewList | 
					
						
							|  |  |  | from docutils.parsers.rst import directives, Directive | 
					
						
							|  |  |  | from sphinx.errors import ExtensionError | 
					
						
							|  |  |  | from sphinx.util.nodes import nested_parse_with_titles | 
					
						
							|  |  |  | import sphinx | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Sphinx up to 1.6 uses AutodocReporter; 1.7 and later | 
					
						
							|  |  |  | # use switch_source_input. Check borrowed from kerneldoc.py. | 
					
						
							|  |  |  | Use_SSI = sphinx.__version__[:3] >= '1.7' | 
					
						
							|  |  |  | if Use_SSI: | 
					
						
							|  |  |  |     from sphinx.util.docutils import switch_source_input | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     from sphinx.ext.autodoc import AutodocReporter | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | __version__ = '1.0' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 17:17:46 +00:00
										 |  |  | # We parse hx files with a state machine which may be in one of two | 
					
						
							|  |  |  | # states: reading the C code fragment, or inside a rST fragment. | 
					
						
							| 
									
										
										
										
											2020-01-24 16:26:01 +00:00
										 |  |  | class HxState(Enum): | 
					
						
							|  |  |  |     CTEXT = 1 | 
					
						
							| 
									
										
										
										
											2020-03-06 17:17:46 +00:00
										 |  |  |     RST = 2 | 
					
						
							| 
									
										
										
										
											2020-01-24 16:26:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def serror(file, lnum, errtext): | 
					
						
							|  |  |  |     """Raise an exception giving a user-friendly syntax error message""" | 
					
						
							|  |  |  |     raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum, errtext)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_directive(line): | 
					
						
							|  |  |  |     """Return first word of line, if any""" | 
					
						
							|  |  |  |     return re.split('\W', line)[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_defheading(file, lnum, line): | 
					
						
							|  |  |  |     """Handle a DEFHEADING directive""" | 
					
						
							|  |  |  |     # The input should be "DEFHEADING(some string)", though note that | 
					
						
							|  |  |  |     # the 'some string' could be the empty string. If the string is | 
					
						
							|  |  |  |     # empty we ignore the directive -- these are used only to add | 
					
						
							|  |  |  |     # blank lines in the plain-text content of the --help output. | 
					
						
							|  |  |  |     # | 
					
						
							| 
									
										
										
										
											2020-02-28 15:36:08 +00:00
										 |  |  |     # Return the heading text. We strip out any trailing ':' for | 
					
						
							|  |  |  |     # consistency with other headings in the rST documentation. | 
					
						
							|  |  |  |     match = re.match(r'DEFHEADING\((.*?):?\)', line) | 
					
						
							| 
									
										
										
										
											2020-01-24 16:26:01 +00:00
										 |  |  |     if match is None: | 
					
						
							|  |  |  |         serror(file, lnum, "Invalid DEFHEADING line") | 
					
						
							|  |  |  |     return match.group(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_archheading(file, lnum, line): | 
					
						
							|  |  |  |     """Handle an ARCHHEADING directive""" | 
					
						
							|  |  |  |     # The input should be "ARCHHEADING(some string, other arg)", | 
					
						
							|  |  |  |     # though note that the 'some string' could be the empty string. | 
					
						
							|  |  |  |     # As with DEFHEADING, empty string ARCHHEADINGs will be ignored. | 
					
						
							|  |  |  |     # | 
					
						
							| 
									
										
										
										
											2020-02-28 15:36:08 +00:00
										 |  |  |     # Return the heading text. We strip out any trailing ':' for | 
					
						
							|  |  |  |     # consistency with other headings in the rST documentation. | 
					
						
							|  |  |  |     match = re.match(r'ARCHHEADING\((.*?):?,.*\)', line) | 
					
						
							| 
									
										
										
										
											2020-01-24 16:26:01 +00:00
										 |  |  |     if match is None: | 
					
						
							|  |  |  |         serror(file, lnum, "Invalid ARCHHEADING line") | 
					
						
							|  |  |  |     return match.group(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class HxtoolDocDirective(Directive): | 
					
						
							|  |  |  |     """Extract rST fragments from the specified .hx file""" | 
					
						
							|  |  |  |     required_argument = 1 | 
					
						
							|  |  |  |     optional_arguments = 1 | 
					
						
							|  |  |  |     option_spec = { | 
					
						
							|  |  |  |         'hxfile': directives.unchanged_required | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     has_content = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self): | 
					
						
							|  |  |  |         env = self.state.document.settings.env | 
					
						
							|  |  |  |         hxfile = env.config.hxtool_srctree + '/' + self.arguments[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Tell sphinx of the dependency | 
					
						
							|  |  |  |         env.note_dependency(os.path.abspath(hxfile)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         state = HxState.CTEXT | 
					
						
							|  |  |  |         # We build up lines of rST in this ViewList, which we will | 
					
						
							|  |  |  |         # later put into a 'section' node. | 
					
						
							|  |  |  |         rstlist = ViewList() | 
					
						
							|  |  |  |         current_node = None | 
					
						
							|  |  |  |         node_list = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         with open(hxfile) as f: | 
					
						
							|  |  |  |             lines = (l.rstrip() for l in f) | 
					
						
							|  |  |  |             for lnum, line in enumerate(lines, 1): | 
					
						
							|  |  |  |                 directive = parse_directive(line) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if directive == 'HXCOMM': | 
					
						
							|  |  |  |                     pass | 
					
						
							|  |  |  |                 elif directive == 'SRST': | 
					
						
							|  |  |  |                     if state == HxState.RST: | 
					
						
							|  |  |  |                         serror(hxfile, lnum, 'expected ERST, found SRST') | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         state = HxState.RST | 
					
						
							|  |  |  |                 elif directive == 'ERST': | 
					
						
							| 
									
										
										
										
											2020-03-06 17:17:46 +00:00
										 |  |  |                     if state == HxState.CTEXT: | 
					
						
							| 
									
										
										
										
											2020-01-24 16:26:01 +00:00
										 |  |  |                         serror(hxfile, lnum, 'expected SRST, found ERST') | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         state = HxState.CTEXT | 
					
						
							|  |  |  |                 elif directive == 'DEFHEADING' or directive == 'ARCHHEADING': | 
					
						
							|  |  |  |                     if directive == 'DEFHEADING': | 
					
						
							|  |  |  |                         heading = parse_defheading(hxfile, lnum, line) | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         heading = parse_archheading(hxfile, lnum, line) | 
					
						
							|  |  |  |                     if heading == "": | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  |                     # Put the accumulated rST into the previous node, | 
					
						
							|  |  |  |                     # and then start a fresh section with this heading. | 
					
						
							|  |  |  |                     if len(rstlist) > 0: | 
					
						
							|  |  |  |                         if current_node is None: | 
					
						
							|  |  |  |                             # We had some rST fragments before the first | 
					
						
							|  |  |  |                             # DEFHEADING. We don't have a section to put | 
					
						
							|  |  |  |                             # these in, so rather than magicing up a section, | 
					
						
							|  |  |  |                             # make it a syntax error. | 
					
						
							|  |  |  |                             serror(hxfile, lnum, | 
					
						
							|  |  |  |                                    'first DEFHEADING must precede all rST text') | 
					
						
							|  |  |  |                         self.do_parse(rstlist, current_node) | 
					
						
							|  |  |  |                         rstlist = ViewList() | 
					
						
							|  |  |  |                     if current_node is not None: | 
					
						
							|  |  |  |                         node_list.append(current_node) | 
					
						
							|  |  |  |                     section_id = 'hxtool-%d' % env.new_serialno('hxtool') | 
					
						
							|  |  |  |                     current_node = nodes.section(ids=[section_id]) | 
					
						
							|  |  |  |                     current_node += nodes.title(heading, heading) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     # Not a directive: put in output if we are in rST fragment | 
					
						
							|  |  |  |                     if state == HxState.RST: | 
					
						
							|  |  |  |                         # Sphinx counts its lines from 0 | 
					
						
							|  |  |  |                         rstlist.append(line, hxfile, lnum - 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if current_node is None: | 
					
						
							|  |  |  |             # We don't have multiple sections, so just parse the rst | 
					
						
							|  |  |  |             # fragments into a dummy node so we can return the children. | 
					
						
							|  |  |  |             current_node = nodes.section() | 
					
						
							|  |  |  |             self.do_parse(rstlist, current_node) | 
					
						
							|  |  |  |             return current_node.children | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # Put the remaining accumulated rST into the last section, and | 
					
						
							|  |  |  |             # return all the sections. | 
					
						
							|  |  |  |             if len(rstlist) > 0: | 
					
						
							|  |  |  |                 self.do_parse(rstlist, current_node) | 
					
						
							|  |  |  |             node_list.append(current_node) | 
					
						
							|  |  |  |             return node_list | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # This is from kerneldoc.py -- it works around an API change in | 
					
						
							|  |  |  |     # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use | 
					
						
							|  |  |  |     # sphinx.util.nodes.nested_parse_with_titles() rather than the | 
					
						
							|  |  |  |     # plain self.state.nested_parse(), and so we can drop the saving | 
					
						
							|  |  |  |     # of title_styles and section_level that kerneldoc.py does, | 
					
						
							|  |  |  |     # because nested_parse_with_titles() does that for us. | 
					
						
							|  |  |  |     def do_parse(self, result, node): | 
					
						
							|  |  |  |         if Use_SSI: | 
					
						
							|  |  |  |             with switch_source_input(self.state, result): | 
					
						
							|  |  |  |                 nested_parse_with_titles(self.state, result, node) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             save = self.state.memo.reporter | 
					
						
							|  |  |  |             self.state.memo.reporter = AutodocReporter(result, self.state.memo.reporter) | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 nested_parse_with_titles(self.state, result, node) | 
					
						
							|  |  |  |             finally: | 
					
						
							|  |  |  |                 self.state.memo.reporter = save | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def setup(app): | 
					
						
							|  |  |  |     """ Register hxtool-doc directive with Sphinx""" | 
					
						
							|  |  |  |     app.add_config_value('hxtool_srctree', None, 'env') | 
					
						
							|  |  |  |     app.add_directive('hxtool-doc', HxtoolDocDirective) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return dict( | 
					
						
							|  |  |  |         version = __version__, | 
					
						
							|  |  |  |         parallel_read_safe = True, | 
					
						
							|  |  |  |         parallel_write_safe = True | 
					
						
							|  |  |  |     ) |