1 import re 2 from abc import ABCMeta, abstractmethod 3 from typing import Iterator, Mapping, Optional, Pattern 4 5 _posix_variable: Pattern[str] = re.compile( 6 r""" 7 \$\{ 8 (?P<name>[^\}:]*) 9 (?::- 10 (?P<default>[^\}]*) 11 )? 12 \} 13 """, 14 re.VERBOSE, 15 ) 16 17 18 class Atom(metaclass=ABCMeta): 19 def __ne__(self, other: object) -> bool: 20 result = self.__eq__(other) 21 if result is NotImplemented: 22 return NotImplemented 23 return not result 24 25 @abstractmethod 26 def resolve(self, env: Mapping[str, Optional[str]]) -> str: ... 27 28 29 class Literal(Atom): 30 def __init__(self, value: str) -> None: 31 self.value = value 32 33 def __repr__(self) -> str: 34 return f"Literal(value={self.value})" 35 36 def __eq__(self, other: object) -> bool: 37 if not isinstance(other, self.__class__): 38 return NotImplemented 39 return self.value == other.value 40 41 def __hash__(self) -> int: 42 return hash((self.__class__, self.value)) 43 44 def resolve(self, env: Mapping[str, Optional[str]]) -> str: 45 return self.value 46 47 48 class Variable(Atom): 49 def __init__(self, name: str, default: Optional[str]) -> None: 50 self.name = name 51 self.default = default 52 53 def __repr__(self) -> str: 54 return f"Variable(name={self.name}, default={self.default})" 55 56 def __eq__(self, other: object) -> bool: 57 if not isinstance(other, self.__class__): 58 return NotImplemented 59 return (self.name, self.default) == (other.name, other.default) 60 61 def __hash__(self) -> int: 62 return hash((self.__class__, self.name, self.default)) 63 64 def resolve(self, env: Mapping[str, Optional[str]]) -> str: 65 default = self.default if self.default is not None else "" 66 result = env.get(self.name, default) 67 return result if result is not None else "" 68 69 70 def parse_variables(value: str) -> Iterator[Atom]: 71 cursor = 0 72 73 for match in _posix_variable.finditer(value): 74 (start, end) = match.span() 75 name = match["name"] 76 default = match["default"] 77 78 if start > cursor: 79 yield Literal(value=value[cursor:start]) 80 81 yield Variable(name=name, default=default) 82 cursor = end 83 84 length = len(value) 85 if cursor < length: 86 yield Literal(value=value[cursor:length]) 87