Coverage for gws-app/gws/lib/vendor/dog/template.py: 0%

139 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 23:09 +0200

1import os 

2import json 

3import tempfile 

4import re 

5import hashlib 

6 

7import jump 

8 

9from . import util, markdown 

10 

11OPTIONS = dict( 

12 comment_symbol='#%', 

13 command_symbol='%', 

14 inline_open_symbol='{%', 

15 inline_close_symbol='%}', 

16 echo_open_symbol='<%', 

17 echo_close_symbol='%>', 

18 echo_start_whitespace=True, 

19) 

20 

21GENERATED_NODE = '__DG__' 

22 

23 

24def compile(builder, path): 

25 try: 

26 return Engine(builder).compile_path(path, **OPTIONS) 

27 except jump.CompileError as exc: 

28 util.log.error(f'template compilation error: {exc.args[0]}') 

29 

30 

31def render(builder, text, path, args): 

32 try: 

33 return Engine(builder).render(text, args, error=_error, path=path, **OPTIONS) 

34 except jump.CompileError as exc: 

35 util.log.error(f'template compilation error: {exc.args[0]}') 

36 

37 

38def call(builder, tpl, args): 

39 return Engine(builder).call(tpl, args) 

40 

41 

42## 

43 

44 

45class Engine(jump.Engine): 

46 def __init__(self, builder): 

47 self.b = builder 

48 

49 def generated_node(self, cls, args): 

50 args['class'] = cls 

51 js = json.dumps(args) 

52 return f'\n```\n{GENERATED_NODE}{js}\n```\n' 

53 

54 def render_dot(self, text): 

55 tmp = os.path.join(tempfile.gettempdir(), util.random_string(8) + '.dot') 

56 util.write_file(tmp, text) 

57 ok, out = util.run(['dot', '-Tsvg', tmp], pipe=True) 

58 if not ok: 

59 return f'<xmp>DOT ERROR: {out}</xmp>' 

60 os.unlink(tmp) 

61 return '<svg' + out.split('<svg')[1] 

62 

63 def wrap_html(self, before, text, after): 

64 return ( 

65 self.generated_node('RawHtmlNode', {'html': before}) 

66 + _dedent(text) 

67 + self.generated_node('RawHtmlNode', {'html': after}) 

68 ) 

69 

70 def box_info(self, text): 

71 return self.wrap_html('<div class="admonition_info">', text, '</div>') 

72 

73 def box_warn(self, text): 

74 return self.wrap_html('<div class="admonition_warn">', text, '</div>') 

75 

76 def box_toc(self, text, depth=1): 

77 items = [ln.strip() for ln in text.strip().splitlines() if ln.strip()] 

78 return self.generated_node('TocNode', {'items': items, 'depth': depth}) 

79 

80 def box_graph(self, text, caption=''): 

81 def go(): 

82 svg = self.render_dot(text) 

83 cap = '' 

84 if caption: 

85 cap = '<figcaption>' + markdown.escape(caption) + '</figcaption>' 

86 return f'<figure>{svg}{cap}</figure>\n' 

87 

88 return self.b.cached(_hash(text + caption), go) 

89 

90 DBGRAPH_COLORS = { 

91 'text': '#455a64', 

92 'arrow': '#b0bec5', 

93 'head': '#1565c0', 

94 'border': '#b0bec5', 

95 'pk': '#ff8f00', 

96 'fk': '#00c5cf', 

97 } 

98 

99 def box_dbgraph(self, text, caption=''): 

100 def go(): 

101 try: 

102 dot = _dbgraph_to_dot(text, self.DBGRAPH_COLORS) 

103 except Exception: 

104 return '<xmp>DBGRAPH SYNTAX ERROR</xmp>' 

105 return self.box_graph(dot, caption) 

106 

107 return self.b.cached(_hash(text + caption), go) 

108 

109 

110## 

111 

112 

113def _dbgraph_to_dot(text, colors): 

114 nl = '\n'.join 

115 

116 def span(s, color_name): 

117 c = colors[color_name] 

118 return f'<FONT COLOR="{c}">{s}</FONT>' 

119 

120 def bold(s, color_name): 

121 c = colors[color_name] 

122 return f'<FONT COLOR="{c}"><B>{s}</B></FONT>' 

123 

124 def make_table(rows, tab_name): 

125 w_col = 0 

126 tbody = [] 

127 

128 for tab, col, typ, pk, ref_tab, ref_col in rows: 

129 if tab != tab_name: 

130 continue 

131 w_col = max(w_col, len(col)) 

132 

133 for tab, col, typ, pk, ref_tab, ref_col in rows: 

134 if tab != tab_name: 

135 continue 

136 s = '' 

137 if pk: 

138 s += bold('&#x2B25;', 'pk') 

139 elif ref_tab: 

140 s += bold('&#x2B25;', 'fk') 

141 else: 

142 s += bold('&#x2B25;', 'arrow') 

143 s += ' ' 

144 s += span((col or ' ').ljust(w_col), 'text') 

145 s += ' ' * 2 

146 s += bold(typ or ' ', 'text') 

147 

148 tbody.append(f'<TR><TD ALIGN="left" PORT="{col}">{s}</TD></TR>') 

149 

150 c = colors['border'] 

151 

152 return f"""{tab_name} [ label=< 

153 <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="6" COLOR="{c}"> 

154 <TR><TD> {bold(tab_name, 'head')} </TD></TR> 

155 {nl(tbody)} 

156 </TABLE> 

157 >]""" 

158 

159 def make_arrow(tab, col, ref_tab, ref_col): 

160 src = f'{tab}:{col}' 

161 dst = f'{ref_tab}:{ref_col}' 

162 

163 c = colors['arrow'] 

164 d = 0.5 

165 

166 return f""" 

167 {src} -> {dst} [ 

168 color="{c}",  

169 dir="both",  

170 arrowtail="none",  

171 arrowhead="vee",  

172 arrowsize="{d}" 

173 ] 

174 """ 

175 

176 def parse(text): 

177 rows = [] 

178 

179 for m in re.findall(r'(?xs)(\w+) \s* \( (.*?) \)', text): 

180 tab, body = m 

181 for r in body.split(','): 

182 r = r.strip().split() 

183 if not r: 

184 continue 

185 col = r.pop(0) 

186 typ, pk, ref_tab, ref_col = '', False, '', '' 

187 while r: 

188 s = r.pop(0) 

189 if s == 'pk': 

190 pk = True 

191 elif s == '->': 

192 ref_tab, ref_col = r.pop(0).split('.') 

193 else: 

194 typ += s + ' ' 

195 rows.append((tab, col, typ.strip(), pk, ref_tab, ref_col)) 

196 

197 return rows 

198 

199 rows = parse(text) 

200 

201 tables = [] 

202 arrows = [] 

203 

204 for tab_name in set(r[0] for r in rows): 

205 tables.append(make_table(rows, tab_name)) 

206 

207 for tab, col, typ, pk, ref_tab, ref_col in rows: 

208 if ref_tab: 

209 arrows.append(make_arrow(tab, col, ref_tab, ref_col)) 

210 

211 return f""" 

212 digraph {{ 

213 layout="dot" 

214 rankdir="LR" 

215 bgcolor="transparent" 

216 splines="spline" 

217 node [fontname="Menlo, monospace", fontsize=9, shape="plaintext"] 

218 {nl(tables)} 

219 {nl(arrows)} 

220 }} 

221 """ 

222 

223 

224def _error(exc, source_path, source_lineno, env): 

225 util.log.error(f'template error: {exc.args[0]} in {source_path!r}:{source_lineno}') 

226 

227 

228def _dedent(text): 

229 lines = text.split('\n') 

230 ind = 100_000 

231 for ln in lines: 

232 n = len(ln.lstrip()) 

233 if n > 0: 

234 ind = min(ind, len(ln) - n) 

235 return '\n'.join(ln[ind:] for ln in lines) 

236 

237 

238def _hash(s): 

239 return hashlib.sha256(s.encode('utf8')).hexdigest()