Source code for PyR3.factory.fields.Unit

# -*- coding: utf-8 -*-
from __future__ import annotations

import math
import re
from numbers import Number
from typing import List, Tuple

from .Field import Field


class _SuffixParser:

    _float_regex = r"(?P<VALUE>[\-+]?[0-9]*\.?[0-9]+)"
    tokens: List[re.Pattern, float]
    suffixes: List[str]

    def __init__(self, suffix_to_value: Tuple[Tuple[str, float]]) -> None:
        self.tokens = []
        self.suffixes = []
        for suffix, multiplier in suffix_to_value:
            self.suffixes.append(suffix)
            token = re.compile(f"{self._float_regex}{suffix}")
            self.tokens.append((token, multiplier))

    def parse(self, string: str) -> float:
        total = 0
        index = 0
        while index < len(string):
            for token, multiplier in self.tokens:
                token: re.Pattern
                if match := token.match(string, pos=index):
                    total += float(match.groupdict()["VALUE"]) * multiplier
                    index = match.end()
                    break
            else:
                index += 1
        return total

    def __str__(self) -> str:
        return f"SuffixParser, suffixes: {self.suffixes}"

    __repr__ = __str__


[docs]class Length(Field): """Accepts float with optional length unit suffix. Unit suffix causes float value to be converted to value with unit denoted by `output_unit`. Valid unit suffixes are: - **mil** for mils - **in** for inches - **ft** for feets - **mm** for millimeters - **cm** for centimeters - **dm** for decimeters - **m** for meters Signs that doesn't match anything are ignored and treated as separators. """ _suffix_to_value_map = ( ("mil", 2.54 * 1e-5), ("in", 0.0254), ("ft", 0.3048), ("mm", 0.001), ("cm", 0.01), ("dm", 0.1), ("m", 1), ("", 1), ) parser = _SuffixParser(_suffix_to_value_map) _suffix_to_value_map = dict(_suffix_to_value_map) def __init__( self, *, output_unit: str = "m", default: str | Number = None, ) -> None: self.output_divider = self._suffix_to_value_map[output_unit] if default is not None: self.default = self.clean_value(default)
[docs] def clean_value(self, value: str | Number) -> float: if isinstance(value, str): return self.parser.parse(value) elif isinstance(value, Number): return float(value) else: self._raise_invalid_value_type(value)
[docs] def digest(self, literal: str | Number = None) -> float: """Returns total value contained in the literal in meters. :param literal: literal to consume or Number :type literal: Union[str, Number] :raises TypeError: If other type than str or Number is given. :raises KeyError: If value is None and no default is given. :return: total in meters. :rtype: float """ if literal is None: value = self.get_default() else: value = self.clean_value(literal) return self._convert_to_output_unit(value)
def _convert_to_output_unit(self, value: float) -> float: return value / self.output_divider
[docs]class Angle(Length): """Accepts float with optional angle unit suffix. Unit suffix causes float value to be converted to value with unit denoted by `output_unit`. Valid unit suffixes are: - **rad** for radians - **π** / **pi** for radians, multiplied by π (3.14...) - **deg** for degrees - **"** / **sec** for seconds of angle - **'** / **min** for minutes of angle Signs that doesn't match anything are ignored and treated as separators. """ _suffix_to_value_map = ( ('"', math.pi / (3600 * 180)), ("sec", math.pi / (3600 * 180)), ("'", math.pi / (60 * 180)), ("min", math.pi / (60 * 180)), ("°", math.pi / 180), ("deg", math.pi / 180), ("π", math.pi), ("pi", math.pi), ("rad", 1), ("", 1), ) parser = _SuffixParser(_suffix_to_value_map) _suffix_to_value_map = dict(_suffix_to_value_map) def __init__( self, *, output_unit: str = "rad", default: str | Number = None ) -> None: super().__init__(output_unit=output_unit, default=default)