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

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

31 

32 self.engine_functions = {} 

33 

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) 

39 

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 

57 

58 def pushbuf(self): 

59 self.buf.append([]) 

60 

61 def popbuf(self): 

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

63 

64 def echo(self, s): 

65 if s is not None: 

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

67 

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) 

70 

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

79 

80 def attr(self, obj, prop): 

81 try: 

82 return obj[prop] 

83 except: 

84 return getattr(obj, prop) 

85 

86 def attrs(self, obj, props): 

87 for prop in props: 

88 obj = self.attr(obj, prop) 

89 return obj 

90 

91 def iter(self, arg, size): 

92 if not arg: 

93 return '' 

94 

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) 

103 

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

112 

113 try: 

114 return [k for k in arg] 

115 except TypeError: 

116 pass 

117 return vars(arg) 

118 

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) 

125 

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 {} 

135 

136 

137class BaseEngine: 

138 """Basic runtime.""" 

139 

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

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

142 

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

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

145 

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

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

148 

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

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

151 

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

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

154 

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

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

157 

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

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

160 

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

162 return template_fn(self, args, error) 

163 

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) 

167 

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) 

171 

172 

173class Engine(BaseEngine): 

174 """Basic runtime with default filters""" 

175 

176 def def_raw(self, val): 

177 return _str(val) 

178 

179 def def_safe(self, val): 

180 return _str(val) 

181 

182 def def_as_int(self, val): 

183 return int(val) 

184 

185 def def_as_float(self, val): 

186 return float(val) 

187 

188 def def_as_str(self, val): 

189 if isinstance(val, bytes): 

190 return val.decode('utf8') 

191 return _str(val) 

192 

193 def def_xml(self, val): 

194 return _xml(val, False) 

195 

196 def def_xmlq(self, val): 

197 return _xml(val, True) 

198 

199 def def_html(self, val): 

200 return _xml(val, False) 

201 

202 def def_htmlq(self, val): 

203 return _xml(val, True) 

204 

205 def def_h(self, val): 

206 return _xml(val, False) 

207 

208 def def_unhtml(self, val): 

209 return html.unescape(str(val)) 

210 

211 def def_nl2br(self, val): 

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

213 

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

217 

218 def def_url(self, val): 

219 # @TODO 

220 return _str(val) 

221 

222 def def_strip(self, val): 

223 return _str(val).strip() 

224 

225 def def_upper(self, val): 

226 return _str(val).upper() 

227 

228 def def_lower(self, val): 

229 return _str(val).lower() 

230 

231 def def_titlecase(self, val): 

232 return _str(val).title() 

233 

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

253 

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

255 def _repl(m): 

256 url = m.group(0) 

257 

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

259 if target: 

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

261 if rel: 

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

263 

264 text = url 

265 if cut: 

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

267 

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

269 

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

271 

272 def def_format(self, val, fmt): 

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

274 fmt = ':' + fmt 

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

276 

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

282 

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

288 

289 def def_json(self, val, pretty=False): 

290 

291 def dflt(obj): 

292 try: 

293 return vars(obj) 

294 except TypeError: 

295 return str(obj) 

296 

297 if not pretty: 

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

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

300 

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

302 return val[a:b] 

303 

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

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

306 

307 def def_spaces(self, val): 

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

309 

310 def def_commas(self, val): 

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

312 

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

314 return _str(val).split(delim) 

315 

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 

321 

322 def def_sort(self, val): 

323 return sorted(val) 

324 

325 

326## 

327 

328 

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) 

340 

341 

342_formatter = _Formatter() 

343 

344 

345def _str(x): 

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

347 

348 

349def _xml(x, quote=False): 

350 x = _str(x) 

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

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

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

354 if quote: 

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

356 x = x.replace('\'', '&#x27;') 

357 return x