blob: f2b877fb2c7062811f7b3025e0ca9d107749e904 [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"},
Ole Troan6a3064f2019-05-14 13:24:10 +020024 "state": {"type": "string",
Ole Troane774a8b2020-01-02 22:32:57 +010025 "enum": ["production", "experimental", "development"]},
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040026 "features": {"$ref": "#/definitions/features"},
27 "missing": {"$ref": "#/definitions/features"},
28 "properties": {"type": "array",
29 "items": {"type": "string",
30 "enum": ["API", "CLI", "STATS",
31 "MULTITHREAD"]},
Ole Troan6a3064f2019-05-14 13:24:10 +020032 },
33 },
34 "additionalProperties": False,
35 "definitions": {
Ole Troane774a8b2020-01-02 22:32:57 +010036 "maintainers": {
37 "anyof": [{
38 "type": "array",
39 "items": {"type": "string"},
40 "minItems": 1,
Ole Troandbbff852020-01-08 12:37:55 +010041 },
Ole Troane774a8b2020-01-02 22:32:57 +010042 {"type": "string"}],
43 },
Ole Troan6a3064f2019-05-14 13:24:10 +020044 "featureobject": {
45 "type": "object",
46 "patternProperties": {
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040047 "^.*$": {"$ref": "#/definitions/features"},
Ole Troan6a3064f2019-05-14 13:24:10 +020048 },
49 },
50 "features": {
51 "type": "array",
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040052 "items": {"anyOf": [{"$ref": "#/definitions/featureobject"},
53 {"type": "string"},
54 ]},
Ole Troan6a3064f2019-05-14 13:24:10 +020055 "minItems": 1,
56 },
57 },
58}
59
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +020060DEFAULT_REPO_LINK = "https://github.com/FDio/vpp/blob/master/"
Ole Troan6a3064f2019-05-14 13:24:10 +020061
Ole Troan6a3064f2019-05-14 13:24:10 +020062def filelist_from_git_status():
63 filelist = []
Dave Barach35ccd262020-06-17 08:05:37 -040064 git_status = 'git status --porcelain */FEATURE*.yaml'
Ole Troan6a3064f2019-05-14 13:24:10 +020065 rv = run(git_status.split(), stdout=PIPE, stderr=PIPE)
66 if rv.returncode != 0:
67 sys.exit(rv.returncode)
68
69 for l in rv.stdout.decode('ascii').split('\n'):
70 if len(l):
71 filelist.append(l.split()[1])
72 return filelist
73
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040074
Ole Troan6a3064f2019-05-14 13:24:10 +020075def filelist_from_git_ls():
76 filelist = []
Dave Barach35ccd262020-06-17 08:05:37 -040077 git_ls = 'git ls-files :(top)*/FEATURE*.yaml'
Ole Troan6a3064f2019-05-14 13:24:10 +020078 rv = run(git_ls.split(), stdout=PIPE, stderr=PIPE)
79 if rv.returncode != 0:
80 sys.exit(rv.returncode)
81
82 for l in rv.stdout.decode('ascii').split('\n'):
83 if len(l):
84 filelist.append(l)
85 return filelist
86
Ole Troandbbff852020-01-08 12:37:55 +010087def version_from_git():
88 git_describe = 'git describe'
89 rv = run(git_describe.split(), stdout=PIPE, stderr=PIPE)
90 if rv.returncode != 0:
91 sys.exit(rv.returncode)
92 return rv.stdout.decode('ascii').split('\n')[0]
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040093
Ole Troandbbff852020-01-08 12:37:55 +010094class MarkDown():
95 _dispatch = {}
96
97 def __init__(self, stream):
98 self.stream = stream
99 self.toc = []
100
101 def print_maintainer(self, o):
102 write = self.stream.write
103 if type(o) is list:
104 write('Maintainers: ' +
Dave Wallacea0798442020-09-23 11:38:25 -0400105 ', '.join('{m}'.format(m=m) for m in
Ole Troandbbff852020-01-08 12:37:55 +0100106 o) + ' \n')
Ole Troan6a3064f2019-05-14 13:24:10 +0200107 else:
Dave Wallacea0798442020-09-23 11:38:25 -0400108 write('Maintainer: {o} \n'.format(o=o))
Ole Troan6a3064f2019-05-14 13:24:10 +0200109
Ole Troandbbff852020-01-08 12:37:55 +0100110 _dispatch['maintainer'] = print_maintainer
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400111
Ole Troandbbff852020-01-08 12:37:55 +0100112 def print_features(self, o, indent=0):
113 write = self.stream.write
114 for f in o:
115 indentstr = ' ' * indent
116 if type(f) is dict:
117 for k, v in f.items():
Dave Wallacea0798442020-09-23 11:38:25 -0400118 write('{indentstr}- {k}\n'.format(indentstr=indentstr, k=k))
Ole Troandbbff852020-01-08 12:37:55 +0100119 self.print_features(v, indent + 2)
120 else:
Dave Wallacea0798442020-09-23 11:38:25 -0400121 write('{indentstr}- {f}\n'.format(indentstr=indentstr, f=f))
Ole Troandbbff852020-01-08 12:37:55 +0100122 write('\n')
123 _dispatch['features'] = print_features
124
125 def print_markdown_header(self, o):
126 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400127 write('## {o}\n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100128 _dispatch['markdown_header'] = print_markdown_header
129
130 def print_name(self, o):
131 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400132 write('### {o}\n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100133 self.toc.append(o)
134 _dispatch['name'] = print_name
135
136 def print_description(self, o):
137 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400138 write('\n{o}\n\n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100139 _dispatch['description'] = print_description
140
141 def print_state(self, o):
142 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400143 write('Feature maturity level: {o} \n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100144 _dispatch['state'] = print_state
145
146 def print_properties(self, o):
147 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400148 write('Supports: {s} \n'.format(s=" ".join(o)))
Ole Troandbbff852020-01-08 12:37:55 +0100149 _dispatch['properties'] = print_properties
150
151 def print_missing(self, o):
152 write = self.stream.write
153 write('\nNot yet implemented: \n')
154 self.print_features(o)
155 _dispatch['missing'] = print_missing
156
157 def print_code(self, o):
158 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400159 write('Source Code: [{o}]({o}) \n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100160 _dispatch['code'] = print_code
161
162 def print(self, t, o):
163 write = self.stream.write
164 if t in self._dispatch:
165 self._dispatch[t](self, o,)
Ole Troane774a8b2020-01-02 22:32:57 +0100166 else:
Ole Troandbbff852020-01-08 12:37:55 +0100167 write('NOT IMPLEMENTED: {t}\n')
Ole Troan6a3064f2019-05-14 13:24:10 +0200168
Ole Troandbbff852020-01-08 12:37:55 +0100169def output_toc(toc, stream):
170 write = stream.write
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200171 write('# VPP Supported Features\n')
Ole Troandbbff852020-01-08 12:37:55 +0100172
173 for t in toc:
174 ref = t.lower().replace(' ', '-')
Dave Wallacea0798442020-09-23 11:38:25 -0400175 write('[{t}](#{ref}) \n'.format(t=t, ref=ref))
Ole Troandbbff852020-01-08 12:37:55 +0100176
177def featuresort(k):
178 return k[1]['name']
179
180def featurelistsort(k):
181 orderedfields = {
182 'name': 0,
183 'maintainer': 1,
184 'description': 2,
185 'features': 3,
186 'state': 4,
187 'properties': 5,
188 'missing': 6,
189 'code': 7,
190 }
191 return orderedfields[k[0]]
192
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200193def output_markdown(features, fields, notfields, repository_url):
Ole Troandbbff852020-01-08 12:37:55 +0100194 stream = StringIO()
195 m = MarkDown(stream)
196 m.print('markdown_header', 'Feature Details:')
197 for path, featuredef in sorted(features.items(), key=featuresort):
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200198 codeurl = urllib.parse.urljoin(repository_url, os.path.dirname(path))
Ole Troandbbff852020-01-08 12:37:55 +0100199 featuredef['code'] = codeurl
200 for k, v in sorted(featuredef.items(), key=featurelistsort):
201 if notfields:
202 if k not in notfields:
203 m.print(k, v)
204 elif fields:
205 if k in fields:
206 m.print(k, v)
207 else:
208 m.print(k, v)
209
210 tocstream = StringIO()
211 output_toc(m.toc, tocstream)
212 return tocstream, stream
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400213
Ole Troan6a3064f2019-05-14 13:24:10 +0200214def main():
215 parser = argparse.ArgumentParser(description='VPP Feature List.')
216 parser.add_argument('--validate', dest='validate', action='store_true',
217 help='validate the FEATURE.yaml file')
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200218 parser.add_argument("--repolink", metavar="repolink", default=DEFAULT_REPO_LINK,
219 help="Link to public repository [%s]" %
220 DEFAULT_REPO_LINK)
Ole Troan6a3064f2019-05-14 13:24:10 +0200221 parser.add_argument('--git-status', dest='git_status', action='store_true',
222 help='Get filelist from git status')
223 parser.add_argument('--all', dest='all', action='store_true',
224 help='Validate all files in repository')
225 parser.add_argument('--markdown', dest='markdown', action='store_true',
226 help='Output feature table in markdown')
227 parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
228 default=sys.stdin)
Ole Troandbbff852020-01-08 12:37:55 +0100229 group = parser.add_mutually_exclusive_group()
230 group.add_argument('--include', help='List of fields to include')
231 group.add_argument('--exclude', help='List of fields to exclude')
Ole Troan6a3064f2019-05-14 13:24:10 +0200232 args = parser.parse_args()
Ole Troan6a3064f2019-05-14 13:24:10 +0200233 features = {}
234
235 if args.git_status:
236 filelist = filelist_from_git_status()
237 elif args.all:
238 filelist = filelist_from_git_ls()
239 else:
240 filelist = args.infile
241
Ole Troandbbff852020-01-08 12:37:55 +0100242 if args.include:
243 fields = args.include.split(',')
244 else:
245 fields = []
246 if args.exclude:
247 notfields = args.exclude.split(',')
248 else:
249 notfields = []
250
Ole Troan6a3064f2019-05-14 13:24:10 +0200251 for featurefile in filelist:
252 featurefile = featurefile.rstrip()
253
254 # Load configuration file
Paul Vinciguerra36686f42020-05-05 11:46:26 -0400255 with open(featurefile, encoding='utf-8') as f:
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400256 cfg = yaml.load(f, Loader=yaml.SafeLoader)
Ole Troanf3aebda2020-01-03 16:37:27 +0100257 try:
258 validate(instance=cfg, schema=schema)
259 except exceptions.ValidationError:
Dave Wallacea0798442020-09-23 11:38:25 -0400260 print('File does not validate: {featurefile}' \
261 .format(featurefile=featurefile), file=sys.stderr)
Ole Troanf3aebda2020-01-03 16:37:27 +0100262 raise
Ole Troan6a3064f2019-05-14 13:24:10 +0200263 features[featurefile] = cfg
264
265 if args.markdown:
Ole Troandbbff852020-01-08 12:37:55 +0100266 stream = StringIO()
Nathan Skrzypczak9ad39c02021-08-19 11:38:06 +0200267 tocstream, stream = output_markdown(features, fields, notfields, args.repolink)
Ole Troandbbff852020-01-08 12:37:55 +0100268 print(tocstream.getvalue())
269 print(stream.getvalue())
270 stream.close()
Ole Troan6a3064f2019-05-14 13:24:10 +0200271
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400272
Ole Troan6a3064f2019-05-14 13:24:10 +0200273if __name__ == '__main__':
274 main()