| | 1083 | class TextTemplate(Template): |
| | 1084 | """Implementation of a simple text-based template engine. |
| | 1085 | |
| | 1086 | >>> tmpl = TextTemplate('''Dear $name, |
| | 1087 | ... |
| | 1088 | ... We have the following items for you: |
| | 1089 | ... #for item in items |
| | 1090 | ... * $item |
| | 1091 | ... #endfor |
| | 1092 | ... |
| | 1093 | ... All the best, |
| | 1094 | ... Foobar''') |
| | 1095 | >>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render('text') |
| | 1096 | Dear Joe, |
| | 1097 | <BLANKLINE> |
| | 1098 | We have the following items for you: |
| | 1099 | * 1 |
| | 1100 | * 2 |
| | 1101 | * 3 |
| | 1102 | <BLANKLINE> |
| | 1103 | All the best, |
| | 1104 | Foobar |
| | 1105 | |
| | 1106 | >>> tmpl = TextTemplate('''#def sayhi(name) |
| | 1107 | ... Hello, $name! |
| | 1108 | ... #enddef |
| | 1109 | ... ${sayhi('world')}''') |
| | 1110 | >>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render('text') |
| | 1111 | Hello, world! |
| | 1112 | <BLANKLINE> |
| | 1113 | """ |
| | 1114 | |
| | 1115 | directives = [('def', DefDirective), |
| | 1116 | ('comment', StripDirective), |
| | 1117 | ('when', WhenDirective), |
| | 1118 | ('otherwise', OtherwiseDirective), |
| | 1119 | ('for', ForDirective), |
| | 1120 | ('if', IfDirective), |
| | 1121 | ('choose', ChooseDirective), |
| | 1122 | ('with', WithDirective)] |
| | 1123 | _dir_by_name = dict(directives) |
| | 1124 | _dir_order = [directive[1] for directive in directives] |
| | 1125 | |
| | 1126 | _directive_re = re.compile('^\s*#(\w+.*)\n?', re.MULTILINE) |
| | 1127 | |
| | 1128 | def parse(self): |
| | 1129 | stream = [] # list of events of the "compiled" template |
| | 1130 | dirmap = {} # temporary mapping of directives to elements |
| | 1131 | depth = 0 |
| | 1132 | |
| | 1133 | source = self.source.read() |
| | 1134 | offset = 0 |
| | 1135 | lineno = 1 |
| | 1136 | for idx, mo in enumerate(self._directive_re.finditer(source)): |
| | 1137 | start, end = mo.span() |
| | 1138 | if start > offset: |
| | 1139 | text = source[offset:start] |
| | 1140 | for kind, data, pos in self._interpolate(text, self.filename, |
| | 1141 | lineno, 0): |
| | 1142 | stream.append((kind, data, pos)) |
| | 1143 | lineno += len(text.splitlines()) |
| | 1144 | |
| | 1145 | text = source[start:end].lstrip().lstrip('#') |
| | 1146 | lineno += len(text.splitlines()) |
| | 1147 | directive = text.split(None, 1) |
| | 1148 | if len(directive) > 1: |
| | 1149 | command, value = directive |
| | 1150 | else: |
| | 1151 | command, value = directive[0], None |
| | 1152 | |
| | 1153 | if not command.startswith('end'): |
| | 1154 | cls = self._dir_by_name.get(command) |
| | 1155 | if cls is None: |
| | 1156 | raise BadDirectiveError(command) |
| | 1157 | directive = cls(value, self.filename, lineno, 0) |
| | 1158 | dirmap[depth] = (directive, len(stream)) |
| | 1159 | depth += 1 |
| | 1160 | else: |
| | 1161 | depth -= 1 |
| | 1162 | command = command[3:] |
| | 1163 | if depth in dirmap: |
| | 1164 | directive, start_offset = dirmap.pop(depth) |
| | 1165 | substream = stream[start_offset:] |
| | 1166 | stream[start_offset:] = [(SUB, ([directive], substream), |
| | 1167 | (self.filename, lineno, 0))] |
| | 1168 | |
| | 1169 | offset = end |
| | 1170 | |
| | 1171 | if offset < len(source): |
| | 1172 | text = source[offset:] |
| | 1173 | for kind, data, pos in self._interpolate(text, self.filename, |
| | 1174 | lineno, 0): |
| | 1175 | stream.append((kind, data, pos)) |
| | 1176 | |
| | 1177 | return stream |
| | 1178 | |
| | 1179 | def _match(self, stream, ctxt=None): |
| | 1180 | return stream |
| | 1181 | |
| | 1182 | |