blob: b579190e433eb597e737489510e384f2a08f250e [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: ' +
Dave Wallacea0798442020-09-23 11:38:25 -0400102 ', '.join('{m}'.format(m=m) for m in
Ole Troandbbff852020-01-08 12:37:55 +0100103 o) + ' \n')
Ole Troan6a3064f2019-05-14 13:24:10 +0200104 else:
Dave Wallacea0798442020-09-23 11:38:25 -0400105 write('Maintainer: {o} \n'.format(o=o))
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():
Dave Wallacea0798442020-09-23 11:38:25 -0400115 write('{indentstr}- {k}\n'.format(indentstr=indentstr, k=k))
Ole Troandbbff852020-01-08 12:37:55 +0100116 self.print_features(v, indent + 2)
117 else:
Dave Wallacea0798442020-09-23 11:38:25 -0400118 write('{indentstr}- {f}\n'.format(indentstr=indentstr, f=f))
Ole Troandbbff852020-01-08 12:37:55 +0100119 write('\n')
120 _dispatch['features'] = print_features
121
122 def print_markdown_header(self, o):
123 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400124 write('## {o}\n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100125 version = version_from_git()
Dave Wallacea0798442020-09-23 11:38:25 -0400126 write('VPP version: {version}\n\n'.format(version=version))
Ole Troandbbff852020-01-08 12:37:55 +0100127 _dispatch['markdown_header'] = print_markdown_header
128
129 def print_name(self, o):
130 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400131 write('### {o}\n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100132 self.toc.append(o)
133 _dispatch['name'] = print_name
134
135 def print_description(self, o):
136 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400137 write('\n{o}\n\n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100138 _dispatch['description'] = print_description
139
140 def print_state(self, o):
141 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400142 write('Feature maturity level: {o} \n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100143 _dispatch['state'] = print_state
144
145 def print_properties(self, o):
146 write = self.stream.write
Dave Wallacea0798442020-09-23 11:38:25 -0400147 write('Supports: {s} \n'.format(s=" ".join(o)))
Ole Troandbbff852020-01-08 12:37:55 +0100148 _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
Dave Wallacea0798442020-09-23 11:38:25 -0400158 write('Source Code: [{o}]({o}) \n'.format(o=o))
Ole Troandbbff852020-01-08 12:37:55 +0100159 _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(' ', '-')
Dave Wallacea0798442020-09-23 11:38:25 -0400174 write('[{t}](#{ref}) \n'.format(t=t, ref=ref))
Ole Troandbbff852020-01-08 12:37:55 +0100175
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):
Dave Wallacea0798442020-09-23 11:38:25 -0400197 codeurl = 'https://git.fd.io/vpp/tree/src/' + \
198 '/'.join(os.path.normpath(path).split('/')[1:-1])
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')
218 parser.add_argument('--git-status', dest='git_status', action='store_true',
219 help='Get filelist from git status')
220 parser.add_argument('--all', dest='all', action='store_true',
221 help='Validate all files in repository')
222 parser.add_argument('--markdown', dest='markdown', action='store_true',
223 help='Output feature table in markdown')
224 parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
225 default=sys.stdin)
Ole Troandbbff852020-01-08 12:37:55 +0100226 group = parser.add_mutually_exclusive_group()
227 group.add_argument('--include', help='List of fields to include')
228 group.add_argument('--exclude', help='List of fields to exclude')
Ole Troan6a3064f2019-05-14 13:24:10 +0200229 args = parser.parse_args()
Ole Troan6a3064f2019-05-14 13:24:10 +0200230 features = {}
231
232 if args.git_status:
233 filelist = filelist_from_git_status()
234 elif args.all:
235 filelist = filelist_from_git_ls()
236 else:
237 filelist = args.infile
238
Ole Troandbbff852020-01-08 12:37:55 +0100239 if args.include:
240 fields = args.include.split(',')
241 else:
242 fields = []
243 if args.exclude:
244 notfields = args.exclude.split(',')
245 else:
246 notfields = []
247
Ole Troan6a3064f2019-05-14 13:24:10 +0200248 for featurefile in filelist:
249 featurefile = featurefile.rstrip()
250
251 # Load configuration file
Paul Vinciguerra36686f42020-05-05 11:46:26 -0400252 with open(featurefile, encoding='utf-8') as f:
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400253 cfg = yaml.load(f, Loader=yaml.SafeLoader)
Ole Troanf3aebda2020-01-03 16:37:27 +0100254 try:
255 validate(instance=cfg, schema=schema)
256 except exceptions.ValidationError:
Dave Wallacea0798442020-09-23 11:38:25 -0400257 print('File does not validate: {featurefile}' \
258 .format(featurefile=featurefile), file=sys.stderr)
Ole Troanf3aebda2020-01-03 16:37:27 +0100259 raise
Ole Troan6a3064f2019-05-14 13:24:10 +0200260 features[featurefile] = cfg
261
262 if args.markdown:
Ole Troandbbff852020-01-08 12:37:55 +0100263 stream = StringIO()
264 tocstream, stream = output_markdown(features, fields, notfields)
265 print(tocstream.getvalue())
266 print(stream.getvalue())
267 stream.close()
Ole Troan6a3064f2019-05-14 13:24:10 +0200268
Paul Vinciguerraea1a6512019-11-01 02:34:32 -0400269
Ole Troan6a3064f2019-05-14 13:24:10 +0200270if __name__ == '__main__':
271 main()