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