mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
parent
5cac74c4ac
commit
c07e9946e5
82 changed files with 16910 additions and 9 deletions
279
datasette/vendored/pint/delegates/txt_defparser/plain.py
Normal file
279
datasette/vendored/pint/delegates/txt_defparser/plain.py
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
"""
|
||||
pint.delegates.txt_defparser.plain
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Definitions for parsing:
|
||||
- Equality
|
||||
- CommentDefinition
|
||||
- PrefixDefinition
|
||||
- UnitDefinition
|
||||
- DimensionDefinition
|
||||
- DerivedDimensionDefinition
|
||||
- AliasDefinition
|
||||
|
||||
Notices that some of the checks are done within the
|
||||
format agnostic parent definition class.
|
||||
|
||||
See each one for a slighly longer description of the
|
||||
syntax.
|
||||
|
||||
:copyright: 2022 by Pint Authors, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
import flexparser as fp
|
||||
|
||||
from ...converters import Converter
|
||||
from ...facets.plain import definitions
|
||||
from ...util import UnitsContainer
|
||||
from ..base_defparser import ParserConfig, PintParsedStatement
|
||||
from . import common
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Equality(PintParsedStatement, definitions.Equality):
|
||||
"""An equality statement contains a left and right hand separated
|
||||
|
||||
lhs and rhs should be space stripped.
|
||||
"""
|
||||
|
||||
@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 common.DefinitionSyntaxError(
|
||||
f"Exactly two terms expected, not {len(parts)} (`{s}`)"
|
||||
)
|
||||
return cls(*parts)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CommentDefinition(PintParsedStatement, definitions.CommentDefinition):
|
||||
"""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.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, s: str) -> fp.NullableParsedResult[CommentDefinition]:
|
||||
if not s.startswith("#"):
|
||||
return None
|
||||
return cls(s[1:].strip())
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PrefixDefinition(PintParsedStatement, definitions.PrefixDefinition):
|
||||
"""Definition of a prefix::
|
||||
|
||||
<prefix>- = <value> [= <symbol>] [= <alias>] [ = <alias> ] [...]
|
||||
|
||||
Example::
|
||||
|
||||
deca- = 1e+1 = da- = deka-
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_string_and_config(
|
||||
cls, s: str, config: ParserConfig
|
||||
) -> fp.NullableParsedResult[PrefixDefinition]:
|
||||
if "=" not in s:
|
||||
return None
|
||||
|
||||
name, value, *aliases = s.split("=")
|
||||
|
||||
name = name.strip()
|
||||
if not name.endswith("-"):
|
||||
return None
|
||||
|
||||
name = name.rstrip("-")
|
||||
aliases = tuple(alias.strip().rstrip("-") for alias in aliases)
|
||||
|
||||
defined_symbol = None
|
||||
if aliases:
|
||||
if aliases[0] == "_":
|
||||
aliases = aliases[1:]
|
||||
else:
|
||||
defined_symbol, *aliases = aliases
|
||||
|
||||
aliases = tuple(alias for alias in aliases if alias not in ("", "_"))
|
||||
|
||||
try:
|
||||
value = config.to_number(value)
|
||||
except definitions.NotNumeric as ex:
|
||||
return common.DefinitionSyntaxError(
|
||||
f"Prefix definition ('{name}') must contain only numbers, not {ex.value}"
|
||||
)
|
||||
|
||||
try:
|
||||
return cls(name, value, defined_symbol, aliases)
|
||||
except Exception as exc:
|
||||
return common.DefinitionSyntaxError(str(exc))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UnitDefinition(PintParsedStatement, definitions.UnitDefinition):
|
||||
"""Definition of a unit::
|
||||
|
||||
<canonical name> = <relation to another unit or dimension> [= <symbol>] [= <alias>] [ = <alias> ] [...]
|
||||
|
||||
Example::
|
||||
|
||||
millennium = 1e3 * year = _ = millennia
|
||||
|
||||
Parameters
|
||||
----------
|
||||
reference : UnitsContainer
|
||||
Reference units.
|
||||
is_base : bool
|
||||
Indicates if it is a base unit.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_string_and_config(
|
||||
cls, s: str, config: ParserConfig
|
||||
) -> fp.NullableParsedResult[UnitDefinition]:
|
||||
if "=" not in s:
|
||||
return None
|
||||
|
||||
name, value, *aliases = (p.strip() for p in s.split("="))
|
||||
|
||||
defined_symbol = None
|
||||
if aliases:
|
||||
if aliases[0] == "_":
|
||||
aliases = aliases[1:]
|
||||
else:
|
||||
defined_symbol, *aliases = aliases
|
||||
|
||||
aliases = tuple(alias for alias in aliases if alias not in ("", "_"))
|
||||
|
||||
if ";" in value:
|
||||
[converter, modifiers] = value.split(";", 1)
|
||||
|
||||
try:
|
||||
modifiers = {
|
||||
key.strip(): config.to_number(value)
|
||||
for key, value in (part.split(":") for part in modifiers.split(";"))
|
||||
}
|
||||
except definitions.NotNumeric as ex:
|
||||
return common.DefinitionSyntaxError(
|
||||
f"Unit definition ('{name}') must contain only numbers in modifier, not {ex.value}"
|
||||
)
|
||||
|
||||
else:
|
||||
converter = value
|
||||
modifiers = {}
|
||||
|
||||
converter = config.to_scaled_units_container(converter)
|
||||
|
||||
try:
|
||||
reference = UnitsContainer(converter)
|
||||
# reference = converter.to_units_container()
|
||||
except common.DefinitionSyntaxError as ex:
|
||||
return common.DefinitionSyntaxError(f"While defining {name}: {ex}")
|
||||
|
||||
try:
|
||||
converter = Converter.from_arguments(scale=converter.scale, **modifiers)
|
||||
except Exception as ex:
|
||||
return common.DefinitionSyntaxError(
|
||||
f"Unable to assign a converter to the unit {ex}"
|
||||
)
|
||||
|
||||
try:
|
||||
return cls(name, defined_symbol, tuple(aliases), converter, reference)
|
||||
except Exception as ex:
|
||||
return common.DefinitionSyntaxError(str(ex))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DimensionDefinition(PintParsedStatement, definitions.DimensionDefinition):
|
||||
"""Definition of a root dimension::
|
||||
|
||||
[dimension name]
|
||||
|
||||
Example::
|
||||
|
||||
[volume]
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, s: str) -> fp.NullableParsedResult[DimensionDefinition]:
|
||||
s = s.strip()
|
||||
|
||||
if not (s.startswith("[") and "=" not in s):
|
||||
return None
|
||||
|
||||
return cls(s)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DerivedDimensionDefinition(
|
||||
PintParsedStatement, definitions.DerivedDimensionDefinition
|
||||
):
|
||||
"""Definition of a derived dimension::
|
||||
|
||||
[dimension name] = <relation to other dimensions>
|
||||
|
||||
Example::
|
||||
|
||||
[density] = [mass] / [volume]
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_string_and_config(
|
||||
cls, s: str, config: ParserConfig
|
||||
) -> fp.NullableParsedResult[DerivedDimensionDefinition]:
|
||||
if not (s.startswith("[") and "=" in s):
|
||||
return None
|
||||
|
||||
name, value, *aliases = s.split("=")
|
||||
|
||||
if aliases:
|
||||
return common.DefinitionSyntaxError(
|
||||
"Derived dimensions cannot have aliases."
|
||||
)
|
||||
|
||||
try:
|
||||
reference = config.to_dimension_container(value)
|
||||
except common.DefinitionSyntaxError as exc:
|
||||
return common.DefinitionSyntaxError(
|
||||
f"In {name} derived dimensions must only be referenced "
|
||||
f"to dimensions. {exc}"
|
||||
)
|
||||
|
||||
try:
|
||||
return cls(name.strip(), reference)
|
||||
except Exception as exc:
|
||||
return common.DefinitionSyntaxError(str(exc))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AliasDefinition(PintParsedStatement, definitions.AliasDefinition):
|
||||
"""Additional alias(es) for an already existing unit::
|
||||
|
||||
@alias <canonical name or previous alias> = <alias> [ = <alias> ] [...]
|
||||
|
||||
Example::
|
||||
|
||||
@alias meter = my_meter
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, s: str) -> fp.NullableParsedResult[AliasDefinition]:
|
||||
if not s.startswith("@alias "):
|
||||
return None
|
||||
name, *aliases = s[len("@alias ") :].split("=")
|
||||
|
||||
try:
|
||||
return cls(name.strip(), tuple(alias.strip() for alias in aliases))
|
||||
except Exception as exc:
|
||||
return common.DefinitionSyntaxError(str(exc))
|
||||
Loading…
Add table
Add a link
Reference in a new issue