Coverage for debputy/substitution.py: 76%

56 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-22 14:29 +0100

1import os 

2import re 

3 

4from debputy.architecture_support import dpkg_architecture_table 

5from debputy.util import _error, glob_escape 

6 

7_VAR_RE = re.compile(r'[$][{]([A-Za-z0-9][-_:0-9A-Za-z]*)[}]') 

8 

9 

10class Substitution: 

11 def substitute(self, value: str, definition_source: str, /, escape_glob_characters: bool = False) -> str: 

12 raise NotImplementedError 

13 

14 def with_extra_substitutions(self, **extra_substitutions) -> 'Substitution': 

15 raise NotImplementedError 

16 

17 

18class NullSubstitution(Substitution): 

19 def substitute(self, value: str, definition_source: str, /, escape_glob_characters: bool = False) -> str: 

20 return value 

21 

22 def with_extra_substitutions(self, **extra_substitutions) -> 'Substitution': 

23 return self 

24 

25 

26NULL_SUBSTITUTION = NullSubstitution() 

27del NullSubstitution 

28 

29 

30class SubstitutionImpl(Substitution): 

31 

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 } 

47 

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}') 

63 

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. 

79 

80 return replacement.replace('${}', '$') 

81 

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 )