| import re |
| from abc import ABCMeta, abstractmethod |
| from typing import Iterator, Mapping, Optional, Pattern |
|
|
| _posix_variable: Pattern[str] = re.compile( |
| r""" |
| \$\{ |
| (?P<name>[^\}:]*) |
| (?::- |
| (?P<default>[^\}]*) |
| )? |
| \} |
| """, |
| re.VERBOSE, |
| ) |
|
|
|
|
| class Atom(metaclass=ABCMeta): |
| def __ne__(self, other: object) -> bool: |
| result = self.__eq__(other) |
| if result is NotImplemented: |
| return NotImplemented |
| return not result |
|
|
| @abstractmethod |
| def resolve(self, env: Mapping[str, Optional[str]]) -> str: ... |
|
|
|
|
| class Literal(Atom): |
| def __init__(self, value: str) -> None: |
| self.value = value |
|
|
| def __repr__(self) -> str: |
| return f"Literal(value={self.value})" |
|
|
| def __eq__(self, other: object) -> bool: |
| if not isinstance(other, self.__class__): |
| return NotImplemented |
| return self.value == other.value |
|
|
| def __hash__(self) -> int: |
| return hash((self.__class__, self.value)) |
|
|
| def resolve(self, env: Mapping[str, Optional[str]]) -> str: |
| return self.value |
|
|
|
|
| class Variable(Atom): |
| def __init__(self, name: str, default: Optional[str]) -> None: |
| self.name = name |
| self.default = default |
|
|
| def __repr__(self) -> str: |
| return f"Variable(name={self.name}, default={self.default})" |
|
|
| def __eq__(self, other: object) -> bool: |
| if not isinstance(other, self.__class__): |
| return NotImplemented |
| return (self.name, self.default) == (other.name, other.default) |
|
|
| def __hash__(self) -> int: |
| return hash((self.__class__, self.name, self.default)) |
|
|
| def resolve(self, env: Mapping[str, Optional[str]]) -> str: |
| default = self.default if self.default is not None else "" |
| result = env.get(self.name, default) |
| return result if result is not None else "" |
|
|
|
|
| def parse_variables(value: str) -> Iterator[Atom]: |
| cursor = 0 |
|
|
| for match in _posix_variable.finditer(value): |
| (start, end) = match.span() |
| name = match["name"] |
| default = match["default"] |
|
|
| if start > cursor: |
| yield Literal(value=value[cursor:start]) |
|
|
| yield Variable(name=name, default=default) |
| cursor = end |
|
|
| length = len(value) |
| if cursor < length: |
| yield Literal(value=value[cursor:length]) |
|
|