Coverage for gws-app/gws/lib/vendor/jump/engine.py: 28%
249 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +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 Environment:
25 def __init__(self, engine, paths, args, errorhandler):
26 self.engine = engine
27 self.buf = []
28 self.haserr = False
29 self.paths = paths
30 self.ARGS = self.prepare(args)
32 self.engine_functions = {}
34 for a in dir(self.engine):
35 if '_' in a:
36 cmd, _, name = a.partition('_')
37 if cmd in compiler.C.DEF_COMMANDS:
38 self.engine_functions[name] = getattr(self.engine, a)
40 if errorhandler:
41 def err(exc, pos):
42 try:
43 ok = errorhandler(exc, self.paths[pos[0]], pos[1], self)
44 except:
45 self.haserr = True
46 raise
47 if not ok:
48 self.haserr = True
49 raise
50 else:
51 def err(exc, pos):
52 self.haserr = True
53 if isinstance(exc, RuntimeError):
54 raise exc
55 raise RuntimeError(str(exc), self.paths[pos[0]], pos[1]) from exc
56 self.error = err
58 def pushbuf(self):
59 self.buf.append([])
61 def popbuf(self):
62 return ''.join(str(s) for s in self.buf.pop())
64 def echo(self, s):
65 if s is not None:
66 self.buf[-1].append(s)
68 def print(self, *args, end='\n'):
69 self.buf[-1].append(' '.join(str(s) for s in args if s is not None) + end)
71 def get(self, name):
72 if name in self.ARGS:
73 return self.ARGS[name]
74 if name in self.engine_functions:
75 return self.engine_functions[name]
76 if name in builtins_dct:
77 return builtins_dct[name]
78 raise NameError(f'name {name!r} is not defined')
80 def attr(self, obj, prop):
81 try:
82 return obj[prop]
83 except:
84 return getattr(obj, prop)
86 def attrs(self, obj, props):
87 for prop in props:
88 obj = self.attr(obj, prop)
89 return obj
91 def iter(self, arg, size):
92 if not arg:
93 return ''
95 if size == 1:
96 if isinstance(arg, abc.Collection):
97 return arg
98 try:
99 return [k for k in arg]
100 except TypeError:
101 pass
102 return vars(arg)
104 if size == 2:
105 if isinstance(arg, abc.Mapping):
106 return list(arg.items())
107 try:
108 return [k for k in arg]
109 except TypeError:
110 pass
111 return list(vars(arg).items())
113 try:
114 return [k for k in arg]
115 except TypeError:
116 pass
117 return vars(arg)
119 def isempty(self, x):
120 if isinstance(x, str):
121 return len(x.strip()) == 0
122 if isinstance(x, (int, float)):
123 return False
124 return not bool(x)
126 def prepare(self, args):
127 if isinstance(args, dict):
128 return args
129 if not args:
130 return {}
131 try:
132 return vars(args)
133 except TypeError:
134 return {}
137class BaseEngine:
138 """Basic runtime."""
140 def environment(self, paths, args, errorhandler):
141 return Environment(self, paths, args, errorhandler)
143 def parse(self, text, **options):
144 return compiler.do('parse', self, options, text, None)
146 def parse_path(self, path, **options):
147 return compiler.do('parse', self, options, None, path)
149 def translate(self, text, **options):
150 return compiler.do('translate', self, options, text, None)
152 def translate_path(self, path, **options):
153 return compiler.do('translate', self, options, None, path)
155 def compile(self, text, **options):
156 return compiler.do('compile', self, options, text, None)
158 def compile_path(self, path, **options):
159 return compiler.do('compile', self, options, None, path)
161 def call(self, template_fn, args=None, error=None):
162 return template_fn(self, args, error)
164 def render(self, text, args=None, error=None, **options):
165 template_fn = self.compile(text, **options)
166 return self.call(template_fn, args, error)
168 def render_path(self, path, args=None, error=None, **options):
169 template_fn = self.compile_path(path, **options)
170 return self.call(template_fn, args, error)
173class Engine(BaseEngine):
174 """Basic runtime with default filters"""
176 def def_raw(self, val):
177 return _str(val)
179 def def_safe(self, val):
180 return _str(val)
182 def def_as_int(self, val):
183 return int(val)
185 def def_as_float(self, val):
186 return float(val)
188 def def_as_str(self, val):
189 if isinstance(val, bytes):
190 return val.decode('utf8')
191 return _str(val)
193 def def_xml(self, val):
194 return _xml(val, False)
196 def def_xmlq(self, val):
197 return _xml(val, True)
199 def def_html(self, val):
200 return _xml(val, False)
202 def def_htmlq(self, val):
203 return _xml(val, True)
205 def def_h(self, val):
206 return _xml(val, False)
208 def def_unhtml(self, val):
209 return html.unescape(str(val))
211 def def_nl2br(self, val):
212 return _str(val).replace('\n', '<br/>')
214 def def_nl2p(self, val):
215 s = re.sub(r'\n[ \t]*\n\s*', '\0', _str(val))
216 return '\n'.join(f'<p>' + p.strip() + '</p>' for p in s.split('\0'))
218 def def_url(self, val):
219 # @TODO
220 return _str(val)
222 def def_strip(self, val):
223 return _str(val).strip()
225 def def_upper(self, val):
226 return _str(val).upper()
228 def def_lower(self, val):
229 return _str(val).lower()
231 def def_titlecase(self, val):
232 return _str(val).title()
234 # based on: https://daringfireball.net/2010/07/improved_regex_for_matching_urls
235 linkify_re = r'''(?xi)
236 \b
237 (
238 https?://
239 |
240 www\d?\.
241 )
242 (
243 [^\s()<>{}\[\]]
244 |
245 \( [^\s()]+ \)
246 )+
247 (
248 \( [^\s()]+ \)
249 |
250 [^\s`!()\[\]{};:'".,<>?«»“”‘’]
251 )
252 '''
254 def def_linkify(self, val, target=None, rel=None, cut=None, ellipsis=None):
255 def _repl(m):
256 url = m.group(0)
258 attr = 'href="{}"'.format(self.def_url(url))
259 if target:
260 attr += ' target="{}"'.format(target)
261 if rel:
262 attr += ' rel="{}"'.format(rel)
264 text = url
265 if cut:
266 text = self.def_cut(text, cut, ellipsis)
268 return f'<a {attr}>{_xml(text)}</a>'
270 return re.sub(self.linkify_re, _repl, str(val))
272 def def_format(self, val, fmt):
273 if fmt[0] not in ':!':
274 fmt = ':' + fmt
275 return _formatter.format('{' + fmt + '}', val)
277 def def_cut(self, val, n, ellip=None):
278 val = _str(val)
279 if len(val) <= n:
280 return val
281 return val[:n] + (ellip or '')
283 def def_shorten(self, val, n, ellip=None):
284 val = _str(val)
285 if len(val) <= n:
286 return val
287 return val[:(n + 1) >> 1] + (ellip or '') + val[-(n >> 1):]
289 def def_json(self, val, pretty=False):
291 def dflt(obj):
292 try:
293 return vars(obj)
294 except TypeError:
295 return str(obj)
297 if not pretty:
298 return json.dumps(val, default=dflt)
299 return json.dumps(val, default=dflt, indent=4, sort_keys=True)
301 def def_slice(self, val, a, b):
302 return val[a:b]
304 def def_join(self, val, delim=''):
305 return str(delim).join(_str(x) for x in val)
307 def def_spaces(self, val):
308 return ' '.join(_str(x).strip() for x in val)
310 def def_commas(self, val):
311 return ','.join(_str(x) for x in val)
313 def def_split(self, val, delim=None):
314 return _str(val).split(delim)
316 def def_lines(self, val, strip=False):
317 s = _str(val).split('\n')
318 if strip:
319 return [ln.strip() for ln in s]
320 return s
322 def def_sort(self, val):
323 return sorted(val)
326##
329class _Formatter(string.Formatter):
330 def format_field(self, val, spec):
331 if spec:
332 s = spec[-1]
333 if s in 'bcdoxXn':
334 val = int(val)
335 elif s in 'eEfFgGn%':
336 val = float(val)
337 elif s == 's':
338 val = _str(val)
339 return format(val, spec)
342_formatter = _Formatter()
345def _str(x):
346 return '' if x is None else str(x)
349def _xml(x, quote=False):
350 x = _str(x)
351 x = x.replace('&', '&')
352 x = x.replace('<', '<')
353 x = x.replace('>', '>')
354 if quote:
355 x = x.replace('"', '"')
356 x = x.replace('\'', ''')
357 return x