10
10
11
11
# Coincidentally named the same as http://code.activestate.com/recipes/496702/
12
12
13
+ from __future__ import annotations
14
+
13
15
import re
14
16
17
+ from typing import (
18
+ Any , Callable , Dict , List , NoReturn , Optional , Set , Union , cast ,
19
+ )
20
+
15
21
16
22
class TempliteSyntaxError (ValueError ):
17
23
"""Raised when a template has a syntax error."""
@@ -26,45 +32,45 @@ class TempliteValueError(ValueError):
26
32
class CodeBuilder :
27
33
"""Build source code conveniently."""
28
34
29
- def __init__ (self , indent = 0 ) :
30
- self .code = []
35
+ def __init__ (self , indent : int = 0 ) -> None :
36
+ self .code : List [ Union [ str , CodeBuilder ]] = []
31
37
self .indent_level = indent
32
38
33
- def __str__ (self ):
39
+ def __str__ (self ) -> str :
34
40
return "" .join (str (c ) for c in self .code )
35
41
36
- def add_line (self , line ) :
42
+ def add_line (self , line : str ) -> None :
37
43
"""Add a line of source to the code.
38
44
39
45
Indentation and newline will be added for you, don't provide them.
40
46
41
47
"""
42
48
self .code .extend ([" " * self .indent_level , line , "\n " ])
43
49
44
- def add_section (self ):
50
+ def add_section (self ) -> CodeBuilder :
45
51
"""Add a section, a sub-CodeBuilder."""
46
52
section = CodeBuilder (self .indent_level )
47
53
self .code .append (section )
48
54
return section
49
55
50
56
INDENT_STEP = 4 # PEP8 says so!
51
57
52
- def indent (self ):
58
+ def indent (self ) -> None :
53
59
"""Increase the current indent for following lines."""
54
60
self .indent_level += self .INDENT_STEP
55
61
56
- def dedent (self ):
62
+ def dedent (self ) -> None :
57
63
"""Decrease the current indent for following lines."""
58
64
self .indent_level -= self .INDENT_STEP
59
65
60
- def get_globals (self ):
66
+ def get_globals (self ) -> Dict [ str , Any ] :
61
67
"""Execute the code, and return a dict of globals it defines."""
62
68
# A check that the caller really finished all the blocks they started.
63
69
assert self .indent_level == 0
64
70
# Get the Python source as a single string.
65
71
python_source = str (self )
66
72
# Execute the source, defining globals, and return them.
67
- global_namespace = {}
73
+ global_namespace : Dict [ str , Any ] = {}
68
74
exec (python_source , global_namespace )
69
75
return global_namespace
70
76
@@ -111,7 +117,7 @@ class Templite:
111
117
})
112
118
113
119
"""
114
- def __init__ (self , text , * contexts ) :
120
+ def __init__ (self , text : str , * contexts : Dict [ str , Any ]) -> None :
115
121
"""Construct a Templite with the given `text`.
116
122
117
123
`contexts` are dictionaries of values to use for future renderings.
@@ -122,8 +128,8 @@ def __init__(self, text, *contexts):
122
128
for context in contexts :
123
129
self .context .update (context )
124
130
125
- self .all_vars = set ()
126
- self .loop_vars = set ()
131
+ self .all_vars : Set [ str ] = set ()
132
+ self .loop_vars : Set [ str ] = set ()
127
133
128
134
# We construct a function in source form, then compile it and hold onto
129
135
# it, and execute it to render the template.
@@ -137,9 +143,9 @@ def __init__(self, text, *contexts):
137
143
code .add_line ("extend_result = result.extend" )
138
144
code .add_line ("to_str = str" )
139
145
140
- buffered = []
146
+ buffered : List [ str ] = []
141
147
142
- def flush_output ():
148
+ def flush_output () -> None :
143
149
"""Force `buffered` to the code builder."""
144
150
if len (buffered ) == 1 :
145
151
code .add_line ("append_result(%s)" % buffered [0 ])
@@ -232,9 +238,15 @@ def flush_output():
232
238
233
239
code .add_line ('return "".join(result)' )
234
240
code .dedent ()
235
- self ._render_function = code .get_globals ()['render_function' ]
241
+ self ._render_function = cast (
242
+ Callable [
243
+ [Dict [str , Any ], Callable [..., Any ]],
244
+ str
245
+ ],
246
+ code .get_globals ()['render_function' ],
247
+ )
236
248
237
- def _expr_code (self , expr ) :
249
+ def _expr_code (self , expr : str ) -> str :
238
250
"""Generate a Python expression for `expr`."""
239
251
if "|" in expr :
240
252
pipes = expr .split ("|" )
@@ -252,11 +264,11 @@ def _expr_code(self, expr):
252
264
code = "c_%s" % expr
253
265
return code
254
266
255
- def _syntax_error (self , msg , thing ) :
267
+ def _syntax_error (self , msg : str , thing : Any ) -> NoReturn :
256
268
"""Raise a syntax error using `msg`, and showing `thing`."""
257
269
raise TempliteSyntaxError (f"{ msg } : { thing !r} " )
258
270
259
- def _variable (self , name , vars_set ) :
271
+ def _variable (self , name : str , vars_set : Set [ str ]) -> None :
260
272
"""Track that `name` is used as a variable.
261
273
262
274
Adds the name to `vars_set`, a set of variable names.
@@ -268,7 +280,7 @@ def _variable(self, name, vars_set):
268
280
self ._syntax_error ("Not a valid name" , name )
269
281
vars_set .add (name )
270
282
271
- def render (self , context = None ):
283
+ def render (self , context : Optional [ Dict [ str , Any ]] = None ) -> str :
272
284
"""Render this template by applying it to `context`.
273
285
274
286
`context` is a dictionary of values to use in this rendering.
@@ -280,7 +292,7 @@ def render(self, context=None):
280
292
render_context .update (context )
281
293
return self ._render_function (render_context , self ._do_dots )
282
294
283
- def _do_dots (self , value , * dots ) :
295
+ def _do_dots (self , value : Any , * dots : str ) -> Any :
284
296
"""Evaluate dotted expressions at run-time."""
285
297
for dot in dots :
286
298
try :
0 commit comments