Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 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 |
Ole Troan | f3aebda | 2020-01-03 16:37:27 +0100 | [diff] [blame] | 9 | from jsonschema import validate, exceptions |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 10 | import argparse |
| 11 | from subprocess import run, PIPE |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 12 | from io import StringIO |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 13 | |
| 14 | # VPP feature JSON schema |
| 15 | schema = { |
| 16 | "$schema": "http://json-schema.org/schema#", |
| 17 | "type": "object", |
| 18 | "properties": { |
| 19 | "name": {"type": "string"}, |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 20 | "description": {"type": "string"}, |
Ole Troan | e774a8b | 2020-01-02 22:32:57 +0100 | [diff] [blame] | 21 | "maintainer": {"$ref": "#/definitions/maintainers"}, |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 22 | "state": {"type": "string", |
Ole Troan | e774a8b | 2020-01-02 22:32:57 +0100 | [diff] [blame] | 23 | "enum": ["production", "experimental", "development"]}, |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 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"]}, |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 30 | }, |
| 31 | }, |
| 32 | "additionalProperties": False, |
| 33 | "definitions": { |
Ole Troan | e774a8b | 2020-01-02 22:32:57 +0100 | [diff] [blame] | 34 | "maintainers": { |
| 35 | "anyof": [{ |
| 36 | "type": "array", |
| 37 | "items": {"type": "string"}, |
| 38 | "minItems": 1, |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 39 | }, |
Ole Troan | e774a8b | 2020-01-02 22:32:57 +0100 | [diff] [blame] | 40 | {"type": "string"}], |
| 41 | }, |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 42 | "featureobject": { |
| 43 | "type": "object", |
| 44 | "patternProperties": { |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 45 | "^.*$": {"$ref": "#/definitions/features"}, |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 46 | }, |
| 47 | }, |
| 48 | "features": { |
| 49 | "type": "array", |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 50 | "items": {"anyOf": [{"$ref": "#/definitions/featureobject"}, |
| 51 | {"type": "string"}, |
| 52 | ]}, |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 53 | "minItems": 1, |
| 54 | }, |
| 55 | }, |
| 56 | } |
| 57 | |
| 58 | |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 59 | def filelist_from_git_status(): |
| 60 | filelist = [] |
Dave Barach | 35ccd26 | 2020-06-17 08:05:37 -0400 | [diff] [blame] | 61 | git_status = 'git status --porcelain */FEATURE*.yaml' |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 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 | |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 71 | |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 72 | def filelist_from_git_ls(): |
| 73 | filelist = [] |
Dave Barach | 35ccd26 | 2020-06-17 08:05:37 -0400 | [diff] [blame] | 74 | git_ls = 'git ls-files :(top)*/FEATURE*.yaml' |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 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 | |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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] |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 90 | |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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: ' + |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 102 | ', '.join('{m}'.format(m=m) for m in |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 103 | o) + ' \n') |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 104 | else: |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 105 | write('Maintainer: {o} \n'.format(o=o)) |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 106 | |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 107 | _dispatch['maintainer'] = print_maintainer |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 108 | |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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(): |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 115 | write('{indentstr}- {k}\n'.format(indentstr=indentstr, k=k)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 116 | self.print_features(v, indent + 2) |
| 117 | else: |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 118 | write('{indentstr}- {f}\n'.format(indentstr=indentstr, f=f)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 119 | write('\n') |
| 120 | _dispatch['features'] = print_features |
| 121 | |
| 122 | def print_markdown_header(self, o): |
| 123 | write = self.stream.write |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 124 | write('## {o}\n'.format(o=o)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 125 | version = version_from_git() |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 126 | write('VPP version: {version}\n\n'.format(version=version)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 127 | _dispatch['markdown_header'] = print_markdown_header |
| 128 | |
| 129 | def print_name(self, o): |
| 130 | write = self.stream.write |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 131 | write('### {o}\n'.format(o=o)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 132 | self.toc.append(o) |
| 133 | _dispatch['name'] = print_name |
| 134 | |
| 135 | def print_description(self, o): |
| 136 | write = self.stream.write |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 137 | write('\n{o}\n\n'.format(o=o)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 138 | _dispatch['description'] = print_description |
| 139 | |
| 140 | def print_state(self, o): |
| 141 | write = self.stream.write |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 142 | write('Feature maturity level: {o} \n'.format(o=o)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 143 | _dispatch['state'] = print_state |
| 144 | |
| 145 | def print_properties(self, o): |
| 146 | write = self.stream.write |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 147 | write('Supports: {s} \n'.format(s=" ".join(o))) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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 |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 158 | write('Source Code: [{o}]({o}) \n'.format(o=o)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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 Troan | e774a8b | 2020-01-02 22:32:57 +0100 | [diff] [blame] | 165 | else: |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 166 | write('NOT IMPLEMENTED: {t}\n') |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 167 | |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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(' ', '-') |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 174 | write('[{t}](#{ref}) \n'.format(t=t, ref=ref)) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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): |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 197 | codeurl = 'https://git.fd.io/vpp/tree/src/' + \ |
| 198 | '/'.join(os.path.normpath(path).split('/')[1:-1]) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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 |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 213 | |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 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) |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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') |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 229 | args = parser.parse_args() |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 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 | |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 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 | |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 248 | for featurefile in filelist: |
| 249 | featurefile = featurefile.rstrip() |
| 250 | |
| 251 | # Load configuration file |
Paul Vinciguerra | 36686f4 | 2020-05-05 11:46:26 -0400 | [diff] [blame] | 252 | with open(featurefile, encoding='utf-8') as f: |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 253 | cfg = yaml.load(f, Loader=yaml.SafeLoader) |
Ole Troan | f3aebda | 2020-01-03 16:37:27 +0100 | [diff] [blame] | 254 | try: |
| 255 | validate(instance=cfg, schema=schema) |
| 256 | except exceptions.ValidationError: |
Dave Wallace | a079844 | 2020-09-23 11:38:25 -0400 | [diff] [blame] | 257 | print('File does not validate: {featurefile}' \ |
| 258 | .format(featurefile=featurefile), file=sys.stderr) |
Ole Troan | f3aebda | 2020-01-03 16:37:27 +0100 | [diff] [blame] | 259 | raise |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 260 | features[featurefile] = cfg |
| 261 | |
| 262 | if args.markdown: |
Ole Troan | dbbff85 | 2020-01-08 12:37:55 +0100 | [diff] [blame] | 263 | stream = StringIO() |
| 264 | tocstream, stream = output_markdown(features, fields, notfields) |
| 265 | print(tocstream.getvalue()) |
| 266 | print(stream.getvalue()) |
| 267 | stream.close() |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 268 | |
Paul Vinciguerra | ea1a651 | 2019-11-01 02:34:32 -0400 | [diff] [blame] | 269 | |
Ole Troan | 6a3064f | 2019-05-14 13:24:10 +0200 | [diff] [blame] | 270 | if __name__ == '__main__': |
| 271 | main() |