19 raise SyntaxError(_('unmatched quotes')) |
19 raise SyntaxError(_('unmatched quotes')) |
20 return s[1:-1].decode('string_escape') |
20 return s[1:-1].decode('string_escape') |
21 |
21 |
22 return s.decode('string_escape') |
22 return s.decode('string_escape') |
23 |
23 |
24 class templater(object): |
24 class engine(object): |
25 '''template expansion engine. |
25 '''template expansion engine. |
26 |
26 |
27 template expansion works like this. a map file contains key=value |
27 template expansion works like this. a map file contains key=value |
28 pairs. if value is quoted, it is treated as string. otherwise, it |
28 pairs. if value is quoted, it is treated as string. otherwise, it |
29 is treated as name of template file. |
29 is treated as name of template file. |
41 filter uses function to transform value. syntax is |
41 filter uses function to transform value. syntax is |
42 {key|filter1|filter2|...}.''' |
42 {key|filter1|filter2|...}.''' |
43 |
43 |
44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))" |
44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))" |
45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]") |
45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]") |
|
46 |
|
47 def __init__(self, loader, filters={}, defaults={}): |
|
48 self.loader = loader |
|
49 self.filters = filters |
|
50 self.defaults = defaults |
|
51 |
|
52 def process(self, t, map): |
|
53 '''Perform expansion. t is name of map element to expand. map contains |
|
54 added elements for use during expansion. Is a generator.''' |
|
55 tmpl = self.loader(t) |
|
56 iters = [self._process(tmpl, map)] |
|
57 while iters: |
|
58 try: |
|
59 item = iters[0].next() |
|
60 except StopIteration: |
|
61 iters.pop(0) |
|
62 continue |
|
63 if isinstance(item, str): |
|
64 yield item |
|
65 elif item is None: |
|
66 yield '' |
|
67 elif hasattr(item, '__iter__'): |
|
68 iters.insert(0, iter(item)) |
|
69 else: |
|
70 yield str(item) |
|
71 |
|
72 def _process(self, tmpl, map): |
|
73 '''Render a template. Returns a generator.''' |
|
74 while tmpl: |
|
75 m = self.template_re.search(tmpl) |
|
76 if not m: |
|
77 yield tmpl |
|
78 break |
|
79 |
|
80 start, end = m.span(0) |
|
81 key, format, fl = m.groups() |
|
82 |
|
83 if start: |
|
84 yield tmpl[:start] |
|
85 tmpl = tmpl[end:] |
|
86 |
|
87 if key in map: |
|
88 v = map[key] |
|
89 else: |
|
90 v = self.defaults.get(key, "") |
|
91 if callable(v): |
|
92 v = v(**map) |
|
93 if format: |
|
94 if not hasattr(v, '__iter__'): |
|
95 raise SyntaxError(_("Error expanding '%s%%%s'") |
|
96 % (key, format)) |
|
97 lm = map.copy() |
|
98 for i in v: |
|
99 lm.update(i) |
|
100 yield self.process(format, lm) |
|
101 else: |
|
102 if fl: |
|
103 for f in fl.split("|")[1:]: |
|
104 v = self.filters[f](v) |
|
105 yield v |
|
106 |
|
107 class templater(object): |
46 |
108 |
47 def __init__(self, mapfile, filters={}, defaults={}, cache={}, |
109 def __init__(self, mapfile, filters={}, defaults={}, cache={}, |
48 minchunk=1024, maxchunk=65536): |
110 minchunk=1024, maxchunk=65536): |
49 '''set up template engine. |
111 '''set up template engine. |
50 mapfile is name of file to read map definitions from. |
112 mapfile is name of file to read map definitions from. |
77 self.map[key] = os.path.join(self.base, val) |
139 self.map[key] = os.path.join(self.base, val) |
78 |
140 |
79 def __contains__(self, key): |
141 def __contains__(self, key): |
80 return key in self.cache or key in self.map |
142 return key in self.cache or key in self.map |
81 |
143 |
82 def _template(self, t): |
144 def load(self, t): |
83 '''Get the template for the given template name. Use a local cache.''' |
145 '''Get the template for the given template name. Use a local cache.''' |
84 if not t in self.cache: |
146 if not t in self.cache: |
85 try: |
147 try: |
86 self.cache[t] = file(self.map[t]).read() |
148 self.cache[t] = file(self.map[t]).read() |
87 except IOError, inst: |
149 except IOError, inst: |
88 raise IOError(inst.args[0], _('template file %s: %s') % |
150 raise IOError(inst.args[0], _('template file %s: %s') % |
89 (self.map[t], inst.args[1])) |
151 (self.map[t], inst.args[1])) |
90 return self.cache[t] |
152 return self.cache[t] |
91 |
153 |
92 def _process(self, tmpl, map): |
|
93 '''Render a template. Returns a generator.''' |
|
94 while tmpl: |
|
95 m = self.template_re.search(tmpl) |
|
96 if not m: |
|
97 yield tmpl |
|
98 break |
|
99 |
|
100 start, end = m.span(0) |
|
101 key, format, fl = m.groups() |
|
102 |
|
103 if start: |
|
104 yield tmpl[:start] |
|
105 tmpl = tmpl[end:] |
|
106 |
|
107 if key in map: |
|
108 v = map[key] |
|
109 else: |
|
110 v = self.defaults.get(key, "") |
|
111 if callable(v): |
|
112 v = v(**map) |
|
113 if format: |
|
114 if not hasattr(v, '__iter__'): |
|
115 raise SyntaxError(_("Error expanding '%s%%%s'") |
|
116 % (key, format)) |
|
117 lm = map.copy() |
|
118 for i in v: |
|
119 lm.update(i) |
|
120 t = self._template(format) |
|
121 yield self._process(t, lm) |
|
122 else: |
|
123 if fl: |
|
124 for f in fl.split("|")[1:]: |
|
125 v = self.filters[f](v) |
|
126 yield v |
|
127 |
|
128 def __call__(self, t, **map): |
154 def __call__(self, t, **map): |
129 stream = self.expand(t, **map) |
155 proc = engine(self.load, self.filters, self.defaults) |
|
156 stream = proc.process(t, map) |
130 if self.minchunk: |
157 if self.minchunk: |
131 stream = util.increasingchunks(stream, min=self.minchunk, |
158 stream = util.increasingchunks(stream, min=self.minchunk, |
132 max=self.maxchunk) |
159 max=self.maxchunk) |
133 return stream |
160 return stream |
134 |
|
135 def expand(self, t, **map): |
|
136 '''Perform expansion. t is name of map element to expand. map contains |
|
137 added elements for use during expansion. Is a generator.''' |
|
138 tmpl = self._template(t) |
|
139 iters = [self._process(tmpl, map)] |
|
140 while iters: |
|
141 try: |
|
142 item = iters[0].next() |
|
143 except StopIteration: |
|
144 iters.pop(0) |
|
145 continue |
|
146 if isinstance(item, str): |
|
147 yield item |
|
148 elif item is None: |
|
149 yield '' |
|
150 elif hasattr(item, '__iter__'): |
|
151 iters.insert(0, iter(item)) |
|
152 else: |
|
153 yield str(item) |
|
154 |
161 |
155 def templatepath(name=None): |
162 def templatepath(name=None): |
156 '''return location of template file or directory (if no name). |
163 '''return location of template file or directory (if no name). |
157 returns None if not found.''' |
164 returns None if not found.''' |
158 normpaths = [] |
165 normpaths = [] |