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

1"""Basic runtime""" 

2 

3import html 

4import json 

5import re 

6import string 

7import builtins 

8from collections import abc 

9 

10from . import compiler 

11 

12builtins_dct = {k: getattr(builtins, k) for k in dir(builtins)} 

13 

14 

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 

22 

23 

24class Args: 

25 def __getitem__(self, key): 

26 return self.__dict__[key] 

27 

28 def __setitem__(self, key, value): 

29 self.__dict__[key] = value 

30 

31 def get(self, key, default=None): 

32 return self.__dict__.get(key, default) 

33 

34 

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) 

42 

43 self.engine_functions = {} 

44 

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) 

50 

51 if errorhandler: 

52 

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 

62 

63 self.error = err_with_handler 

64 else: 

65 

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 

71 

72 self.error = err 

73 

74 def pushbuf(self): 

75 self.buf.append([]) 

76 

77 def popbuf(self): 

78 return ''.join(str(s) for s in self.buf.pop()) 

79 

80 def echo(self, s): 

81 if s is not None: 

82 self.buf[-1].append(s) 

83 

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) 

86 

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') 

95 

96 def attr(self, obj, prop): 

97 try: 

98 return obj[prop] 

99 except: 

100 return getattr(obj, prop) 

101 

102 def attrs(self, obj, props): 

103 for prop in props: 

104 obj = self.attr(obj, prop) 

105 return obj 

106 

107 def iter(self, arg, size): 

108 if not arg: 

109 return '' 

110 

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) 

119 

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()) 

128 

129 try: 

130 return [k for k in arg] 

131 except TypeError: 

132 pass 

133 return vars(arg) 

134 

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) 

141 

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) 

158 

159 

160class BaseEngine: 

161 """Basic runtime.""" 

162 

163 def environment(self, paths, args, errorhandler): 

164 return Environment(self, paths, args, errorhandler) 

165 

166 def parse(self, text, **options): 

167 return compiler.do('parse', self, options, text, None) 

168 

169 def parse_path(self, path, **options): 

170 return compiler.do('parse', self, options, None, path) 

171 

172 def translate(self, text, **options): 

173 return compiler.do('translate', self, options, text, None) 

174 

175 def translate_path(self, path, **options): 

176 return compiler.do('translate', self, options, None, path) 

177 

178 def compile(self, text, **options): 

179 return compiler.do('compile', self, options, text, None) 

180 

181 def compile_path(self, path, **options): 

182 return compiler.do('compile', self, options, None, path) 

183 

184 def call(self, template_fn, args=None, error=None): 

185 return template_fn(self, args, error) 

186 

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) 

190 

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) 

194 

195 

196class Engine(BaseEngine): 

197 """Basic runtime with default filters""" 

198 

199 def def_raw(self, val): 

200 return _str(val) 

201 

202 def def_safe(self, val): 

203 return _str(val) 

204 

205 def def_as_int(self, val): 

206 return int(val) 

207 

208 def def_as_float(self, val): 

209 return float(val) 

210 

211 def def_as_str(self, val): 

212 if isinstance(val, bytes): 

213 return val.decode('utf8') 

214 return _str(val) 

215 

216 def def_xml(self, val): 

217 return _xml(val, False) 

218 

219 def def_xmlq(self, val): 

220 return _xml(val, True) 

221 

222 def def_html(self, val): 

223 return _xml(val, False) 

224 

225 def def_htmlq(self, val): 

226 return _xml(val, True) 

227 

228 def def_h(self, val): 

229 return _xml(val, False) 

230 

231 def def_unhtml(self, val): 

232 return html.unescape(str(val)) 

233 

234 def def_nl2br(self, val): 

235 return _str(val).replace('\n', '<br/>') 

236 

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')) 

240 

241 def def_url(self, val): 

242 # @TODO 

243 return _str(val) 

244 

245 def def_strip(self, val): 

246 return _str(val).strip() 

247 

248 def def_upper(self, val): 

249 return _str(val).upper() 

250 

251 def def_lower(self, val): 

252 return _str(val).lower() 

253 

254 def def_titlecase(self, val): 

255 return _str(val).title() 

256 

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 """ 

276 

277 def def_linkify(self, val, target=None, rel=None, cut=None, ellipsis=None): 

278 def _repl(m): 

279 url = m.group(0) 

280 

281 attr = 'href="{}"'.format(self.def_url(url)) 

282 if target: 

283 attr += ' target="{}"'.format(target) 

284 if rel: 

285 attr += ' rel="{}"'.format(rel) 

286 

287 text = url 

288 if cut: 

289 text = self.def_cut(text, cut, ellipsis) 

290 

291 return f'<a {attr}>{_xml(text)}</a>' 

292 

293 return re.sub(self.linkify_re, _repl, str(val)) 

294 

295 def def_format(self, val, fmt): 

296 if fmt[0] not in ':!': 

297 fmt = ':' + fmt 

298 return _formatter.format('{' + fmt + '}', val) 

299 

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 '') 

305 

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) :] 

311 

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) 

318 

319 if not pretty: 

320 return json.dumps(val, default=dflt) 

321 return json.dumps(val, default=dflt, indent=4, sort_keys=True) 

322 

323 def def_slice(self, val, a, b): 

324 return val[a:b] 

325 

326 def def_join(self, val, delim=''): 

327 return str(delim).join(_str(x) for x in val) 

328 

329 def def_spaces(self, val): 

330 return ' '.join(_str(x).strip() for x in val) 

331 

332 def def_commas(self, val): 

333 return ','.join(_str(x) for x in val) 

334 

335 def def_split(self, val, delim=None): 

336 return _str(val).split(delim) 

337 

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 

343 

344 def def_sort(self, val): 

345 return sorted(val) 

346 

347 

348## 

349 

350 

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) 

362 

363 

364_formatter = _Formatter() 

365 

366 

367def _str(x): 

368 return '' if x is None else str(x) 

369 

370 

371def _xml(x, quote=False): 

372 x = _str(x) 

373 x = x.replace('&', '&amp;') 

374 x = x.replace('<', '&lt;') 

375 x = x.replace('>', '&gt;') 

376 if quote: 

377 x = x.replace('"', '&#x22;') 

378 x = x.replace("'", '&#x27;') 

379 return x