Files
meson/docs/jsonvalidator.py
Dylan Baker e991c4d454 Use SPDX-License-Identifier consistently
This replaces all of the Apache blurbs at the start of each file with an
`# SPDX-License-Identifier: Apache-2.0` string. It also fixes existing
uses to be consistent in capitalization, and to be placed above any
copyright notices.

This removes nearly 3000 lines of boilerplate from the project (only
python files), which no developer cares to look at.

SPDX is in common use, particularly in the Linux kernel, and is the
recommended format for Meson's own `project(license: )` field
2023-12-13 15:19:21 -05:00

197 lines
8.2 KiB
Python
Executable File

#!/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())