session: API cleanup
[vpp.git] / src / scripts / fts.py
1 #!/usr/bin/env python3
2
3 import sys
4 import os
5 import ipaddress
6 import yaml
7 from pprint import pprint
8 import re
9 from jsonschema import validate, exceptions
10 import argparse
11 from subprocess import run, PIPE
12 from io import StringIO
13
14 # VPP feature JSON schema
15 schema = {
16     "$schema": "http://json-schema.org/schema#",
17     "type": "object",
18     "properties": {
19         "name": {"type": "string"},
20         "description": {"type": "string"},
21         "maintainer": {"$ref": "#/definitions/maintainers"},
22         "state": {"type": "string",
23                   "enum": ["production", "experimental", "development"]},
24         "features": {"$ref": "#/definitions/features"},
25         "missing": {"$ref": "#/definitions/features"},
26         "properties": {"type": "array",
27                        "items": {"type": "string",
28                                  "enum": ["API", "CLI", "STATS",
29                                           "MULTITHREAD"]},
30                        },
31     },
32     "additionalProperties": False,
33     "definitions": {
34         "maintainers": {
35             "anyof": [{
36                 "type": "array",
37                 "items": {"type": "string"},
38                 "minItems": 1,
39             },
40                 {"type": "string"}],
41         },
42         "featureobject": {
43             "type": "object",
44             "patternProperties": {
45                 "^.*$": {"$ref": "#/definitions/features"},
46             },
47         },
48         "features": {
49             "type": "array",
50             "items": {"anyOf": [{"$ref": "#/definitions/featureobject"},
51                                 {"type": "string"},
52                                 ]},
53             "minItems": 1,
54         },
55     },
56 }
57
58
59 def filelist_from_git_status():
60     filelist = []
61     git_status = 'git status --porcelain */FEATURE.yaml'
62     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
71
72 def filelist_from_git_ls():
73     filelist = []
74     git_ls = 'git ls-files :(top)*/FEATURE.yaml'
75     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
84 def 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]
90
91 class 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')
104         else:
105             write(f'Maintainer: {o}  \n')
106
107     _dispatch['maintainer'] = print_maintainer
108
109     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,)
165         else:
166             write('NOT IMPLEMENTED: {t}\n')
167
168 def 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
176 def featuresort(k):
177     return k[1]['name']
178
179 def 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
192 def 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
212
213 def 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)
225     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')
228     args = parser.parse_args()
229     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
238     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
247     for featurefile in filelist:
248         featurefile = featurefile.rstrip()
249
250         # Load configuration file
251         with open(featurefile) as f:
252             cfg = yaml.load(f, Loader=yaml.SafeLoader)
253         try:
254             validate(instance=cfg, schema=schema)
255         except exceptions.ValidationError:
256             print('File does not validate: {featurefile}',
257                   file=sys.stderr)
258             raise
259         features[featurefile] = cfg
260
261     if args.markdown:
262         stream = StringIO()
263         tocstream, stream = output_markdown(features, fields, notfields)
264         print(tocstream.getvalue())
265         print(stream.getvalue())
266         stream.close()
267
268
269 if __name__ == '__main__':
270     main()