11 |
11 |
12 from .i18n import _ |
12 from .i18n import _ |
13 from . import ( |
13 from . import ( |
14 error, |
14 error, |
15 pycompat, |
15 pycompat, |
16 templatekw, |
|
17 util, |
16 util, |
18 ) |
17 ) |
19 |
18 |
20 class ResourceUnavailable(error.Abort): |
19 class ResourceUnavailable(error.Abort): |
21 pass |
20 pass |
22 |
21 |
23 class TemplateNotFound(error.Abort): |
22 class TemplateNotFound(error.Abort): |
24 pass |
23 pass |
25 |
24 |
|
25 class hybrid(object): |
|
26 """Wrapper for list or dict to support legacy template |
|
27 |
|
28 This class allows us to handle both: |
|
29 - "{files}" (legacy command-line-specific list hack) and |
|
30 - "{files % '{file}\n'}" (hgweb-style with inlining and function support) |
|
31 and to access raw values: |
|
32 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}" |
|
33 - "{get(extras, key)}" |
|
34 - "{files|json}" |
|
35 """ |
|
36 |
|
37 def __init__(self, gen, values, makemap, joinfmt, keytype=None): |
|
38 if gen is not None: |
|
39 self.gen = gen # generator or function returning generator |
|
40 self._values = values |
|
41 self._makemap = makemap |
|
42 self.joinfmt = joinfmt |
|
43 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved |
|
44 def gen(self): |
|
45 """Default generator to stringify this as {join(self, ' ')}""" |
|
46 for i, x in enumerate(self._values): |
|
47 if i > 0: |
|
48 yield ' ' |
|
49 yield self.joinfmt(x) |
|
50 def itermaps(self): |
|
51 makemap = self._makemap |
|
52 for x in self._values: |
|
53 yield makemap(x) |
|
54 def __contains__(self, x): |
|
55 return x in self._values |
|
56 def __getitem__(self, key): |
|
57 return self._values[key] |
|
58 def __len__(self): |
|
59 return len(self._values) |
|
60 def __iter__(self): |
|
61 return iter(self._values) |
|
62 def __getattr__(self, name): |
|
63 if name not in (r'get', r'items', r'iteritems', r'iterkeys', |
|
64 r'itervalues', r'keys', r'values'): |
|
65 raise AttributeError(name) |
|
66 return getattr(self._values, name) |
|
67 |
|
68 class mappable(object): |
|
69 """Wrapper for non-list/dict object to support map operation |
|
70 |
|
71 This class allows us to handle both: |
|
72 - "{manifest}" |
|
73 - "{manifest % '{rev}:{node}'}" |
|
74 - "{manifest.rev}" |
|
75 |
|
76 Unlike a hybrid, this does not simulate the behavior of the underling |
|
77 value. Use unwrapvalue() or unwraphybrid() to obtain the inner object. |
|
78 """ |
|
79 |
|
80 def __init__(self, gen, key, value, makemap): |
|
81 if gen is not None: |
|
82 self.gen = gen # generator or function returning generator |
|
83 self._key = key |
|
84 self._value = value # may be generator of strings |
|
85 self._makemap = makemap |
|
86 |
|
87 def gen(self): |
|
88 yield pycompat.bytestr(self._value) |
|
89 |
|
90 def tomap(self): |
|
91 return self._makemap(self._key) |
|
92 |
|
93 def itermaps(self): |
|
94 yield self.tomap() |
|
95 |
|
96 def hybriddict(data, key='key', value='value', fmt=None, gen=None): |
|
97 """Wrap data to support both dict-like and string-like operations""" |
|
98 prefmt = pycompat.identity |
|
99 if fmt is None: |
|
100 fmt = '%s=%s' |
|
101 prefmt = pycompat.bytestr |
|
102 return hybrid(gen, data, lambda k: {key: k, value: data[k]}, |
|
103 lambda k: fmt % (prefmt(k), prefmt(data[k]))) |
|
104 |
|
105 def hybridlist(data, name, fmt=None, gen=None): |
|
106 """Wrap data to support both list-like and string-like operations""" |
|
107 prefmt = pycompat.identity |
|
108 if fmt is None: |
|
109 fmt = '%s' |
|
110 prefmt = pycompat.bytestr |
|
111 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x)) |
|
112 |
|
113 def unwraphybrid(thing): |
|
114 """Return an object which can be stringified possibly by using a legacy |
|
115 template""" |
|
116 gen = getattr(thing, 'gen', None) |
|
117 if gen is None: |
|
118 return thing |
|
119 if callable(gen): |
|
120 return gen() |
|
121 return gen |
|
122 |
|
123 def unwrapvalue(thing): |
|
124 """Move the inner value object out of the wrapper""" |
|
125 if not util.safehasattr(thing, '_value'): |
|
126 return thing |
|
127 return thing._value |
|
128 |
|
129 def wraphybridvalue(container, key, value): |
|
130 """Wrap an element of hybrid container to be mappable |
|
131 |
|
132 The key is passed to the makemap function of the given container, which |
|
133 should be an item generated by iter(container). |
|
134 """ |
|
135 makemap = getattr(container, '_makemap', None) |
|
136 if makemap is None: |
|
137 return value |
|
138 if util.safehasattr(value, '_makemap'): |
|
139 # a nested hybrid list/dict, which has its own way of map operation |
|
140 return value |
|
141 return mappable(None, key, value, makemap) |
|
142 |
|
143 def compatdict(context, mapping, name, data, key='key', value='value', |
|
144 fmt=None, plural=None, separator=' '): |
|
145 """Wrap data like hybriddict(), but also supports old-style list template |
|
146 |
|
147 This exists for backward compatibility with the old-style template. Use |
|
148 hybriddict() for new template keywords. |
|
149 """ |
|
150 c = [{key: k, value: v} for k, v in data.iteritems()] |
|
151 t = context.resource(mapping, 'templ') |
|
152 f = _showlist(name, c, t, mapping, plural, separator) |
|
153 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f) |
|
154 |
|
155 def compatlist(context, mapping, name, data, element=None, fmt=None, |
|
156 plural=None, separator=' '): |
|
157 """Wrap data like hybridlist(), but also supports old-style list template |
|
158 |
|
159 This exists for backward compatibility with the old-style template. Use |
|
160 hybridlist() for new template keywords. |
|
161 """ |
|
162 t = context.resource(mapping, 'templ') |
|
163 f = _showlist(name, data, t, mapping, plural, separator) |
|
164 return hybridlist(data, name=element or name, fmt=fmt, gen=f) |
|
165 |
|
166 def _showlist(name, values, templ, mapping, plural=None, separator=' '): |
|
167 '''expand set of values. |
|
168 name is name of key in template map. |
|
169 values is list of strings or dicts. |
|
170 plural is plural of name, if not simply name + 's'. |
|
171 separator is used to join values as a string |
|
172 |
|
173 expansion works like this, given name 'foo'. |
|
174 |
|
175 if values is empty, expand 'no_foos'. |
|
176 |
|
177 if 'foo' not in template map, return values as a string, |
|
178 joined by 'separator'. |
|
179 |
|
180 expand 'start_foos'. |
|
181 |
|
182 for each value, expand 'foo'. if 'last_foo' in template |
|
183 map, expand it instead of 'foo' for last key. |
|
184 |
|
185 expand 'end_foos'. |
|
186 ''' |
|
187 strmapping = pycompat.strkwargs(mapping) |
|
188 if not plural: |
|
189 plural = name + 's' |
|
190 if not values: |
|
191 noname = 'no_' + plural |
|
192 if noname in templ: |
|
193 yield templ(noname, **strmapping) |
|
194 return |
|
195 if name not in templ: |
|
196 if isinstance(values[0], bytes): |
|
197 yield separator.join(values) |
|
198 else: |
|
199 for v in values: |
|
200 r = dict(v) |
|
201 r.update(mapping) |
|
202 yield r |
|
203 return |
|
204 startname = 'start_' + plural |
|
205 if startname in templ: |
|
206 yield templ(startname, **strmapping) |
|
207 vmapping = mapping.copy() |
|
208 def one(v, tag=name): |
|
209 try: |
|
210 vmapping.update(v) |
|
211 # Python 2 raises ValueError if the type of v is wrong. Python |
|
212 # 3 raises TypeError. |
|
213 except (AttributeError, TypeError, ValueError): |
|
214 try: |
|
215 # Python 2 raises ValueError trying to destructure an e.g. |
|
216 # bytes. Python 3 raises TypeError. |
|
217 for a, b in v: |
|
218 vmapping[a] = b |
|
219 except (TypeError, ValueError): |
|
220 vmapping[name] = v |
|
221 return templ(tag, **pycompat.strkwargs(vmapping)) |
|
222 lastname = 'last_' + name |
|
223 if lastname in templ: |
|
224 last = values.pop() |
|
225 else: |
|
226 last = None |
|
227 for v in values: |
|
228 yield one(v) |
|
229 if last is not None: |
|
230 yield one(last, tag=lastname) |
|
231 endname = 'end_' + plural |
|
232 if endname in templ: |
|
233 yield templ(endname, **strmapping) |
|
234 |
26 def stringify(thing): |
235 def stringify(thing): |
27 """Turn values into bytes by converting into text and concatenating them""" |
236 """Turn values into bytes by converting into text and concatenating them""" |
28 thing = templatekw.unwraphybrid(thing) |
237 thing = unwraphybrid(thing) |
29 if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes): |
238 if util.safehasattr(thing, '__iter__') and not isinstance(thing, bytes): |
30 if isinstance(thing, str): |
239 if isinstance(thing, str): |
31 # This is only reachable on Python 3 (otherwise |
240 # This is only reachable on Python 3 (otherwise |
32 # isinstance(thing, bytes) would have been true), and is |
241 # isinstance(thing, bytes) would have been true), and is |
33 # here to prevent infinite recursion bugs on Python 3. |
242 # here to prevent infinite recursion bugs on Python 3. |