Files
meson/docs/refman/loaderyaml.py
Eli Schwartz caa38dad45 fix broken type annotation imports being ignored
If an annotation could not be resolved, it's classified as a "missing
import" and our configuration ignored it:

```
Skipping analyzing "mesonbuild.backends": module is installed, but missing library stubs or py.typed marker
```

As far as mypy is concerned, this library may or may not exist, but it
doesn't have any typing information at all (may need to be installed
first).

We ignored this because of our docs/ and tools/ thirdparty dependencies,
but we really should not. It is trivial to install them, and then
enforce that this "just works".

By enforcing it, we also make sure typos get caught.
2023-11-26 17:12:52 -05:00

316 lines
10 KiB
Python

# Copyright 2021 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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