A string class for supporting $-substitutions.
| 62 | |
| 63 | |
| 64 | class Template: |
| 65 | """A string class for supporting $-substitutions.""" |
| 66 | |
| 67 | delimiter = '$' |
| 68 | # r'[a-z]' matches to non-ASCII letters when used with IGNORECASE, but |
| 69 | # without the ASCII flag. We can't add re.ASCII to flags because of |
| 70 | # backward compatibility. So we use the ?a local flag and [a-z] pattern. |
| 71 | # See https://bugs.python.org/issue31672 |
| 72 | idpattern = r'(?a:[_a-z][_a-z0-9]*)' |
| 73 | braceidpattern = None |
| 74 | flags = None # default: re.IGNORECASE |
| 75 | |
| 76 | pattern = _TemplatePattern # use a descriptor to compile the pattern |
| 77 | |
| 78 | def __init_subclass__(cls): |
| 79 | super().__init_subclass__() |
| 80 | cls._compile_pattern() |
| 81 | |
| 82 | @classmethod |
| 83 | def _compile_pattern(cls): |
| 84 | import re # deferred import, for performance |
| 85 | |
| 86 | pattern = cls.__dict__.get('pattern', _TemplatePattern) |
| 87 | if pattern is _TemplatePattern: |
| 88 | delim = re.escape(cls.delimiter) |
| 89 | id = cls.idpattern |
| 90 | bid = cls.braceidpattern or cls.idpattern |
| 91 | pattern = fr""" |
| 92 | {delim}(?: |
| 93 | (?P<escaped>{delim}) | # Escape sequence of two delimiters |
| 94 | (?P<named>{id}) | # delimiter and a Python identifier |
| 95 | {{(?P<braced>{bid})}} | # delimiter and a braced identifier |
| 96 | (?P<invalid>) # Other ill-formed delimiter exprs |
| 97 | ) |
| 98 | """ |
| 99 | if cls.flags is None: |
| 100 | cls.flags = re.IGNORECASE |
| 101 | pat = cls.pattern = re.compile(pattern, cls.flags | re.VERBOSE) |
| 102 | return pat |
| 103 | |
| 104 | def __init__(self, template): |
| 105 | self.template = template |
| 106 | |
| 107 | # Search for $$, $identifier, ${identifier}, and any bare $'s |
| 108 | |
| 109 | def _invalid(self, mo): |
| 110 | i = mo.start('invalid') |
| 111 | lines = self.template[:i].splitlines(keepends=True) |
| 112 | if not lines: |
| 113 | colno = 1 |
| 114 | lineno = 1 |
| 115 | else: |
| 116 | colno = i - len(''.join(lines[:-1])) |
| 117 | lineno = len(lines) |
| 118 | raise ValueError('Invalid placeholder in string: line %d, col %d' % |
| 119 | (lineno, colno)) |
| 120 | |
| 121 | def substitute(self, mapping=_sentinel_dict, /, **kws): |
no outgoing calls
searching dependent graphs…