X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fscripts%2Ffts.py;h=e8ff477c7887f6fb40f48e26750c8435548a4f94;hb=d9b0c6fbf7aa5bd9af84264105b39c82028a4a29;hp=6d224ddffe461072ad0b42777433b3dd3467a5a4;hpb=6a3064fdf6883fa09b4325e6f011f0e8acad295e;p=vpp.git diff --git a/src/scripts/fts.py b/src/scripts/fts.py index 6d224ddffe4..e8ff477c788 100755 --- a/src/scripts/fts.py +++ b/src/scripts/fts.py @@ -2,13 +2,16 @@ import sys import os +import os.path import ipaddress import yaml from pprint import pprint import re -from jsonschema import validate +from jsonschema import validate, exceptions import argparse from subprocess import run, PIPE +from io import StringIO +import urllib.parse # VPP feature JSON schema schema = { @@ -16,96 +19,260 @@ schema = { "type": "object", "properties": { "name": {"type": "string"}, - "description": { "type": "string" }, - "maintainer": { "type": "string" }, - "state": {"type": "string", - "enum": ["production", "experimental"]}, - "features": { "$ref": "#/definitions/features" }, - "missing": { "$ref": "#/definitions/features" }, - "properties": { "type": "array", - "items": { "type": "string", - "enum": ["API", "CLI", "STATS", "MULTITHREAD"] }, - }, + "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" }, + "^.*$": {"$ref": "#/definitions/features"}, }, }, "features": { "type": "array", - "items": {"anyOf": [{ "$ref": "#/definitions/featureobject" }, - { "type": "string" }, - ]}, + "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' + 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'): + 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' + 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'): + for l in rv.stdout.decode("ascii").split("\n"): if len(l): filelist.append(l) return filelist -def output_features(indent, fl): - for f in fl: - if type(f) is dict: - for k,v in f.items(): - print('{}- {}'.format(' ' * indent, k)) - output_features(indent + 2, v) + +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: - print('{}- {}'.format(' ' * indent, f)) - -def output_markdown(features): - for k,v in features.items(): - print('# {}'.format(v['name'])) - print('Maintainer: {} '.format(v['maintainer'])) - print('State: {}\n'.format(v['state'])) - print('{}\n'.format(v['description'])) - output_features(0, v['features']) - if 'missing' in v: - print('\n## Missing') - output_features(0, v['missing']) - print() + 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('--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) + 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: @@ -115,17 +282,38 @@ def main(): 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) as f: - cfg = yaml.load(f) - validate(instance=cfg, schema=schema) + 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: - output_markdown(features) + stream = StringIO() + tocstream, stream = output_markdown(features, fields, notfields, args.repolink) + print(tocstream.getvalue()) + print(stream.getvalue()) + stream.close() + -if __name__ == '__main__': +if __name__ == "__main__": main()