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