Coverage for gws-app/gws/lib/vendor/jump/engine.py: 47%
264 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 23:09 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 23:09 +0200
1"""Basic runtime"""
3import html
4import json
5import re
6import string
7import builtins
8from collections import abc
10from . import compiler
12builtins_dct = {k: getattr(builtins, k) for k in dir(builtins)}
15class RuntimeError(ValueError):
16 def __init__(self, message, path: str, line: int):
17 self.path = path
18 self.line = line
19 message += ' in ' + path + ':' + str(line)
20 super().__init__(message)
21 self.message = message
24class Args:
25 def __getitem__(self, key):
26 return self.__dict__[key]
28 def __setitem__(self, key, value):
29 self.__dict__[key] = value
31 def get(self, key, default=None):
32 return self.__dict__.get(key, default)
35class Environment:
36 def __init__(self, engine, paths, args, errorhandler):
37 self.engine = engine
38 self.buf = []
39 self.haserr = False
40 self.paths = paths
41 self.ARGS = self.prepare(args)
43 self.engine_functions = {}
45 for a in dir(self.engine):
46 if '_' in a:
47 cmd, _, name = a.partition('_')
48 if cmd in compiler.C.DEF_COMMANDS:
49 self.engine_functions[name] = getattr(self.engine, a)
51 if errorhandler:
53 def err_with_handler(exc, pos):
54 try:
55 ok = errorhandler(exc, self.paths[pos[0]], pos[1], self)
56 except:
57 self.haserr = True
58 raise
59 if not ok:
60 self.haserr = True
61 raise
63 self.error = err_with_handler
64 else:
66 def err(exc, pos):
67 self.haserr = True
68 if isinstance(exc, RuntimeError):
69 raise exc
70 raise RuntimeError(str(exc), self.paths[pos[0]], pos[1]) from exc
72 self.error = err
74 def pushbuf(self):
75 self.buf.append([])
77 def popbuf(self):
78 return ''.join(str(s) for s in self.buf.pop())
80 def echo(self, s):
81 if s is not None:
82 self.buf[-1].append(s)
84 def print(self, *args, end='\n'):
85 self.buf[-1].append(' '.join(str(s) for s in args if s is not None) + end)
87 def get(self, name):
88 if name in self.ARGS.__dict__:
89 return self.ARGS.__dict__[name]
90 if name in self.engine_functions:
91 return self.engine_functions[name]
92 if name in builtins_dct:
93 return builtins_dct[name]
94 raise NameError(f'name {name!r} is not defined')
96 def attr(self, obj, prop):
97 try:
98 return obj[prop]
99 except:
100 return getattr(obj, prop)
102 def attrs(self, obj, props):
103 for prop in props:
104 obj = self.attr(obj, prop)
105 return obj
107 def iter(self, arg, size):
108 if not arg:
109 return ''
111 if size == 1:
112 if isinstance(arg, abc.Collection):
113 return arg
114 try:
115 return [k for k in arg]
116 except TypeError:
117 pass
118 return vars(arg)
120 if size == 2:
121 if isinstance(arg, abc.Mapping):
122 return list(arg.items())
123 try:
124 return [k for k in arg]
125 except TypeError:
126 pass
127 return list(vars(arg).items())
129 try:
130 return [k for k in arg]
131 except TypeError:
132 pass
133 return vars(arg)
135 def isempty(self, x):
136 if isinstance(x, str):
137 return len(x.strip()) == 0
138 if isinstance(x, (int, float)):
139 return False
140 return not bool(x)
142 def prepare(self, args):
143 a = Args()
144 if isinstance(args, dict):
145 a.__dict__ = args
146 return a
147 if not args:
148 return a
149 try:
150 a.__dict__ = vars(args)
151 return a
152 except TypeError:
153 pass
154 if hasattr(args, '__slots__'):
155 a.__dict__ = {k: getattr(args, k) for k in args.__slots__ if hasattr(args, k)}
156 return a
157 raise RuntimeError('template arguments must be a dict or an object', self.paths[0], 1)
160class BaseEngine:
161 """Basic runtime."""
163 def environment(self, paths, args, errorhandler):
164 return Environment(self, paths, args, errorhandler)
166 def parse(self, text, **options):
167 return compiler.do('parse', self, options, text, None)
169 def parse_path(self, path, **options):
170 return compiler.do('parse', self, options, None, path)
172 def translate(self, text, **options):
173 return compiler.do('translate', self, options, text, None)
175 def translate_path(self, path, **options):
176 return compiler.do('translate', self, options, None, path)
178 def compile(self, text, **options):
179 return compiler.do('compile', self, options, text, None)
181 def compile_path(self, path, **options):
182 return compiler.do('compile', self, options, None, path)
184 def call(self, template_fn, args=None, error=None):
185 return template_fn(self, args, error)
187 def render(self, text, args=None, error=None, **options):
188 template_fn = self.compile(text, **options)
189 return self.call(template_fn, args, error)
191 def render_path(self, path, args=None, error=None, **options):
192 template_fn = self.compile_path(path, **options)
193 return self.call(template_fn, args, error)
196class Engine(BaseEngine):
197 """Basic runtime with default filters"""
199 def def_raw(self, val):
200 return _str(val)
202 def def_safe(self, val):
203 return _str(val)
205 def def_as_int(self, val):
206 return int(val)
208 def def_as_float(self, val):
209 return float(val)
211 def def_as_str(self, val):
212 if isinstance(val, bytes):
213 return val.decode('utf8')
214 return _str(val)
216 def def_xml(self, val):
217 return _xml(val, False)
219 def def_xmlq(self, val):
220 return _xml(val, True)
222 def def_html(self, val):
223 return _xml(val, False)
225 def def_htmlq(self, val):
226 return _xml(val, True)
228 def def_h(self, val):
229 return _xml(val, False)
231 def def_unhtml(self, val):
232 return html.unescape(str(val))
234 def def_nl2br(self, val):
235 return _str(val).replace('\n', '<br/>')
237 def def_nl2p(self, val):
238 s = re.sub(r'\n[ \t]*\n\s*', '\0', _str(val))
239 return '\n'.join(f'<p>' + p.strip() + '</p>' for p in s.split('\0'))
241 def def_url(self, val):
242 # @TODO
243 return _str(val)
245 def def_strip(self, val):
246 return _str(val).strip()
248 def def_upper(self, val):
249 return _str(val).upper()
251 def def_lower(self, val):
252 return _str(val).lower()
254 def def_titlecase(self, val):
255 return _str(val).title()
257 # based on: https://daringfireball.net/2010/07/improved_regex_for_matching_urls
258 linkify_re = r"""(?xi)
259 \b
260 (
261 https?://
262 |
263 www\d?\.
264 )
265 (
266 [^\s()<>{}\[\]]
267 |
268 \( [^\s()]+ \)
269 )+
270 (
271 \( [^\s()]+ \)
272 |
273 [^\s`!()\[\]{};:'".,<>?«»“”‘’]
274 )
275 """
277 def def_linkify(self, val, target=None, rel=None, cut=None, ellipsis=None):
278 def _repl(m):
279 url = m.group(0)
281 attr = 'href="{}"'.format(self.def_url(url))
282 if target:
283 attr += ' target="{}"'.format(target)
284 if rel:
285 attr += ' rel="{}"'.format(rel)
287 text = url
288 if cut:
289 text = self.def_cut(text, cut, ellipsis)
291 return f'<a {attr}>{_xml(text)}</a>'
293 return re.sub(self.linkify_re, _repl, str(val))
295 def def_format(self, val, fmt):
296 if fmt[0] not in ':!':
297 fmt = ':' + fmt
298 return _formatter.format('{' + fmt + '}', val)
300 def def_cut(self, val, n, ellip=None):
301 val = _str(val)
302 if len(val) <= n:
303 return val
304 return val[:n] + (ellip or '')
306 def def_shorten(self, val, n, ellip=None):
307 val = _str(val)
308 if len(val) <= n:
309 return val
310 return val[: (n + 1) >> 1] + (ellip or '') + val[-(n >> 1) :]
312 def def_json(self, val, pretty=False):
313 def dflt(obj):
314 try:
315 return vars(obj)
316 except TypeError:
317 return str(obj)
319 if not pretty:
320 return json.dumps(val, default=dflt)
321 return json.dumps(val, default=dflt, indent=4, sort_keys=True)
323 def def_slice(self, val, a, b):
324 return val[a:b]
326 def def_join(self, val, delim=''):
327 return str(delim).join(_str(x) for x in val)
329 def def_spaces(self, val):
330 return ' '.join(_str(x).strip() for x in val)
332 def def_commas(self, val):
333 return ','.join(_str(x) for x in val)
335 def def_split(self, val, delim=None):
336 return _str(val).split(delim)
338 def def_lines(self, val, strip=False):
339 s = _str(val).split('\n')
340 if strip:
341 return [ln.strip() for ln in s]
342 return s
344 def def_sort(self, val):
345 return sorted(val)
348##
351class _Formatter(string.Formatter):
352 def format_field(self, val, spec):
353 if spec:
354 s = spec[-1]
355 if s in 'bcdoxXn':
356 val = int(val)
357 elif s in 'eEfFgGn%':
358 val = float(val)
359 elif s == 's':
360 val = _str(val)
361 return format(val, spec)
364_formatter = _Formatter()
367def _str(x):
368 return '' if x is None else str(x)
371def _xml(x, quote=False):
372 x = _str(x)
373 x = x.replace('&', '&')
374 x = x.replace('<', '<')
375 x = x.replace('>', '>')
376 if quote:
377 x = x.replace('"', '"')
378 x = x.replace("'", ''')
379 return x