| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  | import random | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  | from typing import cast, Any | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | from Options import FreeText, NumericOption | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class FloatRangeText(FreeText, NumericOption): | 
					
						
							|  |  |  |     """FreeText option optimized for entering float numbers.
 | 
					
						
							|  |  |  |     Supports everything that Range supports. | 
					
						
							|  |  |  |     range_start and range_end have to be floats, while default has to be a string."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     default = "0.0" | 
					
						
							|  |  |  |     value: float | 
					
						
							|  |  |  |     range_start: float = 0.0 | 
					
						
							|  |  |  |     range_end: float = 1.0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, value: str): | 
					
						
							|  |  |  |         super().__init__(value) | 
					
						
							|  |  |  |         value = value.lower() | 
					
						
							|  |  |  |         if value.startswith("random"): | 
					
						
							|  |  |  |             self.value = self.weighted_range(value) | 
					
						
							|  |  |  |         elif value == "default" and hasattr(self, "default"): | 
					
						
							|  |  |  |             self.value = float(self.default) | 
					
						
							|  |  |  |         elif value == "high": | 
					
						
							|  |  |  |             self.value = self.range_end | 
					
						
							|  |  |  |         elif value == "low": | 
					
						
							|  |  |  |             self.value = self.range_start | 
					
						
							|  |  |  |         elif self.range_start == 0.0 \ | 
					
						
							|  |  |  |                 and hasattr(self, "default") \ | 
					
						
							|  |  |  |                 and self.default != "0.0" \ | 
					
						
							|  |  |  |                 and value in ("true", "false"): | 
					
						
							|  |  |  |             # these are the conditions where "true" and "false" make sense | 
					
						
							|  |  |  |             if value == "true": | 
					
						
							|  |  |  |                 self.value = float(self.default) | 
					
						
							|  |  |  |             else:  # "false" | 
					
						
							|  |  |  |                 self.value = 0.0 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 self.value = float(value) | 
					
						
							|  |  |  |             except ValueError: | 
					
						
							|  |  |  |                 raise Exception(f"Invalid value for option {self.__class__.__name__}: {value}") | 
					
						
							|  |  |  |             except OverflowError: | 
					
						
							|  |  |  |                 raise Exception(f"Out of range floating value for option {self.__class__.__name__}: {value}") | 
					
						
							|  |  |  |             if self.value < self.range_start: | 
					
						
							|  |  |  |                 raise Exception(f"{value} is lower than minimum {self.range_start} for option {self.__class__.__name__}") | 
					
						
							|  |  |  |             if self.value > self.range_end: | 
					
						
							|  |  |  |                 raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def from_text(cls, text: str) -> Any: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         return cls(text) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def weighted_range(cls, text: str) -> float: | 
					
						
							|  |  |  |         if text == "random-low": | 
					
						
							|  |  |  |             return random.triangular(cls.range_start, cls.range_end, cls.range_start) | 
					
						
							|  |  |  |         elif text == "random-high": | 
					
						
							|  |  |  |             return random.triangular(cls.range_start, cls.range_end, cls.range_end) | 
					
						
							|  |  |  |         elif text == "random-middle": | 
					
						
							|  |  |  |             return random.triangular(cls.range_start, cls.range_end) | 
					
						
							|  |  |  |         elif text.startswith("random-range-"): | 
					
						
							|  |  |  |             return cls.custom_range(text) | 
					
						
							|  |  |  |         elif text == "random": | 
					
						
							|  |  |  |             return random.uniform(cls.range_start, cls.range_end) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise Exception(f"random text \"{text}\" did not resolve to a recognized pattern. " | 
					
						
							|  |  |  |                             f"Acceptable values are: random, random-high, random-middle, random-low, " | 
					
						
							|  |  |  |                             f"random-range-low-<min>-<max>, random-range-middle-<min>-<max>, " | 
					
						
							|  |  |  |                             f"random-range-high-<min>-<max>, or random-range-<min>-<max>.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def custom_range(cls, text: str) -> float: | 
					
						
							|  |  |  |         textsplit = text.split("-") | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             random_range = [float(textsplit[len(textsplit) - 2]), float(textsplit[len(textsplit) - 1])] | 
					
						
							|  |  |  |         except ValueError: | 
					
						
							|  |  |  |             raise ValueError(f"Invalid random range {text} for option {cls.__name__}") | 
					
						
							|  |  |  |         except OverflowError: | 
					
						
							|  |  |  |             raise Exception(f"Out of range floating value for option {cls.__name__}: {text}") | 
					
						
							|  |  |  |         random_range.sort() | 
					
						
							|  |  |  |         if random_range[0] < cls.range_start or random_range[1] > cls.range_end: | 
					
						
							|  |  |  |             raise Exception( | 
					
						
							|  |  |  |                 f"{random_range[0]}-{random_range[1]} is outside allowed range " | 
					
						
							|  |  |  |                 f"{cls.range_start}-{cls.range_end} for option {cls.__name__}") | 
					
						
							|  |  |  |         if text.startswith("random-range-low"): | 
					
						
							|  |  |  |             return random.triangular(random_range[0], random_range[1], random_range[0]) | 
					
						
							|  |  |  |         elif text.startswith("random-range-middle"): | 
					
						
							|  |  |  |             return random.triangular(random_range[0], random_range[1]) | 
					
						
							|  |  |  |         elif text.startswith("random-range-high"): | 
					
						
							|  |  |  |             return random.triangular(random_range[0], random_range[1], random_range[1]) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return random.uniform(random_range[0], random_range[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def current_key(self) -> str: | 
					
						
							|  |  |  |         return str(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def get_option_name(cls, value: float) -> str: | 
					
						
							|  |  |  |         return str(value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __eq__(self, other: Any): | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         if isinstance(other, NumericOption): | 
					
						
							|  |  |  |             return self.value == other.value | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |             return cast(bool, self.value == other) | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __lt__(self, other: int | float | NumericOption) -> bool: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         if isinstance(other, NumericOption): | 
					
						
							|  |  |  |             return self.value < other.value | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return self.value < other | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __le__(self, other: int | float | NumericOption) -> bool: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         if isinstance(other, NumericOption): | 
					
						
							|  |  |  |             return self.value <= other.value | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return self.value <= other | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __gt__(self, other: int | float | NumericOption) -> bool: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         if isinstance(other, NumericOption): | 
					
						
							|  |  |  |             return self.value > other.value | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return self.value > other | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __ge__(self, other: int | float | NumericOption) -> bool: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         if isinstance(other, NumericOption): | 
					
						
							|  |  |  |             return self.value >= other.value | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return self.value >= other | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __int__(self) -> int: | 
					
						
							|  |  |  |         return int(self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __and__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("& operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __floordiv__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         return int(self.value // float(other)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __invert__(self) -> int: | 
					
						
							|  |  |  |         raise TypeError("~ operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __lshift__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("<< operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __mod__(self, other: Any) -> float: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         return self.value % float(other) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __neg__(self) -> float: | 
					
						
							|  |  |  |         return -self.value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __or__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("| operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __pos__(self) -> float: | 
					
						
							|  |  |  |         return +self.value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rand__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("& operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rfloordiv__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         return int(float(other) // self.value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rlshift__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("<< operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rmod__(self, other: Any) -> float: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         return float(other) % self.value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __ror__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("| operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __round__(self, ndigits: int | None = None) -> float: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         return round(self.value, ndigits) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rpow__(self, base: Any) -> Any: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         return base ** self.value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rrshift__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError(">> operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rshift__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError(">> operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __rxor__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("^ operator not supported for float values") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 17:01:57 +02:00
										 |  |  |     def __xor__(self, other: Any) -> int: | 
					
						
							| 
									
										
										
										
											2025-05-21 14:30:39 +02:00
										 |  |  |         raise TypeError("^ operator not supported for float values") |