b579190e433eb597e737489510e384f2a08f250e
[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('{m}'.format(m=m) for m in
103                             o) + '  \n')
104         else:
105             write('Maintainer: {o}  \n'.format(o=o))
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('{indentstr}- {k}\n'.format(indentstr=indentstr, k=k))
116                     self.print_features(v, indent + 2)
117             else:
118                 write('{indentstr}- {f}\n'.format(indentstr=indentstr, f=f))
119         write('\n')
120     _dispatch['features'] = print_features
121
122     def print_markdown_header(self, o):
123         write = self.stream.write
124         write('## {o}\n'.format(o=o))
125         version = version_from_git()
126         write('VPP version: {version}\n\n'.format(version=version))
127     _dispatch['markdown_header'] = print_markdown_header
128
129     def print_name(self, o):
130         write = self.stream.write
131         write('### {o}\n'.format(o=o))
132         self.toc.append(o)
133     _dispatch['name'] = print_name
134
135     def print_description(self, o):
136         write = self.stream.write
137         write('\n{o}\n\n'.format(o=o))
138     _dispatch['description'] = print_description
139
140     def print_state(self, o):
141         write = self.stream.write
142         write('Feature maturity level: {o}  \n'.format(o=o))
143     _dispatch['state'] = print_state
144
145     def print_properties(self, o):
146         write = self.stream.write
147         write('Supports: {s}  \n'.format(s=" ".join(o)))
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('Source Code: [{o}]({o}) \n'.format(o=o))
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('[{t}](#{ref})  \n'.format(t=t, ref=ref))
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/' + \
198                   '/'.join(os.path.normpath(path).split('/')[1:-1])
199         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
213
214 def 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)
226     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')
229     args = parser.parse_args()
230     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
239     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
248     for featurefile in filelist:
249         featurefile = featurefile.rstrip()
250
251         # Load configuration file
252         with open(featurefile, encoding='utf-8') as f:
253             cfg = yaml.load(f, Loader=yaml.SafeLoader)
254         try:
255             validate(instance=cfg, schema=schema)
256         except exceptions.ValidationError:
257             print('File does not validate: {featurefile}' \
258                   .format(featurefile=featurefile), file=sys.stderr)
259             raise
260         features[featurefile] = cfg
261
262     if args.markdown:
263         stream = StringIO()
264         tocstream, stream = output_markdown(features, fields, notfields)
265         print(tocstream.getvalue())
266         print(stream.getvalue())
267         stream.close()
268
269
270 if __name__ == '__main__':
271     main()