Coverage for src/debputy/manifest_parser/mapper_code.py: 58%
61 statements
« prev ^ index » next coverage.py v7.8.2, created at 2026-01-16 17:20 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2026-01-16 17:20 +0000
1import dataclasses
2from typing import (
3 TypeVar,
4 Optional,
5 TYPE_CHECKING,
6)
7from collections.abc import Callable, Sequence, Mapping
9from debputy.manifest_parser.exceptions import ManifestTypeException
10from debputy.packages import BinaryPackage
11from debputy.util import assume_not_none
13if TYPE_CHECKING:
14 from debputy.manifest_parser.util import AttributePath
15 from debputy.manifest_parser.parser_data import ParserContextData
17S = TypeVar("S")
18T = TypeVar("T")
21@dataclasses.dataclass(slots=True, frozen=True)
22class PackageSelectorRule:
23 valid_values: frozenset[str]
24 impl: Callable[[BinaryPackage, str], bool]
27def _pkg_selector_arch(package: BinaryPackage, selection_value: str) -> bool:
28 if selection_value == "all":
29 return package.is_arch_all
30 return not package.is_arch_all
33PACKAGE_SELECTORS: Mapping[str, PackageSelectorRule] = {
34 "arch": PackageSelectorRule(
35 frozenset(["all", "any"]),
36 _pkg_selector_arch,
37 ),
38 "package-type": PackageSelectorRule(
39 frozenset(["deb", "udeb"]),
40 lambda p, v: p.package_type == v,
41 ),
42}
45def type_mapper_str2package(
46 raw_package_name: str,
47 ap: "AttributePath",
48 opc: Optional["ParserContextData"],
49) -> BinaryPackage:
50 pc = assume_not_none(opc)
51 if "{{" in raw_package_name:
52 resolved_package_name = pc.substitution.substitute(raw_package_name, ap.path)
53 else:
54 resolved_package_name = raw_package_name
56 package_name_in_message = raw_package_name
57 if resolved_package_name != raw_package_name:
58 package_name_in_message = f'"{resolved_package_name}" ["{raw_package_name}"]'
60 if not pc.is_known_package(resolved_package_name): 60 ↛ 61line 60 didn't jump to line 61 because the condition on line 60 was never true
61 package_names = ", ".join(pc.binary_packages)
62 raise ManifestTypeException(
63 f'The value {package_name_in_message} (from "{ap.path}") does not reference a package declared in'
64 f" debian/control. Valid package names are: {package_names}"
65 )
66 package_data = pc.binary_package_data(resolved_package_name)
67 if package_data.is_auto_generated_package: 67 ↛ 68line 67 didn't jump to line 68 because the condition on line 67 was never true
68 package_names = ", ".join(pc.binary_packages)
69 raise ManifestTypeException(
70 f'The package name {package_name_in_message} (from "{ap.path}") references an auto-generated package.'
71 " However, auto-generated packages are now permitted here. Valid package names are:"
72 f" {package_names}"
73 )
74 return package_data.binary_package
77@dataclasses.dataclass(slots=True, frozen=True)
78class PackageSelector:
79 matched_binary_packages: Sequence[BinaryPackage]
81 @classmethod
82 def parse(
83 cls,
84 raw_value: str,
85 attribute_path: "AttributePath",
86 parser_context: "ParserContextData",
87 ) -> "PackageSelector":
88 pc = assume_not_none(parser_context)
89 if ":" in raw_value:
90 key, value = raw_value.split(":", maxsplit=1)
91 selector = PACKAGE_SELECTORS.get(key)
92 if selector is None:
93 package_names = ", ".join(pc.binary_packages)
94 valid_selectors = ", ".join(
95 f"{k}:<value>" for k in sorted(PACKAGE_SELECTORS)
96 )
97 raise ManifestTypeException(
98 f'Unknown package selector "{raw_value}" (from "{attribute_path.path}").'
99 f" Valid values here are: {package_names}, OR one of {valid_selectors}"
100 )
102 if value not in selector.valid_values:
103 valid_values = ", ".join(sorted(selector.valid_values))
104 raise ManifestTypeException(
105 f'The selection criteria "{key}" does not accept "{value}" (from "{attribute_path.path}").'
106 f" Valid values here are: {valid_values}"
107 )
109 matches = tuple(
110 p
111 for p in parser_context.binary_packages.values()
112 if selector.impl(p, value)
113 )
114 return PackageSelector(matches)
115 package = type_mapper_str2package(raw_value, attribute_path, parser_context)
116 return PackageSelector((package,))
119def wrap_into_list(
120 x: T,
121 _ap: "AttributePath",
122 _pc: Optional["ParserContextData"],
123) -> list[T]:
124 return [x]
127def normalize_into_list(
128 x: T | list[T],
129 _ap: "AttributePath",
130 _pc: Optional["ParserContextData"],
131) -> list[T]:
132 return x if isinstance(x, list) else [x]
135def map_each_element(
136 mapper: Callable[[S, "AttributePath", Optional["ParserContextData"]], T],
137) -> Callable[[list[S], "AttributePath", Optional["ParserContextData"]], list[T]]:
138 def _generated_mapper(
139 xs: list[S],
140 ap: "AttributePath",
141 pc: Optional["ParserContextData"],
142 ) -> list[T]:
143 return [mapper(s, ap[i], pc) for i, s in enumerate(xs)]
145 return _generated_mapper