enhanced EnumBase and FlagsEnumBase to support induvidual documentation for each enum value via EnumValue

This commit is contained in:
Tim Rid 2022-12-24 12:00:23 +01:00
parent b7e92c3c7d
commit 916349f876
3 changed files with 163 additions and 3 deletions

View file

@ -18,7 +18,7 @@ from .generic_wrapper import (
ListContainer,
PathType,
)
from .tenum import EnumBase, FlagsEnumBase, TEnum, TFlagsEnum
from .tenum import EnumBase, EnumValue, FlagsEnumBase, TEnum, TFlagsEnum
__all__ = [
"DataclassBitStruct",
@ -32,6 +32,7 @@ __all__ = [
"csfield",
"sfield",
"EnumBase",
"EnumValue",
"FlagsEnumBase",
"TEnum",
"TFlagsEnum",

View file

@ -5,13 +5,55 @@ from .generic_wrapper import *
# ## TEnum ############################################################################################################
class EnumValue:
"""
This is a helper class for adding documentation to an enum value.
"""
def __init__(self, value: int, doc: t.Optional[str] = None) -> None:
self.value = value
self.__doc__ = doc if doc else ""
def __int__(self) -> int:
return self.value
class EnumBase(enum.IntEnum):
"""
Base class for an Enum used in `construct_typed.TEnum`.
This class extends the standard `enum.IntEnum`, so that missing values are automatically generated.
This class extends the standard `enum.IntEnum` by.
- missing values are automatically generated
- possibility to add documentation for each enum value (see `EnumValue`)
Example::
>>> class State(EnumBase):
... Idle = 1
... Running = EnumValue(2, "This is the running state.")
>>> State(1)
<State.Idle: 1>
>>> State["Idle"]
<State.Idle: 1>
>>> State.Idle
<State.Idle: 1>
>>> State(3) # missing value
<State.3: 3>
>>> State.Running.__doc__ # documentation
'This is the running state.'
"""
def __init__(self, val: t.Union[EnumValue, int]):
if isinstance(val, EnumValue):
self.__doc__ = val.__doc__
else:
self.__doc__ = ""
# Extend the enum type with _missing_ method. So if a enum value
# not found in the enum, a new pseudo member is created.
# The idea is taken from: https://stackoverflow.com/a/57179436
@ -25,6 +67,7 @@ class EnumBase(enum.IntEnum):
# However, new_member._name_ = value works, too
new_member._name_ = str(value)
new_member._value_ = value
new_member.__doc__ = "missing value"
pseudo_member = cls._value2member_map_.setdefault(value, new_member)
return pseudo_member
return None # will raise the ValueError in Enum.__new__
@ -75,7 +118,51 @@ class TEnum(Adapter[int, int, EnumType, EnumType]):
# ## TFlagsEnum #######################################################################################################
class FlagsEnumBase(enum.IntFlag):
pass
"""
Base class for an Enum used in `construct_typed.TFlagsEnum`.
This class extends the standard `enum.IntFlag` by.
- possibility to add documentation for each enum value (see `EnumValue`)
Example::
>>> class Option(FlagsEnumBase):
... OptOne = 1
... OptTwo = EnumValue(2, "This is option two.")
>>> Option(1)
<Option.OptOne: 1>
>>> Option["OptOne"]
<Option.OptOne: 1>
>>> Option.OptOne
<Option.OptOne: 1>
>>> Option(3)
<Option.OptTwo|OptOne: 3>
>>> Option(4)
<Option.4: 4>
>>> Option.OptTwo.__doc__ # documentation
'This is option two.'
"""
def __init__(self, val: t.Union[EnumValue, int]):
if isinstance(val, EnumValue):
self.__doc__ = val.__doc__
else:
self.__doc__ = ""
@classmethod
def _missing_(cls, value: t.Any) -> t.Any:
"""
Returns member (possibly creating it) if one can be found for value.
"""
new_member = super()._missing_(value)
new_member.__doc__ = "missing value"
return new_member
FlagsEnumType = t.TypeVar("FlagsEnumType", bound=FlagsEnumBase)

View file

@ -384,6 +384,42 @@ def test_tenum_no_enumbase() -> None:
assert raises(lambda: cst.TEnum(cs.Byte, cls)) == TypeError
def test_tenum_docstring() -> None:
class TestEnum(cst.EnumBase):
"""
This is an test enum.
"""
Value_WithDoc = cst.EnumValue(0, doc="an enum with a documentation")
Value_WithMultilineDoc = cst.EnumValue(
1,
"""
An enum with a multiline documentation...
...next line...
""",
)
Value_NoDoc = cst.EnumValue(2)
Value_NoDoc2 = 3
assert (
TestEnum.__doc__
== """
This is an test enum.
"""
)
assert TestEnum.Value_WithDoc.__doc__ == "an enum with a documentation"
assert (
TestEnum.Value_WithMultilineDoc.__doc__
== """
An enum with a multiline documentation...
...next line...
"""
)
assert TestEnum.Value_NoDoc.__doc__ == ""
assert TestEnum.Value_NoDoc2.__doc__ == ""
assert TestEnum(5).__doc__ == "missing value"
def test_dataclass_struct_wrong_enumbase() -> None:
class E1(cst.EnumBase):
a = 1
@ -434,3 +470,39 @@ def test_tenum_flags() -> None:
assert d.build(TestEnum(255)) == b"\xff"
assert d.build(TestEnum.eight) == b"\x08"
assert raises(d.build, 2) == TypeError
def test_tenum_flags_docstring() -> None:
class TestEnum(cst.FlagsEnumBase):
"""
This is an test flags enum.
"""
Value_WithDoc = cst.EnumValue(0, doc="an enum with a documentation")
Value_WithMultilineDoc = cst.EnumValue(
1,
"""
An enum with a multiline documentation...
...next line...
""",
)
Value_NoDoc = cst.EnumValue(2)
Value_NoDoc2 = 4
assert (
TestEnum.__doc__
== """
This is an test flags enum.
"""
)
assert TestEnum.Value_WithDoc.__doc__ == "an enum with a documentation"
assert (
TestEnum.Value_WithMultilineDoc.__doc__
== """
An enum with a multiline documentation...
...next line...
"""
)
assert TestEnum.Value_NoDoc.__doc__ == ""
assert TestEnum.Value_NoDoc2.__doc__ == ""
assert TestEnum(8).__doc__ == "missing value"