191 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			191 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | import random | ||
|  | import typing | ||
|  | 
 | ||
|  | 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 | ||
|  |     def from_text(cls, text: str) -> typing.Any: | ||
|  |         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) | ||
|  | 
 | ||
|  |     def __eq__(self, other: typing.Any): | ||
|  |         if isinstance(other, NumericOption): | ||
|  |             return self.value == other.value | ||
|  |         else: | ||
|  |             return typing.cast(bool, self.value == other) | ||
|  | 
 | ||
|  |     def __lt__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||
|  |         if isinstance(other, NumericOption): | ||
|  |             return self.value < other.value | ||
|  |         else: | ||
|  |             return self.value < other | ||
|  | 
 | ||
|  |     def __le__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||
|  |         if isinstance(other, NumericOption): | ||
|  |             return self.value <= other.value | ||
|  |         else: | ||
|  |             return self.value <= other | ||
|  | 
 | ||
|  |     def __gt__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||
|  |         if isinstance(other, NumericOption): | ||
|  |             return self.value > other.value | ||
|  |         else: | ||
|  |             return self.value > other | ||
|  | 
 | ||
|  |     def __ge__(self, other: typing.Union[int, float, NumericOption]) -> bool: | ||
|  |         if isinstance(other, NumericOption): | ||
|  |             return self.value >= other.value | ||
|  |         else: | ||
|  |             return self.value >= other | ||
|  | 
 | ||
|  |     def __int__(self) -> int: | ||
|  |         return int(self.value) | ||
|  | 
 | ||
|  |     def __and__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("& operator not supported for float values") | ||
|  | 
 | ||
|  |     def __floordiv__(self, other: typing.Any) -> int: | ||
|  |         return int(self.value // float(other)) | ||
|  | 
 | ||
|  |     def __invert__(self) -> int: | ||
|  |         raise TypeError("~ operator not supported for float values") | ||
|  | 
 | ||
|  |     def __lshift__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("<< operator not supported for float values") | ||
|  | 
 | ||
|  |     def __mod__(self, other: typing.Any) -> float: | ||
|  |         return self.value % float(other) | ||
|  | 
 | ||
|  |     def __neg__(self) -> float: | ||
|  |         return -self.value | ||
|  | 
 | ||
|  |     def __or__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("| operator not supported for float values") | ||
|  | 
 | ||
|  |     def __pos__(self) -> float: | ||
|  |         return +self.value | ||
|  | 
 | ||
|  |     def __rand__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("& operator not supported for float values") | ||
|  | 
 | ||
|  |     def __rfloordiv__(self, other: typing.Any) -> int: | ||
|  |         return int(float(other) // self.value) | ||
|  | 
 | ||
|  |     def __rlshift__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("<< operator not supported for float values") | ||
|  | 
 | ||
|  |     def __rmod__(self, other: typing.Any) -> float: | ||
|  |         return float(other) % self.value | ||
|  | 
 | ||
|  |     def __ror__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("| operator not supported for float values") | ||
|  | 
 | ||
|  |     def __round__(self, ndigits: typing.Optional[int] = None) -> float: | ||
|  |         return round(self.value, ndigits) | ||
|  | 
 | ||
|  |     def __rpow__(self, base: typing.Any) -> typing.Any: | ||
|  |         return base ** self.value | ||
|  | 
 | ||
|  |     def __rrshift__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError(">> operator not supported for float values") | ||
|  | 
 | ||
|  |     def __rshift__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError(">> operator not supported for float values") | ||
|  | 
 | ||
|  |     def __rxor__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("^ operator not supported for float values") | ||
|  | 
 | ||
|  |     def __xor__(self, other: typing.Any) -> int: | ||
|  |         raise TypeError("^ operator not supported for float values") |