blob: 249970405c40d309d16d70fe5382b290698875aa [file] [log] [blame]
Ole Troan6a3064f2019-05-14 13:24:10 +02001#!/usr/bin/env python3
2
3import sys
4import os
5import ipaddress
6import yaml
7from pprint import pprint
8import re
Ole Troanf3aebda2020-01-03 16:37:27 +01009from jsonschema import validate, exceptions
Ole Troan6a3064f2019-05-14 13:24:10 +020010import argparse
11from subprocess import run, PIPE
Ole Troandbbff852020-01-08 12:37:55 +010012from io import StringIO
Ole Troan6a3064f2019-05-14 13:24:10 +020013
14# VPP feature JSON schema
15schema = {
16 "$schema": "http://json-schema.org/schema#",
17 "type": "object",
18 "properties": {
19 "name": {"type": "string"},
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040020 "description": {"type": "string"},
Ole Troane774a8b2020-01-02 22:32:57 +010021 "maintainer": {"$ref": "#/definitions/maintainers"},
Ole Troan6a3064f2019-05-14 13:24:10 +020022 "state": {"type": "string",
Ole Troane774a8b2020-01-02 22:32:57 +010023 "enum": ["production", "experimental", "development"]},
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040024 "features": {"$ref": "#/definitions/features"},
25 "missing": {"$ref": "#/definitions/features"},
26 "properties": {"type": "array",
27 "items": {"type": "string",
28 "enum": ["API", "CLI", "STATS",
29 "MULTITHREAD"]},
Ole Troan6a3064f2019-05-14 13:24:10 +020030 },
31 },
32 "additionalProperties": False,
33 "definitions": {
Ole Troane774a8b2020-01-02 22:32:57 +010034 "maintainers": {
35 "anyof": [{
36 "type": "array",
37 "items": {"type": "string"},
38 "minItems": 1,
Ole Troandbbff852020-01-08 12:37:55 +010039 },
Ole Troane774a8b2020-01-02 22:32:57 +010040 {"type": "string"}],
41 },
Ole Troan6a3064f2019-05-14 13:24:10 +020042 "featureobject": {
43 "type": "object",
44 "patternProperties": {
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040045 "^.*$": {"$ref": "#/definitions/features"},
Ole Troan6a3064f2019-05-14 13:24:10 +020046 },
47 },
48 "features": {
49 "type": "array",
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040050 "items": {"anyOf": [{"$ref": "#/definitions/featureobject"},
51 {"type": "string"},
52 ]},
Ole Troan6a3064f2019-05-14 13:24:10 +020053 "minItems": 1,
54 },
55 },
56}
57
58
Ole Troan6a3064f2019-05-14 13:24:10 +020059def filelist_from_git_status():
60 filelist = []
Dave Barach35ccd262020-06-17 08:05:37 -040061 git_status = 'git status --porcelain */FEATURE*.yaml'
Ole Troan6a3064f2019-05-14 13:24:10 +020062 rv = run(git_status.split(), stdout=PIPE, stderr=PIPE)
63 if rv.returncode != 0:
64 sys.exit(rv.returncode)
65
66 for l in rv.stdout.decode('ascii').split('\n'):
67 if len(l):
68 filelist.append(l.split()[1])
69 return filelist
70
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040071
Ole Troan6a3064f2019-05-14 13:24:10 +020072def filelist_from_git_ls():
73 filelist = []
Dave Barach35ccd262020-06-17 08:05:37 -040074 git_ls = 'git ls-files :(top)*/FEATURE*.yaml'
Ole Troan6a3064f2019-05-14 13:24:10 +020075 rv = run(git_ls.split(), stdout=PIPE, stderr=PIPE)
76 if rv.returncode != 0:
77 sys.exit(rv.returncode)
78
79 for l in rv.stdout.decode('ascii').split('\n'):
80 if len(l):
81 filelist.append(l)
82 return filelist
83
Ole Troandbbff852020-01-08 12:37:55 +010084def version_from_git():
85 git_describe = 'git describe'
86 rv = run(git_describe.split(), stdout=PIPE, stderr=PIPE)
87 if rv.returncode != 0:
88 sys.exit(rv.returncode)
89 return rv.stdout.decode('ascii').split('\n')[0]
Paul Vinciguerraea1a6512019-11-01 02:34:32 -040090
Ole Troandbbff852020-01-08 12:37:55 +010091class MarkDown():
92 _dispatch = {}
93
94 def __init__(self, stream):
95 self.stream = stream
96 self.toc = []
97
98 def print_maintainer(self, o):
99 write = self.stream.write
100 if type(o) is list:
101 write('Maintainers: ' +
102 ', '.join(f'{m}' for m in
103 o) + ' \n')
Ole Troan6a3064f2019-05-14 13:24:10 +0200104 else:
Ole Troandbbff852020-01-08 12:37:55 +0100105 write(f'Maintainer: {o} \n')
Ole Troan6a3064f2019-05-14 13:24:10 +0200106
Ole Troandbbff852020-01-08 12:37:55 +0100107 _dispatch['maintainer'] = print_maintainer
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400108
Ole Troandbbff852020-01-08 12:37:55 +0100109 def print_features(self, o, indent=0):
110 write = self.stream.write
111 for f in o:
112 indentstr = ' ' * indent
113 if type(f) is dict:
114 for k, v in f.items():
115 write(f'{indentstr}- {k}\n')
116 self.print_features(v, indent + 2)
117 else:
118 write(f'{indentstr}- {f}\n')
119 write('\n')
120 _dispatch['features'] = print_features
121
122 def print_markdown_header(self, o):
123 write = self.stream.write
124 write(f'## {o}\n')
125 version = version_from_git()
126 write(f'VPP version: {version}\n\n')
127 _dispatch['markdown_header'] = print_markdown_header
128
129 def print_name(self, o):
130 write = self.stream.write
131 write(f'### {o}\n')
132 self.toc.append(o)
133 _dispatch['name'] = print_name
134
135 def print_description(self, o):
136 write = self.stream.write
137 write(f'\n{o}\n\n')
138 _dispatch['description'] = print_description
139
140 def print_state(self, o):
141 write = self.stream.write
142 write(f'Feature maturity level: {o} \n')
143 _dispatch['state'] = print_state
144
145 def print_properties(self, o):
146 write = self.stream.write
147 write(f'Supports: {" ".join(o)} \n')
148 _dispatch['properties'] = print_properties
149
150 def print_missing(self, o):
151 write = self.stream.write
152 write('\nNot yet implemented: \n')
153 self.print_features(o)
154 _dispatch['missing'] = print_missing
155
156 def print_code(self, o):
157 write = self.stream.write
158 write(f'Source Code: [{o}]({o}) \n')
159 _dispatch['code'] = print_code
160
161 def print(self, t, o):
162 write = self.stream.write
163 if t in self._dispatch:
164 self._dispatch[t](self, o,)
Ole Troane774a8b2020-01-02 22:32:57 +0100165 else:
Ole Troandbbff852020-01-08 12:37:55 +0100166 write('NOT IMPLEMENTED: {t}\n')
Ole Troan6a3064f2019-05-14 13:24:10 +0200167
Ole Troandbbff852020-01-08 12:37:55 +0100168def output_toc(toc, stream):
169 write = stream.write
170 write('## VPP Feature list:\n')
171
172 for t in toc:
173 ref = t.lower().replace(' ', '-')
174 write(f'[{t}](#{ref}) \n')
175
176def featuresort(k):
177 return k[1]['name']
178
179def featurelistsort(k):
180 orderedfields = {
181 'name': 0,
182 'maintainer': 1,
183 'description': 2,
184 'features': 3,
185 'state': 4,
186 'properties': 5,
187 'missing': 6,
188 'code': 7,
189 }
190 return orderedfields[k[0]]
191
192def output_markdown(features, fields, notfields):
193 stream = StringIO()
194 m = MarkDown(stream)
195 m.print('markdown_header', 'Feature Details:')
196 for path, featuredef in sorted(features.items(), key=featuresort):
197 codeurl = 'https://git.fd.io/vpp/tree/src/' + '/'.join(os.path.normpath(path).split('/')[1:-1])
198 featuredef['code'] = codeurl
199 for k, v in sorted(featuredef.items(), key=featurelistsort):
200 if notfields:
201 if k not in notfields:
202 m.print(k, v)
203 elif fields:
204 if k in fields:
205 m.print(k, v)
206 else:
207 m.print(k, v)
208
209 tocstream = StringIO()
210 output_toc(m.toc, tocstream)
211 return tocstream, stream
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400212
Ole Troan6a3064f2019-05-14 13:24:10 +0200213def main():
214 parser = argparse.ArgumentParser(description='VPP Feature List.')
215 parser.add_argument('--validate', dest='validate', action='store_true',
216 help='validate the FEATURE.yaml file')
217 parser.add_argument('--git-status', dest='git_status', action='store_true',
218 help='Get filelist from git status')
219 parser.add_argument('--all', dest='all', action='store_true',
220 help='Validate all files in repository')
221 parser.add_argument('--markdown', dest='markdown', action='store_true',
222 help='Output feature table in markdown')
223 parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
224 default=sys.stdin)
Ole Troandbbff852020-01-08 12:37:55 +0100225 group = parser.add_mutually_exclusive_group()
226 group.add_argument('--include', help='List of fields to include')
227 group.add_argument('--exclude', help='List of fields to exclude')
Ole Troan6a3064f2019-05-14 13:24:10 +0200228 args = parser.parse_args()
Ole Troan6a3064f2019-05-14 13:24:10 +0200229 features = {}
230
231 if args.git_status:
232 filelist = filelist_from_git_status()
233 elif args.all:
234 filelist = filelist_from_git_ls()
235 else:
236 filelist = args.infile
237
Ole Troandbbff852020-01-08 12:37:55 +0100238 if args.include:
239 fields = args.include.split(',')
240 else:
241 fields = []
242 if args.exclude:
243 notfields = args.exclude.split(',')
244 else:
245 notfields = []
246
Ole Troan6a3064f2019-05-14 13:24:10 +0200247 for featurefile in filelist:
248 featurefile = featurefile.rstrip()
249
250 # Load configuration file
Paul Vinciguerra36686f42020-05-05 11:46:26 -0400251 with open(featurefile, encoding='utf-8') as f:
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400252 cfg = yaml.load(f, Loader=yaml.SafeLoader)
Ole Troanf3aebda2020-01-03 16:37:27 +0100253 try:
254 validate(instance=cfg, schema=schema)
255 except exceptions.ValidationError:
Paul Vinciguerra36686f42020-05-05 11:46:26 -0400256 print(f'File does not validate: {featurefile}',
Ole Troanf3aebda2020-01-03 16:37:27 +0100257 file=sys.stderr)
258 raise
Ole Troan6a3064f2019-05-14 13:24:10 +0200259 features[featurefile] = cfg
260
261 if args.markdown:
Ole Troandbbff852020-01-08 12:37:55 +0100262 stream = StringIO()
263 tocstream, stream = output_markdown(features, fields, notfields)
264 print(tocstream.getvalue())
265 print(stream.getvalue())
266 stream.close()
Ole Troan6a3064f2019-05-14 13:24:10 +0200267
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400268
Ole Troan6a3064f2019-05-14 13:24:10 +0200269if __name__ == '__main__':
270 main()