doc: Introduce reqparse, the plain text requirement parser

Signed-off-by: Robert Baldyga <robert.baldyga@intel.com>
This commit is contained in:
Robert Baldyga 2021-02-03 19:35:42 +01:00
parent ff00dab04c
commit a901475f1b
2 changed files with 195 additions and 0 deletions

153
doc/reqparse Executable file
View File

@ -0,0 +1,153 @@
#!/usr/bin/env python3
#
# Copyright(c) 2021 Intel Corporation
# SPDX-License-Identifier: BSD-3-Clause-Clear
#
import argparse
import enum
import glob
import io
import markdown
import os
import sys
import yaml
import yaml.scanner
def error(filename, line, column, string):
string = "\n ".join(string.split('\n'))
print(f"[ERROR] {filename}: line {line}, column {column}: {string}", file=sys.stderr)
exit(1)
class Entity:
def __init__(self):
self.header_text = ""
self.header = None
self.text = ""
class State(enum.Enum):
BEGIN = enum.auto()
GROUP_HEADER = enum.auto()
GROUP_TEXT = enum.auto()
REQ_HEADER_PRE = enum.auto()
REQ_HEADER = enum.auto()
REQ_TEXT = enum.auto()
def parse_header(entity, filename, line):
try:
entity.header = yaml.safe_load(entity.header_text)
except yaml.scanner.ScannerError as e:
error(filename, line+e.problem_mark.line+1,
e.problem_mark.column+1, f"{e.context}\n{e.problem}")
def parse_file(filename):
state = State.BEGIN
group = None
reqs = []
current_entity = None
header_line = 0
with open(filename, "r") as f:
for i, l in enumerate(f.readlines(), start=1):
if l.strip() == "---":
if state == State.BEGIN:
current_entity = Entity()
state = State.GROUP_HEADER
header_line = i
elif state == State.GROUP_HEADER:
parse_header(current_entity, filename, header_line)
state = State.GROUP_TEXT
elif state == State.GROUP_TEXT:
error(filename, i, 1, "unexpected \"---\",\n"
"expected markdown or req header marker")
elif state == State.REQ_HEADER_PRE:
error(filename, i, 1, "unexpected \"---\",\n"
"expected another line of req header marker")
elif state == State.REQ_HEADER:
parse_header(current_entity, filename, header_line)
state = State.REQ_TEXT
elif state == State.REQ_TEXT:
error(filename, i, 1, "unexpected \"---\",\n"
"expected markdown or next req header marker")
else:
error(filename, i, 1, "something went terribly wrong")
elif l.strip() == "-"*80:
if state == State.BEGIN:
error(filename, i, 1, "unexpected req header marker,\n"
"expected group header marker")
elif state == State.GROUP_HEADER:
error(filename, i, 1, "unexpected req header marker,\n"
"expected yaml or group header end marker")
elif state == State.GROUP_TEXT:
group = current_entity
current_entity = None
state = State.REQ_HEADER_PRE
elif state == State.REQ_HEADER_PRE:
current_entity = Entity()
state = State.REQ_HEADER
header_line = i
elif state == State.REQ_HEADER:
error(filename, i, 1, "unexpected req header marker,\n"
"expected markdown or req header end marker")
elif state == State.REQ_TEXT:
reqs.append(current_entity)
current_entity = None
state = State.REQ_HEADER_PRE
else:
error(filename, i, 1, "something went terribly wrong")
else:
if state == State.BEGIN:
error(filename, i, 1, "unexpected character, expected group header")
elif state == State.GROUP_HEADER:
current_entity.header_text += l
elif state == State.GROUP_TEXT:
current_entity.text += l
elif state == State.REQ_HEADER_PRE:
error(filename, i, 1, "expected another line of req header marker")
elif state == State.REQ_HEADER:
current_entity.header_text += l
elif state == State.REQ_TEXT:
current_entity.text += l
else:
error(filename, i, 1, "something went terribly wrong")
if state == State.REQ_TEXT:
reqs.append(current_entity)
return (group, reqs)
parser = argparse.ArgumentParser(prog="reqparse")
parser.add_argument(
"-o", "--output", action="store",
help="Output file"
)
parser.add_argument(
"-f", "--format", action="store",
choices=["html", "markdown"], default="markdown",
help="Output format {html|markdown}",
)
args = parser.parse_args(sys.argv[1:])
buf = ""
with io.StringIO() as output:
for filename in glob.glob("requirements/*"):
group, reqs = parse_file(filename)
output.write(f"# {group.header['group']}\n")
output.write(group.text)
for r in reqs:
output.write(f"## {r.header['title']}\n")
output.write(r.text)
buf = output.getvalue()
if args.format == "html":
buf = markdown.markdown(buf, extensions=['sane_lists'])
if args.output:
with open(args.output, "w+") as outfile:
outfile.write(buf)
else:
print(buf)

42
doc/requirement_example Normal file
View File

@ -0,0 +1,42 @@
---
group: Requirement group title
---
Requirement group description.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
title: Requirement about requirements
id: req_syntax
---
It shall be possible specify requirement header and body.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
title: Markdown in requirements body
id: req_body_markdown
---
It shall be possible use markdown language in requirement body.
The feature list includes:
- **bold**, *italics* text
- ordered and unordered lists
- tables
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
title: YAML in requirements header
id: req_header_yaml
---
It shall be possible to put any YAML content in the requirement header
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
title: Multiple requirements
id: multiple_requirements
---
It shall be possible to place multiple requirements in one file.