Source code for cosapp.utils.naming
import re
from enum import Enum
from typing import Any, List, Tuple, Set
from cosapp.utils.helpers import check_arg
[docs]
class CommonPorts(Enum):
"""Port names common to every system.
INWARDS : For orphan input variables
OUTWARDS : For orphan output variables
MODEVARS_IN : For orphan input mode variables
MODEVARS_OUT : For orphan output mode variables
"""
INWARDS = "inwards"
OUTWARDS = "outwards"
MODEVARS_IN = "modevars_in"
MODEVARS_OUT = "modevars_out"
[docs]
@classmethod
def names(cls) -> Set[str]:
"""Returns common port names as a set."""
return set(case.value for case in cls)
[docs]
def has_time(expression: Any) -> bool:
"""Checks if an expression contains 't'"""
return re.search(r"\bt\b", str(expression)) is not None
[docs]
def natural_varname(name: str) -> str:
"""Strip references to common port names from variable name
"""
pattern = "|".join(CommonPorts.names())
return re.sub(f"({pattern})\.", "", name.strip())
[docs]
class NameChecker:
"""Class handling admissible names, through regular expression filtering"""
def __init__(self,
pattern = r"^[A-Za-z][\w]*$",
message = "Name must start with a letter, and contain only alphanumerics and '_'",
excluded: List[str] = [],
):
self.__error_message = lambda name: None # type: Callable[[str], str]
self.__message = "" # type: str
self.__pattern = None # type: re.Pattern
self.__excluded = tuple()
self.pattern = pattern
self.message = message
self.excluded = excluded
[docs]
@classmethod
def reserved(cls) -> List[str]:
"""List of reserved names"""
return ["t", "time"]
@property
def pattern(self) -> str:
return self.__pattern.pattern
@pattern.setter
def pattern(self, pattern: str) -> None:
self.__pattern = re.compile(pattern)
@property
def excluded(self) -> Tuple[str]:
return self.__excluded
@excluded.setter
def excluded(self, excluded) -> None:
excluded = excluded or []
if isinstance(excluded, str):
excluded = [excluded]
else:
check_arg(excluded, 'excluded', (list, tuple, set),
value_ok = lambda col: all(isinstance(s, str) for s in col)
)
self.__excluded = tuple(excluded)
@property
def message(self) -> str:
"""Returns a human-readable message, transcripted from regexp rule"""
return self.__message
@message.setter
def message(self, message: str) -> None:
check_arg(message, "message", str)
self.__message = message
if len(self.__message) > 0:
self.__error_message = lambda name: f"{self.__message}; got {name!r}."
else:
self.__error_message = lambda name: f"Invalid name {name!r}"
[docs]
def is_valid(self, name: str) -> bool:
"""Method indicating whether or not a name is valid, under the current rule"""
try:
self(name)
except:
return False
else:
return True
def __call__(self, name) -> str:
"""Returns `name` if valid; otherwise, raises an exception"""
check_arg(name, "name", str)
message = None
reserved = self.reserved()
if name in reserved:
message = f"Names {reserved} are reserved"
elif name in self.excluded:
message = f"Names {self.excluded} are invalid"
elif self.__pattern.match(name) is None:
message = self.__error_message(name)
if message is not None:
raise ValueError(message)
return name