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 semver import Version
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)
@@ -104,15 +111,14 @@ class MetadataCMake:
def from_dict(d: dict[str, Any]) -> "MetadataCMake":
"""Build a :class:`MetadataCMake` from its raw TOML table."""
raw_dependencies = dict_typed_get(d, "dependencies", list)
if raw_dependencies is not None:
dependencies_list: list[MetadataCMakeDependency] = []
for i, item in enumerate(raw_dependencies):
if not isinstance(item, dict):
raise TypeError(f"dependencies[{i}] must be a table")
dependencies_list.append(MetadataCMakeDependency.from_dict(item))
dependencies = tuple(dependencies_list)
else:
dependencies = None
dependencies = (
tuple(
MetadataCMakeDependency.from_dict(item)
for item in list_typed_iter(raw_dependencies, dict)
)
if raw_dependencies is not None
else None
)
return MetadataCMake(
namespace_name=dict_typed_get(d, "namespace_name", str),
@@ -157,15 +163,11 @@ class MetadataPkgConfig:
def from_dict(d: dict[str, Any]) -> "MetadataPkgConfig":
"""Build a :class:`MetadataPkgConfig` from its raw TOML table."""
raw_requires = dict_typed_get(d, "requires", list)
if raw_requires is not None:
requires_list: list[str] = []
for i, item in enumerate(raw_requires):
if not isinstance(item, str):
raise TypeError(f"requires[{i}] must be a string")
requires_list.append(item)
requires = tuple(requires_list)
else:
requires = None
requires = (
tuple(list_typed_iter(raw_requires, str))
if raw_requires is not None
else None
)
return MetadataPkgConfig(
id=dict_typed_get(d, "id", str),
@@ -210,13 +212,12 @@ class Metadata:
else:
min_version = None
raw_headers = dict_typed_get_required(d, "headers", list)
headers_list: list[MetadataHeader] = []
for i, item in enumerate(raw_headers):
if not isinstance(item, dict):
raise TypeError(f"headers[{i}] must be a table")
headers_list.append(MetadataHeader.from_dict(item))
headers = tuple(headers_list)
headers = tuple(
MetadataHeader.from_dict(item)
for item in list_typed_iter(
dict_typed_get_required(d, "headers", list), dict
)
)
raw_cmake = dict_typed_get(d, "cmake", dict)
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+)$")
"""The pattern for match host triple in ``rustc -vV``. The group 1 is the triple result."""
class MetadataExtractor:
"""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")
if proc.returncode != 0:
raise RuntimeError(
"fail to fetch host triple: "
+ stderr.decode("utf-8", errors="ignore")
"fail to fetch host triple: " + stderr.decode("utf-8", errors="ignore")
)
m = _HOST_TRIPLE_PATTERN.search(stdout.decode("utf-8", errors="strict"))
if m is None:
@@ -367,11 +368,8 @@ class MetadataExtractor:
:returns: The matching package item.
:raises RuntimeError: if no matching package is found.
"""
packages: list[Any] = dict_typed_get_required(cargo_metadata, "packages", list)
for i, package in enumerate(packages):
if not isinstance(package, dict):
raise TypeError(f"packages[{i}] must be a table")
packages = dict_typed_get_required(cargo_metadata, "packages", list)
for package in list_typed_iter(packages, dict):
raw_manifest_path = dict_typed_get_required(package, "manifest_path", str)
manifest_path = Path(raw_manifest_path)
if manifest_path == cargo_toml_path:
@@ -393,11 +391,8 @@ class MetadataExtractor:
# build the path to lib.rs for comparing
librs = cargo_toml_path.parent / "src" / "lib.rs"
# start checking
targets: list[Any] = dict_typed_get_required(cargo_package, "targets", list)
for i, target in enumerate(targets):
if not isinstance(target, dict):
raise TypeError(f"targets[{i}] must be a table")
targets = dict_typed_get_required(cargo_package, "targets", list)
for target in list_typed_iter(targets, dict):
raw_src_path = dict_typed_get_required(target, "src_path", str)
src_path = Path(raw_src_path)
if src_path == librs:
+15 -1
View File
@@ -7,7 +7,7 @@ validation and template-directory resolution.
from dataclasses import dataclass
from pathlib import Path
from re import Pattern, compile
from typing import Any, overload
from typing import Any, Iterator, overload
from semver import Version
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
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_+-]+")