Coverage for debputy/substitution.py: 76%
56 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-22 14:29 +0100
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-22 14:29 +0100
1import os
2import re
4from debputy.architecture_support import dpkg_architecture_table
5from debputy.util import _error, glob_escape
7_VAR_RE = re.compile(r'[$][{]([A-Za-z0-9][-_:0-9A-Za-z]*)[}]')
10class Substitution:
11 def substitute(self, value: str, definition_source: str, /, escape_glob_characters: bool = False) -> str:
12 raise NotImplementedError
14 def with_extra_substitutions(self, **extra_substitutions) -> 'Substitution':
15 raise NotImplementedError
18class NullSubstitution(Substitution):
19 def substitute(self, value: str, definition_source: str, /, escape_glob_characters: bool = False) -> str:
20 return value
22 def with_extra_substitutions(self, **extra_substitutions) -> 'Substitution':
23 return self
26NULL_SUBSTITUTION = NullSubstitution()
27del NullSubstitution
30class SubstitutionImpl(Substitution):
32 def __init__(self,
33 /,
34 extra_substitutions=None,
35 dpkg_arch_table=None,
36 environment=None,
37 ) -> None:
38 self._extra_substitutions = extra_substitutions if extra_substitutions is not None else {}
39 self._dpkg_arch_table = dpkg_arch_table if dpkg_arch_table is not None else dpkg_architecture_table()
40 self._env = environment if environment is not None else os.environ
41 self._builtin_substs = {
42 'Space': ' ',
43 'Dollar': '$',
44 'Newline': "\n",
45 'Tab': "\t",
46 }
48 def _replacement(self, key: str, definition_source: str) -> str:
49 if key in self._builtin_substs:
50 return self._builtin_substs[key]
51 if key in self._dpkg_arch_table:
52 return self._dpkg_arch_table[key]
53 if key.startswith('env:'): 53 ↛ 59line 53 didn't jump to line 59, because the condition on line 53 was never false
54 k = key[4:]
55 if k in self._env: 55 ↛ 57line 55 didn't jump to line 57, because the condition on line 55 was never false
56 return self._env[k]
57 _error(f'The environment does not contain the variable "{key}" '
58 f'(error occurred while trying to process {definition_source})')
59 if key in self._extra_substitutions:
60 return self._extra_substitutions[key]
61 _error("Cannot resolve ${" + key + "}."
62 f' The error occurred while trying to process {definition_source}')
64 def substitute(self, value: str, definition_source: str, /, escape_glob_characters: bool = False) -> str:
65 if '${' not in value:
66 return value
67 replacement = value
68 offset = 0
69 for match in _VAR_RE.finditer(value):
70 matched_key = match.group(1)
71 replacement_value = self._replacement(matched_key, definition_source)
72 if escape_glob_characters: 72 ↛ 73line 72 didn't jump to line 73, because the condition on line 72 was never true
73 replacement_value = glob_escape(replacement_value)
74 s, e = match.span()
75 s += offset
76 e += offset
77 replacement = replacement[:s] + replacement_value + replacement[e:]
78 offset += len(replacement_value) - len(matched_key) - 3 # the 3 cover the ${} part.
80 return replacement.replace('${}', '$')
82 def with_extra_substitutions(self, **extra_substitutions) -> 'Substitution':
83 if not extra_substitutions:
84 return self
85 combined_extras = self._extra_substitutions.copy()
86 combined_extras.update(extra_substitutions)
87 return SubstitutionImpl(dpkg_arch_table=self._dpkg_arch_table,
88 environment=self._env,
89 extra_substitutions=combined_extras,
90 )