Peter Maydell <peter.mayd...@linaro.org> writes: > Make the handling of indentation in doc comments more sophisticated, > so that when we see a section like: > > Notes: some text > some more text > indented line 3 > > we save it for the doc-comment processing code as: > > some text > some more text > indented line 3 > > and when we see a section with the heading on its own line: > > Notes: > > some text > some more text > indented text > > we also accept that and save it in the same form. > > If we detect that the comment document text is not indented as much > as we expect it to be, we throw a parse error. (We don't complain > about over-indented sections, because for rST this can be legitimate > markup.) > > The golden reference for the doc comment text is updated to remove > the two 'wrong' indents; these now form a test case that we correctly > stripped leading whitespace from an indented multi-line argument > definition. > > We update the documentation in docs/devel/qapi-code-gen.txt to > describe the new indentation rules. > > Signed-off-by: Peter Maydell <peter.mayd...@linaro.org> > --- > docs/devel/qapi-code-gen.txt | 23 +++++++ > scripts/qapi/parser.py | 93 +++++++++++++++++++++------ > tests/qapi-schema/doc-bad-indent.err | 1 + > tests/qapi-schema/doc-bad-indent.json | 8 +++ > tests/qapi-schema/doc-bad-indent.out | 0 > tests/qapi-schema/doc-good.out | 4 +- > tests/qapi-schema/meson.build | 1 + > 7 files changed, 109 insertions(+), 21 deletions(-) > create mode 100644 tests/qapi-schema/doc-bad-indent.err > create mode 100644 tests/qapi-schema/doc-bad-indent.json > create mode 100644 tests/qapi-schema/doc-bad-indent.out > > diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt > index 9eede44350c..69eaffac376 100644 > --- a/docs/devel/qapi-code-gen.txt > +++ b/docs/devel/qapi-code-gen.txt > @@ -901,6 +901,22 @@ commands and events), member (for structs and unions), > branch (for > alternates), or value (for enums), and finally optional tagged > sections. > > +Descriptions of arguments can span multiple lines. The description > +text can start on the line following the '@argname:', in which case > +it must not be indented at all. It can also start on the same line > +as the '@argname:'. In this case if it spans multiple lines then > +second and subsequent lines must be indented to line up with the > +first character of the first line of the description:
Please put two spaces after sentence-ending punctuation, for local consistency, and to keep Emacs sentence commands working. Can touch this up in my tree, of course. > + > +# @argone: > +# This is a two line description > +# in the first style. > +# > +# @argtwo: This is a two line description > +# in the second style. > + > +The number of spaces between the ':' and the text is not significant. > + > FIXME: the parser accepts these things in almost any order. > FIXME: union branches should be described, too. > > @@ -911,6 +927,13 @@ A tagged section starts with one of the following words: > "Note:"/"Notes:", "Since:", "Example"/"Examples", "Returns:", "TODO:". > The section ends with the start of a new section. > > +The text of a section can start on a new line, in > +which case it must not be indented at all. It can also start > +on the same line as the 'Note:', 'Returns:', etc tag. In this > +case if it spans multiple lines then second and subsequent > +lines must be indented to match the first, in the same way as > +multiline argument descriptions. > + > A 'Since: x.y.z' tagged section lists the release that introduced the > definition. > > diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py > index 04bf10db378..6c3455b41f3 100644 > --- a/scripts/qapi/parser.py > +++ b/scripts/qapi/parser.py > @@ -319,17 +319,32 @@ class QAPIDoc: > """ > > class Section: > - def __init__(self, name=None): > + def __init__(self, parser, name=None, indent=0): > + # parser, for error messages about indentation > + self._parser = parser > # optional section name (argument/member or section name) > self.name = name > self.text = '' > + # the expected indent level of the text of this section > + self._indent = indent > > def append(self, line): > + # Strip leading spaces corresponding to the expected indent level > + # Blank lines are always OK. > + if line: > + indent = re.match(r'\s*', line).end() > + if 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' > > class ArgSection(Section): > - def __init__(self, name): > - super().__init__(name) > + def __init__(self, parser, name, indent=0): > + super().__init__(parser, name, indent) > self.member = None > > def connect(self, member): > @@ -343,7 +358,7 @@ class QAPIDoc: > self._parser = parser > self.info = info > self.symbol = None > - self.body = QAPIDoc.Section() > + self.body = QAPIDoc.Section(parser) > # dict mapping parameter name to ArgSection > self.args = OrderedDict() > self.features = OrderedDict() > @@ -447,8 +462,21 @@ class QAPIDoc: > name = line.split(' ', 1)[0] > > if name.startswith('@') and name.endswith(':'): > - line = line[len(name)+1:] > - self._start_args_section(name[1:-1]) > + # 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 PEP 8: You should use two spaces after a sentence-ending period in multi-sentence comments, except after the final sentence. More of the same below. Can touch it up in my tree. > + # 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 = re.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): > self._append_line = self._append_various_line > self._append_various_line(line) > @@ -469,8 +497,21 @@ class QAPIDoc: > name = line.split(' ', 1)[0] > > if name.startswith('@') and name.endswith(':'): > - line = line[len(name)+1:] > - self._start_features_section(name[1:-1]) > + # 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 = re.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): > self._append_line = self._append_various_line > self._append_various_line(line) > @@ -502,12 +543,25 @@ class QAPIDoc: > "'%s' can't follow '%s' section" > % (name, self.sections[0].name)) > if self._is_section_tag(name): > - line = line[len(name)+1:] > - self._start_section(name[:-1]) > + # 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 = re.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 We may want to de-triplicate. Not today. > + self._start_section(name[:-1], indent) > > self._append_freeform(line) > > - def _start_symbol_section(self, symbols_dict, name): > + def _start_symbol_section(self, symbols_dict, name, indent): > # FIXME invalid names other than the empty string aren't flagged > if not name: > raise QAPIParseError(self._parser, "invalid parameter name") > @@ -516,21 +570,21 @@ class QAPIDoc: > "'%s' parameter name duplicated" % name) > assert not self.sections > self._end_section() > - self._section = QAPIDoc.ArgSection(name) > + self._section = QAPIDoc.ArgSection(self._parser, name, indent) > symbols_dict[name] = self._section > > - def _start_args_section(self, name): > - self._start_symbol_section(self.args, name) > + def _start_args_section(self, name, indent): > + self._start_symbol_section(self.args, name, indent) > > - def _start_features_section(self, name): > - self._start_symbol_section(self.features, name) > + def _start_features_section(self, name, indent): > + self._start_symbol_section(self.features, name, indent) > > - def _start_section(self, name=None): > + def _start_section(self, name=None, indent=0): > if name in ('Returns', 'Since') and self.has_section(name): > raise QAPIParseError(self._parser, > "duplicated '%s' section" % name) > self._end_section() > - self._section = QAPIDoc.Section(name) > + self._section = QAPIDoc.Section(self._parser, name, indent) > self.sections.append(self._section) > > def _end_section(self): > @@ -553,7 +607,8 @@ class QAPIDoc: > def connect_member(self, member): > if member.name not in self.args: > # Undocumented TODO outlaw > - self.args[member.name] = QAPIDoc.ArgSection(member.name) > + self.args[member.name] = QAPIDoc.ArgSection(self._parser, > + member.name) > self.args[member.name].connect(member) > > def connect_feature(self, feature): > diff --git a/tests/qapi-schema/doc-bad-indent.err > b/tests/qapi-schema/doc-bad-indent.err > new file mode 100644 > index 00000000000..67844539bd2 > --- /dev/null > +++ b/tests/qapi-schema/doc-bad-indent.err > @@ -0,0 +1 @@ > +doc-bad-indent.json:6:1: unexpected de-indent (expected at least 4 spaces) > diff --git a/tests/qapi-schema/doc-bad-indent.json > b/tests/qapi-schema/doc-bad-indent.json > new file mode 100644 > index 00000000000..edde8f21dc7 > --- /dev/null > +++ b/tests/qapi-schema/doc-bad-indent.json > @@ -0,0 +1,8 @@ > +# Multiline doc comments should have consistent indentation > + > +## > +# @foo: > +# @a: line one > +# line two is wrongly indented > +## > +{ 'command': 'foo', 'data': { 'a': 'int' } } > diff --git a/tests/qapi-schema/doc-bad-indent.out > b/tests/qapi-schema/doc-bad-indent.out > new file mode 100644 > index 00000000000..e69de29bb2d > diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out > index 9993ffcd89d..b7e3f4313da 100644 > --- a/tests/qapi-schema/doc-good.out > +++ b/tests/qapi-schema/doc-good.out > @@ -159,7 +159,7 @@ doc symbol=Alternate > > arg=i > an integer > - @b is undocumented > +@b is undocumented > arg=b > > feature=alt-feat > @@ -174,7 +174,7 @@ doc symbol=cmd > the first argument > arg=arg2 > the second > - argument > +argument > arg=arg3 > > feature=cmd-feat1 > diff --git a/tests/qapi-schema/meson.build b/tests/qapi-schema/meson.build > index f1449298b07..83a0a68389b 100644 > --- a/tests/qapi-schema/meson.build > +++ b/tests/qapi-schema/meson.build > @@ -53,6 +53,7 @@ schemas = [ > 'doc-bad-enum-member.json', > 'doc-bad-event-arg.json', > 'doc-bad-feature.json', > + 'doc-bad-indent.json', > 'doc-bad-section.json', > 'doc-bad-symbol.json', > 'doc-bad-union-member.json', With trivial whitespace touch-ups: Reviewed-by: Markus Armbruster <arm...@redhat.com>