| | 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]) |
| |
|