Expand Up
@@ -346,7 +346,7 @@ def accept(self, skip_comment: bool = True) -> None:
elif not self .tok .isspace ():
# Show up to next structural, whitespace or quote
# character
match = must_match ('[^[\\ ]{}:,\\ s\' " ]+' ,
match = must_match ('[^[\\ ]{}:,\\ s\' ]+' ,
self .src [self .cursor - 1 :])
raise QAPIParseError (self , "stray '%s'" % match .group (0 ))
Expand Down
Expand Up
@@ -468,34 +468,39 @@ class QAPIDoc:
class Section :
# pylint: disable=too-few-public-methods
def __init__ (self , parser : QAPISchemaParser ,
name : Optional [str ] = None , indent : int = 0 ):
name : Optional [str ] = None ):
# parser, for error messages about indentation
self ._parser = parser
# optional section name (argument/member or section name)
self .name = name
# section text without section name
self .text = ''
# the expected indent level of the text of this section
self ._indent = indent
# indentation to strip (None means indeterminate)
self ._indent = None if self . name else 0
def append (self , line : str ) -> None :
# Strip leading spaces corresponding to the expected indent level
# Blank lines are always OK.
line = line . rstrip ()
if line :
indent = must_match (r'\s*' , line ).end ()
if indent < self ._indent :
if self ._indent is None :
# indeterminate indentation
if self .text != '' :
# non-blank, non-first line determines indentation
self ._indent = indent
elif indent < self ._indent :
raise QAPIParseError (
self ._parser ,
"unexpected de-indent (expected at least %d spaces)" %
self ._indent )
line = line [self ._indent :]
self .text += line . rstrip () + '\n '
self .text += line + '\n '
class ArgSection (Section ):
def __init__ (self , parser : QAPISchemaParser ,
name : str , indent : int = 0 ):
super ().__init__ (parser , name , indent )
name : str ):
super ().__init__ (parser , name )
self .member : Optional ['QAPISchemaMember' ] = None
def connect (self , member : 'QAPISchemaMember' ) -> None :
Expand Down
Expand Up
@@ -558,12 +563,12 @@ def end_comment(self) -> None:
self ._switch_section (QAPIDoc .NullSection (self ._parser ))
@staticmethod
def _is_section_tag ( name : str ) -> bool :
return name in ( 'Returns:' , 'Since:' ,
# those are often singular or plural
'Note:' , 'Notes:' ,
'Example:' , 'Examples:' ,
' TODO:' )
def _match_at_name_colon ( string : str ) -> re . Match :
return re . match ( r'@([^:]*): *' , string )
@ staticmethod
def _match_section_tag ( string : str ) -> re . Match :
return re . match ( r'(Returns|Since|Notes?|Examples?| TODO): *' , string )
def _append_body_line (self , line : str ) -> None :
"""
Expand All
@@ -579,7 +584,6 @@ def _append_body_line(self, line: str) -> None:
Else, append the line to the current section.
"""
name = line .split (' ' , 1 )[0 ]
# FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
# recognized, and get silently treated as ordinary text
if not self .symbol and not self .body .text and line .startswith ('@' ):
Expand All
@@ -593,12 +597,12 @@ def _append_body_line(self, line: str) -> None:
self ._parser , "name required after '@'" )
elif self .symbol :
# This is a definition documentation block
if name . startswith ( '@' ) and name . endswith ( ':' ):
if self . _match_at_name_colon ( line ):
self ._append_line = self ._append_args_line
self ._append_args_line (line )
elif line == 'Features:' :
self ._append_line = self ._append_features_line
elif self ._is_section_tag ( name ):
elif self ._match_section_tag ( line ):
self ._append_line = self ._append_various_line
self ._append_various_line (line )
else :
Expand All
@@ -619,25 +623,11 @@ def _append_args_line(self, line: str) -> None:
Else, append the line to the current section.
"""
name = line .split (' ' , 1 )[0 ]
if name .startswith ('@' ) and name .endswith (':' ):
# If line is "@arg: first line of description", find
# the index of 'f', which is the indent we expect for any
# following lines. We then remove the leading "@arg:"
# from line and replace it with spaces so that 'f' has the
# same index as it did in the original line and can be
# handled the same way we will handle following lines.
indent = must_match (r'@\S*:\s*' , line ).end ()
line = line [indent :]
if not line :
# Line was just the "@arg:" header; following lines
# are not indented
indent = 0
else :
line = ' ' * indent + line
self ._start_args_section (name [1 :- 1 ], indent )
elif self ._is_section_tag (name ):
match = self ._match_at_name_colon (line )
if match :
line = line [match .end ():]
self ._start_args_section (match .group (1 ))
elif self ._match_section_tag (line ):
self ._append_line = self ._append_various_line
self ._append_various_line (line )
return
Expand All
@@ -654,25 +644,11 @@ def _append_args_line(self, line: str) -> None:
self ._append_freeform (line )
def _append_features_line (self , line : str ) -> None :
name = line .split (' ' , 1 )[0 ]
if name .startswith ('@' ) and name .endswith (':' ):
# If line is "@arg: first line of description", find
# the index of 'f', which is the indent we expect for any
# following lines. We then remove the leading "@arg:"
# from line and replace it with spaces so that 'f' has the
# same index as it did in the original line and can be
# handled the same way we will handle following lines.
indent = must_match (r'@\S*:\s*' , line ).end ()
line = line [indent :]
if not line :
# Line was just the "@arg:" header; following lines
# are not indented
indent = 0
else :
line = ' ' * indent + line
self ._start_features_section (name [1 :- 1 ], indent )
elif self ._is_section_tag (name ):
match = self ._match_at_name_colon (line )
if match :
line = line [match .end ():]
self ._start_features_section (match .group (1 ))
elif self ._match_section_tag (line ):
self ._append_line = self ._append_various_line
self ._append_various_line (line )
return
Expand All
@@ -696,64 +672,49 @@ def _append_various_line(self, line: str) -> None:
Else, append the line to the current section.
"""
name = line .split (' ' , 1 )[0 ]
if name .startswith ('@' ) and name .endswith (':' ):
match = self ._match_at_name_colon (line )
if match :
raise QAPIParseError (self ._parser ,
"'%s' can't follow '%s' section"
% (name , self .sections [0 ].name ))
if self ._is_section_tag (name ):
# If line is "Section: first line of description", find
# the index of 'f', which is the indent we expect for any
# following lines. We then remove the leading "Section:"
# from line and replace it with spaces so that 'f' has the
# same index as it did in the original line and can be
# handled the same way we will handle following lines.
indent = must_match (r'\S*:\s*' , line ).end ()
line = line [indent :]
if not line :
# Line was just the "Section:" header; following lines
# are not indented
indent = 0
else :
line = ' ' * indent + line
self ._start_section (name [:- 1 ], indent )
"'@%s:' can't follow '%s' section"
% (match .group (1 ), self .sections [0 ].name ))
match = self ._match_section_tag (line )
if match :
line = line [match .end ():]
self ._start_section (match .group (1 ))
self ._append_freeform (line )
def _start_symbol_section (
self ,
symbols_dict : Dict [str , 'QAPIDoc.ArgSection' ],
name : str ,
indent : int ) -> None :
name : str ) -> None :
# FIXME invalid names other than the empty string aren't flagged
if not name :
raise QAPIParseError (self ._parser , "invalid parameter name" )
if name in symbols_dict :
raise QAPIParseError (self ._parser ,
"'%s' parameter name duplicated" % name )
assert not self .sections
new_section = QAPIDoc .ArgSection (self ._parser , name , indent )
new_section = QAPIDoc .ArgSection (self ._parser , name )
self ._switch_section (new_section )
symbols_dict [name ] = new_section
def _start_args_section (self , name : str , indent : int ) -> None :
self ._start_symbol_section (self .args , name , indent )
def _start_args_section (self , name : str ) -> None :
self ._start_symbol_section (self .args , name )
def _start_features_section (self , name : str , indent : int ) -> None :
self ._start_symbol_section (self .features , name , indent )
def _start_features_section (self , name : str ) -> None :
self ._start_symbol_section (self .features , name )
def _start_section (self , name : Optional [str ] = None ,
indent : int = 0 ) -> None :
def _start_section (self , name : Optional [str ] = None ) -> None :
if name in ('Returns' , 'Since' ) and self .has_section (name ):
raise QAPIParseError (self ._parser ,
"duplicated '%s' section" % name )
new_section = QAPIDoc .Section (self ._parser , name , indent )
new_section = QAPIDoc .Section (self ._parser , name )
self ._switch_section (new_section )
self .sections .append (new_section )
def _switch_section (self , new_section : 'QAPIDoc.Section' ) -> None :
text = self ._section .text = self ._section .text .strip ()
text = self ._section .text = self ._section .text .strip (' \n ' )
# Only the 'body' section is allowed to have an empty body.
# All other sections, including anonymous ones, must have text.
Expand Down