# SPDX-License-Identifier: Apache-2.0 # Copyright 2021 The Meson development team from .loaderbase import LoaderBase from .model import ( Type, PosArg, VarArgs, Kwarg, Function, Method, ObjectType, Object, ReferenceManual, ) from mesonbuild import mlog from mesonbuild import mesonlib from pathlib import Path import typing as T class Template: d_feature_check: T.Dict[str, T.Any] = {} s_posarg: T.Dict[str, T.Any] = {} s_varargs: T.Dict[str, T.Any] = {} s_kwarg: T.Dict[str, T.Any] = {} s_function: T.Dict[str, T.Any] = {} s_object: T.Dict[str, T.Any] = {} class StrictTemplate(Template): def __init__(self) -> None: from strictyaml import Map, MapPattern, Optional, Str, Seq, Int, Bool, EmptyList, OrValidator # type: ignore[import-untyped] d_named_object = { 'name': Str(), 'description': Str(), } d_feture_check = { Optional('since', default=''): Str(), Optional('deprecated', default=''): Str(), } self.s_posarg = Map({ **d_feture_check, 'description': Str(), 'type': Str(), Optional('default', default=''): Str(), }) self.s_varargs = Map({ **d_named_object, **d_feture_check, 'type': Str(), Optional('min_varargs', default=-1): Int(), Optional('max_varargs', default=-1): Int(), }) self.s_kwarg = Map({ **d_feture_check, 'type': Str(), 'description': Str(), Optional('required', default=False): Bool(), Optional('default', default=''): Str(), }) self.s_function = Map({ **d_named_object, **d_feture_check, 'returns': Str(), Optional('notes', default=[]): OrValidator(Seq(Str()), EmptyList()), Optional('warnings', default=[]): OrValidator(Seq(Str()), EmptyList()), Optional('example', default=''): Str(), Optional('posargs'): MapPattern(Str(), self.s_posarg), Optional('optargs'): MapPattern(Str(), self.s_posarg), Optional('varargs'): self.s_varargs, Optional('posargs_inherit', default=''): Str(), Optional('optargs_inherit', default=''): Str(), Optional('varargs_inherit', default=''): Str(), Optional('kwargs'): MapPattern(Str(), self.s_kwarg), Optional('kwargs_inherit', default=[]): OrValidator(OrValidator(Seq(Str()), EmptyList()), Str()), Optional('arg_flattening', default=True): Bool(), }) self.s_object = Map({ **d_named_object, **d_feture_check, 'long_name': Str(), Optional('extends', default=''): Str(), Optional('notes', default=[]): OrValidator(Seq(Str()), EmptyList()), Optional('warnings', default=[]): OrValidator(Seq(Str()), EmptyList()), Optional('example', default=''): Str(), Optional('methods'): Seq(self.s_function), Optional('is_container', default=False): Bool() }) class FastTemplate(Template): d_feature_check: T.Dict[str, T.Any] = { 'since': '', 'deprecated': '', } s_posarg = { **d_feature_check, 'default': '', } s_varargs: T.Dict[str, T.Any] = { **d_feature_check, 'min_varargs': -1, 'max_varargs': -1, } s_kwarg = { **d_feature_check, 'required': False, 'default': '', } s_function = { **d_feature_check, 'notes': [], 'warnings': [], 'example': '', 'posargs': {}, 'optargs': {}, 'varargs': None, 'posargs_inherit': '', 'optargs_inherit': '', 'varargs_inherit': '', 'kwargs': {}, 'kwargs_inherit': [], 'arg_flattening': True, } s_object = { **d_feature_check, 'extends': '', 'notes': [], 'warnings': [], 'example': '', 'methods': [], 'is_container': False, } class LoaderYAML(LoaderBase): def __init__(self, yaml_dir: Path, strict: bool=True) -> None: super().__init__() self.yaml_dir = yaml_dir self.func_dir = self.yaml_dir / 'functions' self.elem_dir = self.yaml_dir / 'elementary' self.objs_dir = self.yaml_dir / 'objects' self.builtin_dir = self.yaml_dir / 'builtins' self.modules_dir = self.yaml_dir / 'modules' self.strict = strict template: Template if self.strict: import strictyaml def loader(file: str, template: T.Any, label: str) -> T.Dict: r: T.Dict = strictyaml.load(file, template, label=label).data return r self._load = loader template = StrictTemplate() else: import yaml from yaml import CLoader def loader(file: str, template: T.Any, label: str) -> T.Dict: return {**template, **yaml.load(file, Loader=CLoader)} self._load = loader template = FastTemplate() self.template = template def _fix_default(self, v: T.Dict) -> None: if v["default"] is False: v["default"] = "false" elif v["default"] is True: v["default"] = "true" else: v["default"] = str(v["default"]) def _process_function_base(self, raw: T.Dict, obj: T.Optional[Object] = None) -> Function: # Handle arguments posargs = raw.pop('posargs', {}) optargs = raw.pop('optargs', {}) varargs = raw.pop('varargs', None) kwargs = raw.pop('kwargs', {}) # Fix kwargs_inherit if isinstance(raw['kwargs_inherit'], str): raw['kwargs_inherit'] = [raw['kwargs_inherit']] # Parse args posargs_mapped: T.List[PosArg] = [] optargs_mapped: T.List[PosArg] = [] varargs_mapped: T.Optional[VarArgs] = None kwargs_mapped: T.Dict[str, Kwarg] = {} for k, v in posargs.items(): if not self.strict: v = {**self.template.s_posarg, **v} self._fix_default(v) v['type'] = Type(v['type']) posargs_mapped += [PosArg(name=k, **v)] for k, v in optargs.items(): if not self.strict: v = {**self.template.s_posarg, **v} self._fix_default(v) v['type'] = Type(v['type']) optargs_mapped += [PosArg(name=k, **v)] for k, v in kwargs.items(): if not self.strict: v = {**self.template.s_kwarg, **v} self._fix_default(v) v['type'] = Type(v['type']) kwargs_mapped[k] = Kwarg(name=k, **v) if varargs is not None: if not self.strict: varargs = {**self.template.s_varargs, **varargs} varargs['type'] = Type(varargs['type']) varargs_mapped = VarArgs(**varargs) raw['returns'] = Type(raw['returns']) # Build function object if obj is not None: return Method( posargs=posargs_mapped, optargs=optargs_mapped, varargs=varargs_mapped, kwargs=kwargs_mapped, obj=obj, **raw, ) return Function( posargs=posargs_mapped, optargs=optargs_mapped, varargs=varargs_mapped, kwargs=kwargs_mapped, **raw, ) def _load_function(self, path: Path, obj: T.Optional[Object] = None) -> Function: path_label = path.relative_to(self.yaml_dir).as_posix() mlog.log('Loading', mlog.bold(path_label)) raw = self._load(self.read_file(path), self.template.s_function, label=path_label) return self._process_function_base(raw) def _load_object(self, obj_type: ObjectType, path: Path) -> Object: path_label = path.relative_to(self.yaml_dir).as_posix() mlog.log(f'Loading', mlog.bold(path_label)) raw = self._load(self.read_file(path), self.template.s_object, label=path_label) def as_methods(mlist: T.List[Function]) -> T.List[Method]: res: T.List[Method] = [] for i in mlist: assert isinstance(i, Method) res += [i] return res methods = raw.pop('methods', []) obj = Object(methods=[], obj_type=obj_type, **raw) newmethods = [] for x in methods: if not self.strict: x = {**self.template.s_function, **x} newmethods += [self._process_function_base(x, obj)] obj.methods = as_methods(newmethods) return obj def _load_module(self, path: Path) -> T.List[Object]: assert path.is_dir() module = self._load_object(ObjectType.MODULE, path / 'module.yaml') objs = [] for p in path.iterdir(): if p.name == 'module.yaml': continue obj = self._load_object(ObjectType.RETURNED, p) obj.defined_by_module = module objs += [obj] return [module, *objs] def load_impl(self) -> ReferenceManual: mlog.log('Loading YAML reference manual') with mlog.nested(): manual = ReferenceManual( functions=[self._load_function(x) for x in self.func_dir.iterdir()], objects=mesonlib.listify([ [self._load_object(ObjectType.ELEMENTARY, x) for x in self.elem_dir.iterdir()], [self._load_object(ObjectType.RETURNED, x) for x in self.objs_dir.iterdir()], [self._load_object(ObjectType.BUILTIN, x) for x in self.builtin_dir.iterdir()], [self._load_module(x) for x in self.modules_dir.iterdir()] ], flatten=True) ) if not self.strict: mlog.warning('YAML reference manual loaded using the best-effort fastyaml loader. Results are not guaranteed to be stable or correct.') return manual