Coverage for ramose / filters.py: 100%
46 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-01 13:49 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-01 13:49 +0000
1# SPDX-FileCopyrightText: 2026 Arcangelo Massari <arcangelo.massari@unibo.it>
2#
3# SPDX-License-Identifier: ISC
5from __future__ import annotations
7from collections.abc import Mapping
8from pathlib import Path
9from re import sub
11import yaml
13FiltersConfig = Mapping[str, Mapping[str, "str | dict[str, str]"]]
15_VALUE_PATTERN = r"\{\{\s*value\s*\}\}"
16_ALWAYS_EMPTY_FILTER = "FILTER(false)"
19def render(template: str, value: str) -> str:
20 return sub(_VALUE_PATTERN, value, template)
23def _select_template(key: str, spec: str | dict[str, str], value: str) -> str:
24 if isinstance(spec, str):
25 return spec
26 if value not in spec:
27 msg = f"The value '{value}' is not valid for filter '{key}', valid values are {', '.join(sorted(spec))}"
28 raise ValueError(msg)
29 return spec[value]
32def _is_always_empty(fragment: str) -> bool:
33 return fragment.strip().upper() == _ALWAYS_EMPTY_FILTER.upper()
36def apply_filters(config: FiltersConfig, values: list[str]) -> dict[str, str]:
37 slots: list[str] = []
38 for slot_map in config.values():
39 for slot in slot_map:
40 if slot not in slots:
41 slots.append(slot)
42 result = dict.fromkeys(slots, "")
43 fragments: list[tuple[str, str]] = []
45 raw_pairs = (pair.strip() for value in values for pair in value.split(","))
46 for pair in (pair for pair in raw_pairs if pair):
47 key, value = pair.split(":", 1)
48 if key not in config:
49 msg = f"The filter '{key}' is not configured, configured filters are {', '.join(sorted(config))}"
50 raise ValueError(msg)
51 for slot, spec in config[key].items():
52 fragment = render(_select_template(key, spec, value), value)
53 fragments.append((slot, fragment))
55 always_empty_slots = {slot for slot, fragment in fragments if _is_always_empty(fragment)}
56 if always_empty_slots:
57 for slot in always_empty_slots:
58 result[slot] = _ALWAYS_EMPTY_FILTER
59 return result
61 for slot, fragment in fragments:
62 result[slot] = f"{result[slot]}\n{fragment}" if result[slot] else fragment
63 return result
66def load_filters_config(path: str) -> FiltersConfig:
67 return yaml.safe_load(Path(path).read_text(encoding="utf-8"))