| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: 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), | |
| 'arg_flattening': bool, | |
| } | |
| 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()) |