aboutsummaryrefslogtreecommitdiff
path: root/docs/jsonvalidator.py
diff options
context:
space:
mode:
authorDaniel Mensinger <daniel@mensinger-ka.de>2021-10-09 15:47:34 +0200
committerDaniel Mensinger <daniel@mensinger-ka.de>2021-10-09 22:50:55 +0200
commitd7ea066c5cd04c68c9ed2c3f1b7cd981d9b5f6ac (patch)
treeebb069d87bd334847ffb9b4d9e4d4bfbad4b53e5 /docs/jsonvalidator.py
parentd427c8fdb60ea9fb37673d3f23e4105501c1e42a (diff)
downloadmeson-d7ea066c5cd04c68c9ed2c3f1b7cd981d9b5f6ac.zip
meson-d7ea066c5cd04c68c9ed2c3f1b7cd981d9b5f6ac.tar.gz
meson-d7ea066c5cd04c68c9ed2c3f1b7cd981d9b5f6ac.tar.bz2
docs: Add a validator for the new JSON docs
Diffstat (limited to 'docs/jsonvalidator.py')
-rwxr-xr-xdocs/jsonvalidator.py195
1 files changed, 195 insertions, 0 deletions
diff --git a/docs/jsonvalidator.py b/docs/jsonvalidator.py
new file mode 100755
index 0000000..6a55ddb
--- /dev/null
+++ b/docs/jsonvalidator.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+
+# SPDX-License-Identifer: Apache-2.0
+# Copyright 2021 The Meson development team
+
+import argparse
+import json
+from pathlib import Path
+from copy import deepcopy
+
+import typing as T
+
+T_None = type(None)
+
+# Global root object
+root: dict
+
+def assert_has_typed_keys(path: str, data: dict, keys: T.Dict[str, T.Any]) -> dict:
+ assert set(data.keys()).issuperset(keys.keys()), f'{path}: DIFF: {set(data.keys()).difference(keys.keys())}'
+ res = dict()
+ for key, val in keys.items():
+ cur = data.pop(key)
+ assert isinstance(cur, val), f'{path}: type({key}: {cur}) != {val}'
+ res[key] = cur
+ return res
+
+def validate_base_obj(path: str, name: str, obj: dict) -> None:
+ expected: T.Dict[str, T.Any] = {
+ 'name': str,
+ 'description': str,
+ 'since': (str, T_None),
+ 'deprecated': (str, T_None),
+ 'notes': list,
+ 'warnings': list,
+ }
+ cur = assert_has_typed_keys(f'{path}.{name}', obj, expected)
+ assert cur['name'], f'{path}.{name}'
+ assert cur['description'], f'{path}.{name}'
+ assert cur['name'] == name, f'{path}.{name}'
+ assert all(isinstance(x, str) and x for x in cur['notes']), f'{path}.{name}'
+ assert all(isinstance(x, str) and x for x in cur['warnings']), f'{path}.{name}'
+
+def validate_type(path: str, typ: dict) -> None:
+ expected: T.Dict[str, T.Any] = {
+ 'obj': str,
+ 'holds': list,
+ }
+ cur = assert_has_typed_keys(path, typ, expected)
+ assert not typ, f'{path} has extra keys: {typ.keys()}'
+ assert cur['obj'] in root['objects'], path
+ for i in cur['holds']:
+ validate_type(path, i)
+
+def validate_arg(path: str, name: str, arg: dict) -> None:
+ validate_base_obj(path, name, arg)
+ expected: T.Dict[str, T.Any] = {
+ 'type': list,
+ 'type_str': str,
+ 'required': bool,
+ 'default': (str, T_None),
+ 'min_varargs': (int, T_None),
+ 'max_varargs': (int, T_None),
+ }
+ cur = assert_has_typed_keys(f'{path}.{name}', arg, expected)
+ assert not arg, f'{path}.{name} has extra keys: {arg.keys()}'
+ assert cur['type'], f'{path}.{name}'
+ assert cur['type_str'], f'{path}.{name}'
+ for i in cur['type']:
+ validate_type(f'{path}.{name}', i)
+ if cur['min_varargs'] is not None:
+ assert cur['min_varargs'] > 0, f'{path}.{name}'
+ if cur['max_varargs'] is not None:
+ assert cur['max_varargs'] > 0, f'{path}.{name}'
+
+def validate_function(path: str, name: str, func: dict) -> None:
+ validate_base_obj(path, name, func)
+ expected: T.Dict[str, T.Any] = {
+ 'returns': list,
+ 'returns_str': str,
+ 'example': (str, T_None),
+ 'posargs': dict,
+ 'optargs': dict,
+ 'kwargs': dict,
+ 'varargs': (dict, T_None),
+ }
+ cur = assert_has_typed_keys(f'{path}.{name}', func, expected)
+ assert not func, f'{path}.{name} has extra keys: {func.keys()}'
+ assert cur['returns'], f'{path}.{name}'
+ assert cur['returns_str'], f'{path}.{name}'
+ for i in cur['returns']:
+ validate_type(f'{path}.{name}', i)
+ for k, v in cur['posargs'].items():
+ validate_arg(f'{path}.{name}', k, v)
+ for k, v in cur['optargs'].items():
+ validate_arg(f'{path}.{name}', k, v)
+ for k, v in cur['kwargs'].items():
+ validate_arg(f'{path}.{name}', k, v)
+ if cur['varargs']:
+ validate_arg(f'{path}.{name}', cur['varargs']['name'], cur['varargs'])
+
+def validate_object(path: str, name: str, obj: dict) -> None:
+ validate_base_obj(path, name, obj)
+ expected: T.Dict[str, T.Any] = {
+ 'example': (str, T_None),
+ 'object_type': str,
+ 'methods': dict,
+ 'is_container': bool,
+ 'extends': (str, T_None),
+ 'returned_by': list,
+ 'extended_by': list,
+ 'defined_by_module': (str, T_None),
+ }
+ cur = assert_has_typed_keys(f'{path}.{name}', obj, expected)
+ assert not obj, f'{path}.{name} has extra keys: {obj.keys()}'
+ for key, val in cur['methods'].items():
+ validate_function(f'{path}.{name}', key, val)
+ if cur['extends'] is not None:
+ assert cur['extends'] in root['objects'], f'{path}.{name}'
+ assert all(isinstance(x, str) for x in cur['returned_by']), f'{path}.{name}'
+ assert all(isinstance(x, str) for x in cur['extended_by']), f'{path}.{name}'
+ assert all(x in root['objects'] for x in cur['extended_by']), f'{path}.{name}'
+ if cur['defined_by_module'] is not None:
+ assert cur['defined_by_module'] in root['objects'], f'{path}.{name}'
+ assert cur['object_type'] == 'RETURNED', f'{path}.{name}'
+ assert root['objects'][cur['defined_by_module']]['object_type'] == 'MODULE', f'{path}.{name}'
+ assert name in root['objects_by_type']['modules'][cur['defined_by_module']], f'{path}.{name}'
+ return
+ assert cur['object_type'] in {'ELEMENTARY', 'BUILTIN', 'MODULE', 'RETURNED'}, f'{path}.{name}'
+ if cur['object_type'] == 'ELEMENTARY':
+ assert name in root['objects_by_type']['elementary'], f'{path}.{name}'
+ if cur['object_type'] == 'BUILTIN':
+ assert name in root['objects_by_type']['builtins'], f'{path}.{name}'
+ if cur['object_type'] == 'RETURNED':
+ assert name in root['objects_by_type']['returned'], f'{path}.{name}'
+ if cur['object_type'] == 'MODULE':
+ assert name in root['objects_by_type']['modules'], f'{path}.{name}'
+
+def main() -> int:
+ global root
+
+ parser = argparse.ArgumentParser(description='Meson JSON docs validator')
+ parser.add_argument('doc_file', type=Path, help='The JSON docs to validate')
+ args = parser.parse_args()
+
+ root_tmp = json.loads(args.doc_file.read_text(encoding='utf-8'))
+ root = deepcopy(root_tmp)
+ assert isinstance(root, dict)
+
+ expected: T.Dict[str, T.Any] = {
+ 'version_major': int,
+ 'version_minor': int,
+ 'meson_version': str,
+ 'functions': dict,
+ 'objects': dict,
+ 'objects_by_type': dict,
+ }
+ cur = assert_has_typed_keys('root', root_tmp, expected)
+ assert not root_tmp, f'root has extra keys: {root_tmp.keys()}'
+
+ refs = cur['objects_by_type']
+ expected = {
+ 'elementary': list,
+ 'builtins': list,
+ 'returned': list,
+ 'modules': dict,
+ }
+ assert_has_typed_keys(f'root.objects_by_type', refs, expected)
+ assert not refs, f'root.objects_by_type has extra keys: {refs.keys()}'
+ assert all(isinstance(x, str) for x in root['objects_by_type']['elementary'])
+ assert all(isinstance(x, str) for x in root['objects_by_type']['builtins'])
+ assert all(isinstance(x, str) for x in root['objects_by_type']['returned'])
+ assert all(isinstance(x, str) for x in root['objects_by_type']['modules'])
+ assert all(x in root['objects'] for x in root['objects_by_type']['elementary'])
+ assert all(x in root['objects'] for x in root['objects_by_type']['builtins'])
+ assert all(x in root['objects'] for x in root['objects_by_type']['returned'])
+ assert all(x in root['objects'] for x in root['objects_by_type']['modules'])
+ assert all(root['objects'][x]['object_type'] == 'ELEMENTARY' for x in root['objects_by_type']['elementary'])
+ assert all(root['objects'][x]['object_type'] == 'BUILTIN' for x in root['objects_by_type']['builtins'])
+ assert all(root['objects'][x]['object_type'] == 'RETURNED' for x in root['objects_by_type']['returned'])
+ assert all(root['objects'][x]['object_type'] == 'MODULE' for x in root['objects_by_type']['modules'])
+
+ # Check that module references are correct
+ assert all(all(isinstance(x, str) for x in v) for k, v in root['objects_by_type']['modules'].items())
+ assert all(all(x in root['objects'] for x in v) for k, v in root['objects_by_type']['modules'].items())
+ assert all(all(root['objects'][x]['defined_by_module'] == k for x in v) for k, v in root['objects_by_type']['modules'].items())
+
+ for key, val in cur['functions'].items():
+ validate_function('root', key, val)
+ for key, val in cur['objects'].items():
+ validate_object('root', key, val)
+
+ return 0
+
+if __name__ == '__main__':
+ raise SystemExit(main())