blob: e8ff477c7887f6fb40f48e26750c8435548a4f94 [file] [log] [blame]
Ole Troan6a3064f2019-05-14 13:24:10 +02001#!/usr/bin/env python3
2
3import sys
4import os
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +02005import os.path
Ole Troan6a3064f2019-05-14 13:24:10 +02006import ipaddress
7import yaml
8from pprint import pprint
9import re
Ole Troanf3aebda2020-01-03 16:37:27 +010010from jsonschema import validate, exceptions
Ole Troan6a3064f2019-05-14 13:24:10 +020011import argparse
12from subprocess import run, PIPE
Ole Troandbbff852020-01-08 12:37:55 +010013from io import StringIO
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +020014import urllib.parse
Ole Troan6a3064f2019-05-14 13:24:10 +020015
16# VPP feature JSON schema
17schema = {
18 "$schema": "http://json-schema.org/schema#",
19 "type": "object",
20 "properties": {
21 "name": {"type": "string"},
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040022 "description": {"type": "string"},
Ole Troane774a8b2020-01-02 22:32:57 +010023 "maintainer": {"$ref": "#/definitions/maintainers"},
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020024 "state": {
25 "type": "string",
26 "enum": ["production", "experimental", "development"],
27 },
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040028 "features": {"$ref": "#/definitions/features"},
29 "missing": {"$ref": "#/definitions/features"},
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020030 "properties": {
31 "type": "array",
32 "items": {"type": "string", "enum": ["API", "CLI", "STATS", "MULTITHREAD"]},
33 },
Ole Troan6a3064f2019-05-14 13:24:10 +020034 },
35 "additionalProperties": False,
36 "definitions": {
Ole Troane774a8b2020-01-02 22:32:57 +010037 "maintainers": {
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020038 "anyof": [
39 {
40 "type": "array",
41 "items": {"type": "string"},
42 "minItems": 1,
43 },
44 {"type": "string"},
45 ],
Ole Troane774a8b2020-01-02 22:32:57 +010046 },
Ole Troan6a3064f2019-05-14 13:24:10 +020047 "featureobject": {
48 "type": "object",
49 "patternProperties": {
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040050 "^.*$": {"$ref": "#/definitions/features"},
Ole Troan6a3064f2019-05-14 13:24:10 +020051 },
52 },
53 "features": {
54 "type": "array",
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020055 "items": {
56 "anyOf": [
57 {"$ref": "#/definitions/featureobject"},
58 {"type": "string"},
59 ]
60 },
Ole Troan6a3064f2019-05-14 13:24:10 +020061 "minItems": 1,
62 },
63 },
64}
65
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +020066DEFAULT_REPO_LINK = "https://github.com/FDio/vpp/blob/master/"
Ole Troan6a3064f2019-05-14 13:24:10 +020067
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020068
Ole Troan6a3064f2019-05-14 13:24:10 +020069def filelist_from_git_status():
70 filelist = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020071 git_status = "git status --porcelain */FEATURE*.yaml"
Ole Troan6a3064f2019-05-14 13:24:10 +020072 rv = run(git_status.split(), stdout=PIPE, stderr=PIPE)
73 if rv.returncode != 0:
74 sys.exit(rv.returncode)
75
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020076 for l in rv.stdout.decode("ascii").split("\n"):
Ole Troan6a3064f2019-05-14 13:24:10 +020077 if len(l):
78 filelist.append(l.split()[1])
79 return filelist
80
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040081
Ole Troan6a3064f2019-05-14 13:24:10 +020082def filelist_from_git_ls():
83 filelist = []
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020084 git_ls = "git ls-files :(top)*/FEATURE*.yaml"
Ole Troan6a3064f2019-05-14 13:24:10 +020085 rv = run(git_ls.split(), stdout=PIPE, stderr=PIPE)
86 if rv.returncode != 0:
87 sys.exit(rv.returncode)
88
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020089 for l in rv.stdout.decode("ascii").split("\n"):
Ole Troan6a3064f2019-05-14 13:24:10 +020090 if len(l):
91 filelist.append(l)
92 return filelist
93
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020094
Ole Troandbbff852020-01-08 12:37:55 +010095def version_from_git():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020096 git_describe = "git describe"
Ole Troandbbff852020-01-08 12:37:55 +010097 rv = run(git_describe.split(), stdout=PIPE, stderr=PIPE)
98 if rv.returncode != 0:
99 sys.exit(rv.returncode)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200100 return rv.stdout.decode("ascii").split("\n")[0]
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400101
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200102
103class MarkDown:
Ole Troandbbff852020-01-08 12:37:55 +0100104 _dispatch = {}
105
106 def __init__(self, stream):
107 self.stream = stream
108 self.toc = []
109
110 def print_maintainer(self, o):
111 write = self.stream.write
112 if type(o) is list:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200113 write("Maintainers: " + ", ".join("{m}".format(m=m) for m in o) + " \n")
Ole Troan6a3064f2019-05-14 13:24:10 +0200114 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200115 write("Maintainer: {o} \n".format(o=o))
Ole Troan6a3064f2019-05-14 13:24:10 +0200116
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200117 _dispatch["maintainer"] = print_maintainer
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400118
Ole Troandbbff852020-01-08 12:37:55 +0100119 def print_features(self, o, indent=0):
120 write = self.stream.write
121 for f in o:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200122 indentstr = " " * indent
Ole Troandbbff852020-01-08 12:37:55 +0100123 if type(f) is dict:
124 for k, v in f.items():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200125 write("{indentstr}- {k}\n".format(indentstr=indentstr, k=k))
Ole Troandbbff852020-01-08 12:37:55 +0100126 self.print_features(v, indent + 2)
127 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200128 write("{indentstr}- {f}\n".format(indentstr=indentstr, f=f))
129 write("\n")
130
131 _dispatch["features"] = print_features
Ole Troandbbff852020-01-08 12:37:55 +0100132
133 def print_markdown_header(self, o):
134 write = self.stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200135 write("## {o}\n".format(o=o))
136
137 _dispatch["markdown_header"] = print_markdown_header
Ole Troandbbff852020-01-08 12:37:55 +0100138
139 def print_name(self, o):
140 write = self.stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200141 write("### {o}\n".format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100142 self.toc.append(o)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200143
144 _dispatch["name"] = print_name
Ole Troandbbff852020-01-08 12:37:55 +0100145
146 def print_description(self, o):
147 write = self.stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200148 write("\n{o}\n\n".format(o=o))
149
150 _dispatch["description"] = print_description
Ole Troandbbff852020-01-08 12:37:55 +0100151
152 def print_state(self, o):
153 write = self.stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200154 write("Feature maturity level: {o} \n".format(o=o))
155
156 _dispatch["state"] = print_state
Ole Troandbbff852020-01-08 12:37:55 +0100157
158 def print_properties(self, o):
159 write = self.stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200160 write("Supports: {s} \n".format(s=" ".join(o)))
161
162 _dispatch["properties"] = print_properties
Ole Troandbbff852020-01-08 12:37:55 +0100163
164 def print_missing(self, o):
165 write = self.stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200166 write("\nNot yet implemented: \n")
Ole Troandbbff852020-01-08 12:37:55 +0100167 self.print_features(o)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200168
169 _dispatch["missing"] = print_missing
Ole Troandbbff852020-01-08 12:37:55 +0100170
171 def print_code(self, o):
172 write = self.stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200173 write("Source Code: [{o}]({o}) \n".format(o=o))
174
175 _dispatch["code"] = print_code
Ole Troandbbff852020-01-08 12:37:55 +0100176
177 def print(self, t, o):
178 write = self.stream.write
179 if t in self._dispatch:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200180 self._dispatch[t](
181 self,
182 o,
183 )
Ole Troane774a8b2020-01-02 22:32:57 +0100184 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200185 write("NOT IMPLEMENTED: {t}\n")
186
Ole Troan6a3064f2019-05-14 13:24:10 +0200187
Ole Troandbbff852020-01-08 12:37:55 +0100188def output_toc(toc, stream):
189 write = stream.write
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200190 write("# VPP Supported Features\n")
Ole Troandbbff852020-01-08 12:37:55 +0100191
192 for t in toc:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200193 ref = t.lower().replace(" ", "-")
194 write("[{t}](#{ref}) \n".format(t=t, ref=ref))
195
Ole Troandbbff852020-01-08 12:37:55 +0100196
197def featuresort(k):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200198 return k[1]["name"]
199
Ole Troandbbff852020-01-08 12:37:55 +0100200
201def featurelistsort(k):
202 orderedfields = {
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200203 "name": 0,
204 "maintainer": 1,
205 "description": 2,
206 "features": 3,
207 "state": 4,
208 "properties": 5,
209 "missing": 6,
210 "code": 7,
Ole Troandbbff852020-01-08 12:37:55 +0100211 }
212 return orderedfields[k[0]]
213
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200214
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200215def output_markdown(features, fields, notfields, repository_url):
Ole Troandbbff852020-01-08 12:37:55 +0100216 stream = StringIO()
217 m = MarkDown(stream)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200218 m.print("markdown_header", "Feature Details:")
Ole Troandbbff852020-01-08 12:37:55 +0100219 for path, featuredef in sorted(features.items(), key=featuresort):
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200220 codeurl = urllib.parse.urljoin(repository_url, os.path.dirname(path))
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200221 featuredef["code"] = codeurl
Ole Troandbbff852020-01-08 12:37:55 +0100222 for k, v in sorted(featuredef.items(), key=featurelistsort):
223 if notfields:
224 if k not in notfields:
225 m.print(k, v)
226 elif fields:
227 if k in fields:
228 m.print(k, v)
229 else:
230 m.print(k, v)
231
232 tocstream = StringIO()
233 output_toc(m.toc, tocstream)
234 return tocstream, stream
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400235
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200236
Ole Troan6a3064f2019-05-14 13:24:10 +0200237def main():
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200238 parser = argparse.ArgumentParser(description="VPP Feature List.")
239 parser.add_argument(
240 "--validate",
241 dest="validate",
242 action="store_true",
243 help="validate the FEATURE.yaml file",
244 )
245 parser.add_argument(
246 "--repolink",
247 metavar="repolink",
248 default=DEFAULT_REPO_LINK,
249 help="Link to public repository [%s]" % DEFAULT_REPO_LINK,
250 )
251 parser.add_argument(
252 "--git-status",
253 dest="git_status",
254 action="store_true",
255 help="Get filelist from git status",
256 )
257 parser.add_argument(
258 "--all",
259 dest="all",
260 action="store_true",
261 help="Validate all files in repository",
262 )
263 parser.add_argument(
264 "--markdown",
265 dest="markdown",
266 action="store_true",
267 help="Output feature table in markdown",
268 )
269 parser.add_argument(
270 "infile", nargs="?", type=argparse.FileType("r"), default=sys.stdin
271 )
Ole Troandbbff852020-01-08 12:37:55 +0100272 group = parser.add_mutually_exclusive_group()
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200273 group.add_argument("--include", help="List of fields to include")
274 group.add_argument("--exclude", help="List of fields to exclude")
Ole Troan6a3064f2019-05-14 13:24:10 +0200275 args = parser.parse_args()
Ole Troan6a3064f2019-05-14 13:24:10 +0200276 features = {}
277
278 if args.git_status:
279 filelist = filelist_from_git_status()
280 elif args.all:
281 filelist = filelist_from_git_ls()
282 else:
283 filelist = args.infile
284
Ole Troandbbff852020-01-08 12:37:55 +0100285 if args.include:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200286 fields = args.include.split(",")
Ole Troandbbff852020-01-08 12:37:55 +0100287 else:
288 fields = []
289 if args.exclude:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200290 notfields = args.exclude.split(",")
Ole Troandbbff852020-01-08 12:37:55 +0100291 else:
292 notfields = []
293
Ole Troan6a3064f2019-05-14 13:24:10 +0200294 for featurefile in filelist:
295 featurefile = featurefile.rstrip()
296
297 # Load configuration file
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200298 with open(featurefile, encoding="utf-8") as f:
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400299 cfg = yaml.load(f, Loader=yaml.SafeLoader)
Ole Troanf3aebda2020-01-03 16:37:27 +0100300 try:
301 validate(instance=cfg, schema=schema)
302 except exceptions.ValidationError:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200303 print(
304 "File does not validate: {featurefile}".format(featurefile=featurefile),
305 file=sys.stderr,
306 )
Ole Troanf3aebda2020-01-03 16:37:27 +0100307 raise
Ole Troan6a3064f2019-05-14 13:24:10 +0200308 features[featurefile] = cfg
309
310 if args.markdown:
Ole Troandbbff852020-01-08 12:37:55 +0100311 stream = StringIO()
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200312 tocstream, stream = output_markdown(features, fields, notfields, args.repolink)
Ole Troandbbff852020-01-08 12:37:55 +0100313 print(tocstream.getvalue())
314 print(stream.getvalue())
315 stream.close()
Ole Troan6a3064f2019-05-14 13:24:10 +0200316
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400317
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200318if __name__ == "__main__":
Ole Troan6a3064f2019-05-14 13:24:10 +0200319 main()