import re from functools import total_ordering from typing import Optional import math from ee.formatting import eng_str __all__ = [ "EeException", "StopToolException", "EeVal", "EeValType", "resistance_type", "capacitance_type", "inductance_type", "power_type", ] class EeException(Exception): pass class StopToolException(EeException): pass class EeValType(object): def __init__(self, symbol, alternate_symbols): self.symbol = symbol self.alternate_symbols = alternate_symbols resistance_type = EeValType("\u2126", ["ohm"]) capacitance_type = EeValType("F", []) inductance_type = EeValType("H", []) power_type = EeValType("W", []) @total_ordering class EeVal(object): units = ['F', 'Ohm', '\u03a9', # Ohm, the greek (upper case) letter '\u2126', # Ohm symbol, deprecated from unicode 'H'] exponents = { 'f': -15, 'p': -12, 'n': -9, 'u': -6, '\u00B5': -6, # The micro symbol 'm': -3, 'k': 3, 'M': 6, 'G': 9, 'T': 12, 'P': 15, 'E': 18, } keys = "".join(exponents.keys()) units = "|".join(units) r = re.compile( "([0-9]+(?:\\.[0-9]*)?(?:e-?[0-9]+)|[0-9]+\\.[0-9]+|\\.?[0-9]+|[0-9]+\\.?) *([" + keys + "]?) *(" + units + "?)") def __init__(self, s=None, value=None, exp=None, unit=None): # This is where I regret having a string in the API at once. if s: val = EeVal.parse(s) (self._value, self._exp, self._unit) = (val._value, val._exp, val._unit) else: if value is None or exp is None: raise EeException("Bad arguments, value and exp has to be set.") (self._value, self._exp, self._unit) = (value, exp, unit) @staticmethod def try_parse(s, expected_unit=None) -> Optional["EeVal"]: try: return EeVal.parse(s, expected_unit) except EeException: return None @staticmethod def parse(s, expected_unit=None) -> "EeVal": m = EeVal.r.match(s) if not m: raise EeException("Could not parse value: " + str(s)) gs = m.groups() value = float(gs[0]) exp = gs[1].strip() exp = EeVal.exponents.get(exp) if len(exp) > 0 else 0 unit = gs[2] if value != 0 and value < 1: e = math.ceil(math.log10(value)) exp = exp + e value = value * math.pow(10, -e) unit = unit if len(unit) > 0 else None if expected_unit is not None: if unit is None: unit = expected_unit elif unit != expected_unit: raise EeException("Bad unit when parsing, expected {}, got {}".format(expected_unit, unit)) return EeVal(None, value=float(value), exp=exp, unit=unit) @property def value(self) -> float: return self.__float__() @property def unit(self): return self._unit def set(self, value=None, exp=None, unit=None): return EeVal(None, value=value if value else self._value, exp=exp if exp else self._exp, unit=unit if unit else self._unit) def __hash__(self): return hash((self.__float__(), self._unit)) def __eq__(self, other): return math.isclose(self.__float__(), other.__float__()) and self._unit == other._unit def __lt__(self, other): # return ((self.__float__(), self._unit) < (other.__float__(), other._unit)) x = self.__float__() < other.__float__() if x != 0: return x if self._unit is None and other._unit is None: return 0 if self._unit is not None and other._unit is not None: return self._unit < other._unit return 1 if self.unit is not None else -1 def __str__(self): return eng_str(self.__float__(), self._unit) def __float__(self): return self._value * math.pow(10, self._exp)