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")
|