| 
4 | 4 | from abc import ABC, abstractmethod  | 
5 | 5 | from collections import OrderedDict  | 
6 | 6 | from inspect import isclass  | 
 | 7 | +from itertools import chain  | 
7 | 8 | from pathlib import Path  | 
8 |  | -from typing import Any, Callable, Dict, Generic, Iterator, List, Literal, Optional, Set, TypeVar, Union, cast  | 
 | 9 | +from typing import Any, Callable, Dict, Generic, Iterable, Iterator, List, Literal, Optional, Set, TypeVar, Union, cast  | 
9 | 10 | 
 
  | 
 | 11 | +from tox.config.loader.ini.factor import find_factor_groups  | 
10 | 12 | from tox.config.types import Command, EnvList  | 
11 | 13 | 
 
  | 
12 | 14 | _NO_MAPPING = object()  | 
 | 
16 | 18 | Factory = Optional[Callable[[object], T]]  # note the argument is anything, due e.g. memory loader can inject anything  | 
17 | 19 | 
 
  | 
18 | 20 | 
 
  | 
 | 21 | +class ConditionalValue(Generic[T]):  | 
 | 22 | +    """Value with a condition."""  | 
 | 23 | + | 
 | 24 | +    def __init__(self, value: T, condition: str | None) -> None:  | 
 | 25 | +        self.value = value  | 
 | 26 | +        self.condition = condition  | 
 | 27 | +        self._groups = tuple(find_factor_groups(condition)) if condition is not None else ()  | 
 | 28 | + | 
 | 29 | +    def matches(self, env_name: str) -> bool:  | 
 | 30 | +        """Return whether the value matches the environment name."""  | 
 | 31 | +        if self.condition is None:  | 
 | 32 | +            return True  | 
 | 33 | + | 
 | 34 | +        # Split env_name to factors.  | 
 | 35 | +        env_factors = set(chain.from_iterable([(i for i, _ in a) for a in find_factor_groups(env_name)]))  | 
 | 36 | + | 
 | 37 | +        matches = []  | 
 | 38 | +        for group in self._groups:  | 
 | 39 | +            group_matches = []  | 
 | 40 | +            for factor, negate in group:  | 
 | 41 | +                group_matches.append((factor in env_factors) ^ negate)  | 
 | 42 | +            matches.append(all(group_matches))  | 
 | 43 | +        return any(matches)  | 
 | 44 | + | 
 | 45 | + | 
 | 46 | +class ConditionalSetting(Generic[T]):  | 
 | 47 | +    """Setting whose value depends on various conditions."""  | 
 | 48 | + | 
 | 49 | +    def __init__(self, values: typing.Iterable[ConditionalValue[T]]) -> None:  | 
 | 50 | +        self.values = tuple(values)  | 
 | 51 | + | 
 | 52 | +    def filter(self, env_name: str) -> Iterable[T]:  | 
 | 53 | +        """Filter values for the environment."""  | 
 | 54 | +        for value in self.values:  | 
 | 55 | +            if value.matches(env_name):  | 
 | 56 | +                yield value.value  | 
 | 57 | + | 
 | 58 | + | 
19 | 59 | class Convert(ABC, Generic[T]):  | 
20 | 60 |     """A class that converts a raw type to a given tox (python) type."""  | 
21 | 61 | 
 
  | 
 | 
0 commit comments