File: common.py

package info (click to toggle)
flexparser 0.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 380 kB
  • sloc: python: 2,558; makefile: 4
file content (139 lines) | stat: -rw-r--r-- 3,686 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
from __future__ import annotations

import numbers
import typing as ty
from dataclasses import dataclass

from flexparser import flexparser as fp

from . import errors
from .pintimports import ParserHelper, UnitsContainer


@dataclass(frozen=True)
class Config:
    """Configuration used by the parser."""

    #: Indicates the output type of non integer numbers.
    non_int_type: ty.Type[numbers.Number] = float

    def to_scaled_units_container(self, s: str):
        return ParserHelper.from_string(s, self.non_int_type)

    def to_units_container(self, s: str):
        v = self.to_scaled_units_container(s)
        if v.scale != 1:
            raise errors.UnexpectedScaleInContainer(str(v.scale))
        return UnitsContainer(v)

    def to_dimension_container(self, s: str):
        v = self.to_units_container(s)
        _ = [check_dim(el) for el in v.keys()]
        return v

    def to_number(self, s: str) -> numbers.Number:
        """Try parse a string into a number (without using eval).

        The string can contain a number or a simple equation (3 + 4)

        Raises
        ------
        _NotNumeric
            If the string cannot be parsed as a number.
        """
        val = self.to_scaled_units_container(s)
        if len(val):
            raise NotNumeric(s)
        return val.scale


@dataclass(frozen=True)
class Equality(fp.ParsedStatement):
    """An equality statement contains a left and right hand separated
    by and equal (=) sign.

        lhs = rhs

    lhs and rhs are space stripped.
    """

    lhs: str
    rhs: str

    @classmethod
    def from_string(cls, s: str) -> fp.NullableParsedResult[Equality]:
        if "=" not in s:
            return None
        parts = [p.strip() for p in s.split("=")]
        if len(parts) != 2:
            return errors.DefinitionSyntaxError(
                f"Exactly two terms expected, not {len(parts)} (`{s}`)"
            )
        return cls(*parts)


@dataclass(frozen=True)
class Comment(fp.ParsedStatement):
    """Comments start with a # character.

        # This is a comment.
        ## This is also a comment.

    Captured value does not include the leading # character and space stripped.
    """

    comment: str

    @classmethod
    def from_string(cls, s: str) -> fp.NullableParsedResult[fp.ParsedStatement]:
        if not s.startswith("#"):
            return None
        return cls(s[1:].strip())


@dataclass(frozen=True)
class EndDirectiveBlock(fp.ParsedStatement):
    """An EndDirectiveBlock is simply an "@end" statement."""

    @classmethod
    def from_string(cls, s: str) -> fp.NullableParsedResult[EndDirectiveBlock]:
        if s == "@end":
            return cls()
        return None


@dataclass(frozen=True)
class DirectiveBlock(fp.Block):
    """Directive blocks have beginning statement starting with a @ character.
    and ending with a "@end" (captured using a EndDirectiveBlock).

    Subclass this class for convenience.
    """

    closing: EndDirectiveBlock


class NotNumeric(Exception):
    """Internal exception. Do not expose outside Pint"""

    def __init__(self, value):
        self.value = value


def is_dim(name: str) -> bool:
    return name[0] == "[" and name[-1] == "]"


def check_dim(name: str) -> ty.Union[errors.DefinitionSyntaxError, str]:
    name = name.strip()
    if not is_dim(name):
        raise errors.DefinitionSyntaxError(
            f"Dimension definition `{name}` must be enclosed by []."
        )

    if not str.isidentifier(name[1:-1]):
        raise errors.DefinitionSyntaxError(
            f"`{name[1:-1]}` is not a valid dimension name (must follow Python identifier rules)."
        )

    return name