refactor: improve metadata module

This commit is contained in:
2026-08-06 13:05:18 +08:00
parent ccf19ac0ab
commit 0b4ecccb71
2 changed files with 48 additions and 39 deletions
+33 -38
View File
@@ -18,7 +18,14 @@ from re import Pattern, compile
from typing import Any, Callable from typing import Any, Callable
from semver import Version from semver import Version
from . import utils from . import utils
from .utils import Triple, VERSION, dict_chain_get, dict_typed_get, dict_typed_get_required from .utils import (
Triple,
VERSION,
dict_chain_get,
dict_typed_get,
dict_typed_get_required,
list_typed_iter,
)
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -104,15 +111,14 @@ class MetadataCMake:
def from_dict(d: dict[str, Any]) -> "MetadataCMake": def from_dict(d: dict[str, Any]) -> "MetadataCMake":
"""Build a :class:`MetadataCMake` from its raw TOML table.""" """Build a :class:`MetadataCMake` from its raw TOML table."""
raw_dependencies = dict_typed_get(d, "dependencies", list) raw_dependencies = dict_typed_get(d, "dependencies", list)
if raw_dependencies is not None: dependencies = (
dependencies_list: list[MetadataCMakeDependency] = [] tuple(
for i, item in enumerate(raw_dependencies): MetadataCMakeDependency.from_dict(item)
if not isinstance(item, dict): for item in list_typed_iter(raw_dependencies, dict)
raise TypeError(f"dependencies[{i}] must be a table") )
dependencies_list.append(MetadataCMakeDependency.from_dict(item)) if raw_dependencies is not None
dependencies = tuple(dependencies_list) else None
else: )
dependencies = None
return MetadataCMake( return MetadataCMake(
namespace_name=dict_typed_get(d, "namespace_name", str), namespace_name=dict_typed_get(d, "namespace_name", str),
@@ -157,15 +163,11 @@ class MetadataPkgConfig:
def from_dict(d: dict[str, Any]) -> "MetadataPkgConfig": def from_dict(d: dict[str, Any]) -> "MetadataPkgConfig":
"""Build a :class:`MetadataPkgConfig` from its raw TOML table.""" """Build a :class:`MetadataPkgConfig` from its raw TOML table."""
raw_requires = dict_typed_get(d, "requires", list) raw_requires = dict_typed_get(d, "requires", list)
if raw_requires is not None: requires = (
requires_list: list[str] = [] tuple(list_typed_iter(raw_requires, str))
for i, item in enumerate(raw_requires): if raw_requires is not None
if not isinstance(item, str): else None
raise TypeError(f"requires[{i}] must be a string") )
requires_list.append(item)
requires = tuple(requires_list)
else:
requires = None
return MetadataPkgConfig( return MetadataPkgConfig(
id=dict_typed_get(d, "id", str), id=dict_typed_get(d, "id", str),
@@ -210,13 +212,12 @@ class Metadata:
else: else:
min_version = None min_version = None
raw_headers = dict_typed_get_required(d, "headers", list) headers = tuple(
headers_list: list[MetadataHeader] = [] MetadataHeader.from_dict(item)
for i, item in enumerate(raw_headers): for item in list_typed_iter(
if not isinstance(item, dict): dict_typed_get_required(d, "headers", list), dict
raise TypeError(f"headers[{i}] must be a table") )
headers_list.append(MetadataHeader.from_dict(item)) )
headers = tuple(headers_list)
raw_cmake = dict_typed_get(d, "cmake", dict) raw_cmake = dict_typed_get(d, "cmake", dict)
cmake = MetadataCMake.from_dict(raw_cmake) if raw_cmake is not None else None cmake = MetadataCMake.from_dict(raw_cmake) if raw_cmake is not None else None
@@ -260,6 +261,7 @@ _SUBPROCESS_TIMEOUT_SEC: int = 10
_HOST_TRIPLE_PATTERN: Pattern = compile(r"(?m)^host:\s*(\S+)$") _HOST_TRIPLE_PATTERN: Pattern = compile(r"(?m)^host:\s*(\S+)$")
"""The pattern for match host triple in ``rustc -vV``. The group 1 is the triple result.""" """The pattern for match host triple in ``rustc -vV``. The group 1 is the triple result."""
class MetadataExtractor: class MetadataExtractor:
"""Access layer over ``cargo metadata`` and the OMRF metadata table. """Access layer over ``cargo metadata`` and the OMRF metadata table.
@@ -313,8 +315,7 @@ class MetadataExtractor:
raise RuntimeError("fail to fetch host triple: timed out") raise RuntimeError("fail to fetch host triple: timed out")
if proc.returncode != 0: if proc.returncode != 0:
raise RuntimeError( raise RuntimeError(
"fail to fetch host triple: " "fail to fetch host triple: " + stderr.decode("utf-8", errors="ignore")
+ stderr.decode("utf-8", errors="ignore")
) )
m = _HOST_TRIPLE_PATTERN.search(stdout.decode("utf-8", errors="strict")) m = _HOST_TRIPLE_PATTERN.search(stdout.decode("utf-8", errors="strict"))
if m is None: if m is None:
@@ -367,11 +368,8 @@ class MetadataExtractor:
:returns: The matching package item. :returns: The matching package item.
:raises RuntimeError: if no matching package is found. :raises RuntimeError: if no matching package is found.
""" """
packages: list[Any] = dict_typed_get_required(cargo_metadata, "packages", list) packages = dict_typed_get_required(cargo_metadata, "packages", list)
for i, package in enumerate(packages): for package in list_typed_iter(packages, dict):
if not isinstance(package, dict):
raise TypeError(f"packages[{i}] must be a table")
raw_manifest_path = dict_typed_get_required(package, "manifest_path", str) raw_manifest_path = dict_typed_get_required(package, "manifest_path", str)
manifest_path = Path(raw_manifest_path) manifest_path = Path(raw_manifest_path)
if manifest_path == cargo_toml_path: if manifest_path == cargo_toml_path:
@@ -393,11 +391,8 @@ class MetadataExtractor:
# build the path to lib.rs for comparing # build the path to lib.rs for comparing
librs = cargo_toml_path.parent / "src" / "lib.rs" librs = cargo_toml_path.parent / "src" / "lib.rs"
# start checking # start checking
targets: list[Any] = dict_typed_get_required(cargo_package, "targets", list) targets = dict_typed_get_required(cargo_package, "targets", list)
for i, target in enumerate(targets): for target in list_typed_iter(targets, dict):
if not isinstance(target, dict):
raise TypeError(f"targets[{i}] must be a table")
raw_src_path = dict_typed_get_required(target, "src_path", str) raw_src_path = dict_typed_get_required(target, "src_path", str)
src_path = Path(raw_src_path) src_path = Path(raw_src_path)
if src_path == librs: if src_path == librs:
+15 -1
View File
@@ -7,7 +7,7 @@ validation and template-directory resolution.
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from re import Pattern, compile from re import Pattern, compile
from typing import Any, overload from typing import Any, Iterator, overload
from semver import Version from semver import Version
VERSION: Version = Version(1, 0, 0) VERSION: Version = Version(1, 0, 0)
@@ -106,6 +106,20 @@ def dict_chain_get(d: dict[str, Any], *args: str) -> dict[str, Any]:
return d return d
def list_typed_iter[T](lst: list[Any], ty: type[T]) -> Iterator[T]:
"""Yield each element of ``lst`` after checking it is an instance of ``ty``.
:param lst: The list to iterate.
:param ty: Expected type of every element.
:returns: An iterator over the validated elements, typed ``T``.
:raises TypeError: if an element is not an instance of ``ty``.
"""
for i, item in enumerate(lst):
if not isinstance(item, ty):
raise TypeError(f"item {i} of the given list is not a {ty.__name__}")
yield item
_NAME_PATTERN: Pattern = compile(r"[a-zA-Z0-9_+-]+") _NAME_PATTERN: Pattern = compile(r"[a-zA-Z0-9_+-]+")