
Drop pycodestyle for code style checking in favor of black. Black is much faster, stable PEP8 compliant code style checker offering also automatic formatting. It aims to be very stable and produce smallest diffs. It's used by many small and big projects. Running checkstyle with black takes a few seconds with a terse output. Thus, test-checkstyle-diff is no longer necessary. Expand scope of checkstyle to all python files in the repo, replacing test-checkstyle with checkstyle-python. Also, fixstyle-python is now available for automatic style formatting. Note: python virtualenv has been consolidated in test/Makefile, test/requirements*.txt which will eventually be moved to a central location. This is required to simply the automated generation of docker executor images in the CI. Type: improvement Change-Id: I022a326603485f58585e879ac0f697fceefbc9c8 Signed-off-by: Klement Sekera <klement.sekera@gmail.com> Signed-off-by: Dave Wallace <dwallacelf@gmail.com>
320 lines
8.5 KiB
Python
Executable File
320 lines
8.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import os
|
|
import os.path
|
|
import ipaddress
|
|
import yaml
|
|
from pprint import pprint
|
|
import re
|
|
from jsonschema import validate, exceptions
|
|
import argparse
|
|
from subprocess import run, PIPE
|
|
from io import StringIO
|
|
import urllib.parse
|
|
|
|
# VPP feature JSON schema
|
|
schema = {
|
|
"$schema": "http://json-schema.org/schema#",
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"description": {"type": "string"},
|
|
"maintainer": {"$ref": "#/definitions/maintainers"},
|
|
"state": {
|
|
"type": "string",
|
|
"enum": ["production", "experimental", "development"],
|
|
},
|
|
"features": {"$ref": "#/definitions/features"},
|
|
"missing": {"$ref": "#/definitions/features"},
|
|
"properties": {
|
|
"type": "array",
|
|
"items": {"type": "string", "enum": ["API", "CLI", "STATS", "MULTITHREAD"]},
|
|
},
|
|
},
|
|
"additionalProperties": False,
|
|
"definitions": {
|
|
"maintainers": {
|
|
"anyof": [
|
|
{
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"minItems": 1,
|
|
},
|
|
{"type": "string"},
|
|
],
|
|
},
|
|
"featureobject": {
|
|
"type": "object",
|
|
"patternProperties": {
|
|
"^.*$": {"$ref": "#/definitions/features"},
|
|
},
|
|
},
|
|
"features": {
|
|
"type": "array",
|
|
"items": {
|
|
"anyOf": [
|
|
{"$ref": "#/definitions/featureobject"},
|
|
{"type": "string"},
|
|
]
|
|
},
|
|
"minItems": 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
DEFAULT_REPO_LINK = "https://github.com/FDio/vpp/blob/master/"
|
|
|
|
|
|
def filelist_from_git_status():
|
|
filelist = []
|
|
git_status = "git status --porcelain */FEATURE*.yaml"
|
|
rv = run(git_status.split(), stdout=PIPE, stderr=PIPE)
|
|
if rv.returncode != 0:
|
|
sys.exit(rv.returncode)
|
|
|
|
for l in rv.stdout.decode("ascii").split("\n"):
|
|
if len(l):
|
|
filelist.append(l.split()[1])
|
|
return filelist
|
|
|
|
|
|
def filelist_from_git_ls():
|
|
filelist = []
|
|
git_ls = "git ls-files :(top)*/FEATURE*.yaml"
|
|
rv = run(git_ls.split(), stdout=PIPE, stderr=PIPE)
|
|
if rv.returncode != 0:
|
|
sys.exit(rv.returncode)
|
|
|
|
for l in rv.stdout.decode("ascii").split("\n"):
|
|
if len(l):
|
|
filelist.append(l)
|
|
return filelist
|
|
|
|
|
|
def version_from_git():
|
|
git_describe = "git describe"
|
|
rv = run(git_describe.split(), stdout=PIPE, stderr=PIPE)
|
|
if rv.returncode != 0:
|
|
sys.exit(rv.returncode)
|
|
return rv.stdout.decode("ascii").split("\n")[0]
|
|
|
|
|
|
class MarkDown:
|
|
_dispatch = {}
|
|
|
|
def __init__(self, stream):
|
|
self.stream = stream
|
|
self.toc = []
|
|
|
|
def print_maintainer(self, o):
|
|
write = self.stream.write
|
|
if type(o) is list:
|
|
write("Maintainers: " + ", ".join("{m}".format(m=m) for m in o) + " \n")
|
|
else:
|
|
write("Maintainer: {o} \n".format(o=o))
|
|
|
|
_dispatch["maintainer"] = print_maintainer
|
|
|
|
def print_features(self, o, indent=0):
|
|
write = self.stream.write
|
|
for f in o:
|
|
indentstr = " " * indent
|
|
if type(f) is dict:
|
|
for k, v in f.items():
|
|
write("{indentstr}- {k}\n".format(indentstr=indentstr, k=k))
|
|
self.print_features(v, indent + 2)
|
|
else:
|
|
write("{indentstr}- {f}\n".format(indentstr=indentstr, f=f))
|
|
write("\n")
|
|
|
|
_dispatch["features"] = print_features
|
|
|
|
def print_markdown_header(self, o):
|
|
write = self.stream.write
|
|
write("## {o}\n".format(o=o))
|
|
|
|
_dispatch["markdown_header"] = print_markdown_header
|
|
|
|
def print_name(self, o):
|
|
write = self.stream.write
|
|
write("### {o}\n".format(o=o))
|
|
self.toc.append(o)
|
|
|
|
_dispatch["name"] = print_name
|
|
|
|
def print_description(self, o):
|
|
write = self.stream.write
|
|
write("\n{o}\n\n".format(o=o))
|
|
|
|
_dispatch["description"] = print_description
|
|
|
|
def print_state(self, o):
|
|
write = self.stream.write
|
|
write("Feature maturity level: {o} \n".format(o=o))
|
|
|
|
_dispatch["state"] = print_state
|
|
|
|
def print_properties(self, o):
|
|
write = self.stream.write
|
|
write("Supports: {s} \n".format(s=" ".join(o)))
|
|
|
|
_dispatch["properties"] = print_properties
|
|
|
|
def print_missing(self, o):
|
|
write = self.stream.write
|
|
write("\nNot yet implemented: \n")
|
|
self.print_features(o)
|
|
|
|
_dispatch["missing"] = print_missing
|
|
|
|
def print_code(self, o):
|
|
write = self.stream.write
|
|
write("Source Code: [{o}]({o}) \n".format(o=o))
|
|
|
|
_dispatch["code"] = print_code
|
|
|
|
def print(self, t, o):
|
|
write = self.stream.write
|
|
if t in self._dispatch:
|
|
self._dispatch[t](
|
|
self,
|
|
o,
|
|
)
|
|
else:
|
|
write("NOT IMPLEMENTED: {t}\n")
|
|
|
|
|
|
def output_toc(toc, stream):
|
|
write = stream.write
|
|
write("# VPP Supported Features\n")
|
|
|
|
for t in toc:
|
|
ref = t.lower().replace(" ", "-")
|
|
write("[{t}](#{ref}) \n".format(t=t, ref=ref))
|
|
|
|
|
|
def featuresort(k):
|
|
return k[1]["name"]
|
|
|
|
|
|
def featurelistsort(k):
|
|
orderedfields = {
|
|
"name": 0,
|
|
"maintainer": 1,
|
|
"description": 2,
|
|
"features": 3,
|
|
"state": 4,
|
|
"properties": 5,
|
|
"missing": 6,
|
|
"code": 7,
|
|
}
|
|
return orderedfields[k[0]]
|
|
|
|
|
|
def output_markdown(features, fields, notfields, repository_url):
|
|
stream = StringIO()
|
|
m = MarkDown(stream)
|
|
m.print("markdown_header", "Feature Details:")
|
|
for path, featuredef in sorted(features.items(), key=featuresort):
|
|
codeurl = urllib.parse.urljoin(repository_url, os.path.dirname(path))
|
|
featuredef["code"] = codeurl
|
|
for k, v in sorted(featuredef.items(), key=featurelistsort):
|
|
if notfields:
|
|
if k not in notfields:
|
|
m.print(k, v)
|
|
elif fields:
|
|
if k in fields:
|
|
m.print(k, v)
|
|
else:
|
|
m.print(k, v)
|
|
|
|
tocstream = StringIO()
|
|
output_toc(m.toc, tocstream)
|
|
return tocstream, stream
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="VPP Feature List.")
|
|
parser.add_argument(
|
|
"--validate",
|
|
dest="validate",
|
|
action="store_true",
|
|
help="validate the FEATURE.yaml file",
|
|
)
|
|
parser.add_argument(
|
|
"--repolink",
|
|
metavar="repolink",
|
|
default=DEFAULT_REPO_LINK,
|
|
help="Link to public repository [%s]" % DEFAULT_REPO_LINK,
|
|
)
|
|
parser.add_argument(
|
|
"--git-status",
|
|
dest="git_status",
|
|
action="store_true",
|
|
help="Get filelist from git status",
|
|
)
|
|
parser.add_argument(
|
|
"--all",
|
|
dest="all",
|
|
action="store_true",
|
|
help="Validate all files in repository",
|
|
)
|
|
parser.add_argument(
|
|
"--markdown",
|
|
dest="markdown",
|
|
action="store_true",
|
|
help="Output feature table in markdown",
|
|
)
|
|
parser.add_argument(
|
|
"infile", nargs="?", type=argparse.FileType("r"), default=sys.stdin
|
|
)
|
|
group = parser.add_mutually_exclusive_group()
|
|
group.add_argument("--include", help="List of fields to include")
|
|
group.add_argument("--exclude", help="List of fields to exclude")
|
|
args = parser.parse_args()
|
|
features = {}
|
|
|
|
if args.git_status:
|
|
filelist = filelist_from_git_status()
|
|
elif args.all:
|
|
filelist = filelist_from_git_ls()
|
|
else:
|
|
filelist = args.infile
|
|
|
|
if args.include:
|
|
fields = args.include.split(",")
|
|
else:
|
|
fields = []
|
|
if args.exclude:
|
|
notfields = args.exclude.split(",")
|
|
else:
|
|
notfields = []
|
|
|
|
for featurefile in filelist:
|
|
featurefile = featurefile.rstrip()
|
|
|
|
# Load configuration file
|
|
with open(featurefile, encoding="utf-8") as f:
|
|
cfg = yaml.load(f, Loader=yaml.SafeLoader)
|
|
try:
|
|
validate(instance=cfg, schema=schema)
|
|
except exceptions.ValidationError:
|
|
print(
|
|
"File does not validate: {featurefile}".format(featurefile=featurefile),
|
|
file=sys.stderr,
|
|
)
|
|
raise
|
|
features[featurefile] = cfg
|
|
|
|
if args.markdown:
|
|
stream = StringIO()
|
|
tocstream, stream = output_markdown(features, fields, notfields, args.repolink)
|
|
print(tocstream.getvalue())
|
|
print(stream.getvalue())
|
|
stream.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|