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

113 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 01:37 +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 span(s, color_name): 

101 c = self.DBGRAPH_COLORS[color_name] 

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

103 

104 def bold(s, color_name): 

105 c = self.DBGRAPH_COLORS[color_name] 

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

107 

108 def parse_row(r): 

109 row = r.strip().split() 

110 k = row.pop() if row[-1].lower() in {'pk', 'fk'} else '' 

111 return [row[0], ' '.join(row[1:]) or ' ', k.lower()] 

112 

113 def format_row(row, w0, w1): 

114 s = '' 

115 s += span(row[0], 'text') + ' ' * (w0 - len(row[0])) 

116 s += bold(row[1], 'text') + ' ' * (w1 - len(row[1])) 

117 if row[2] == 'pk': 

118 s += bold('&#x26bf;', 'pk') 

119 if row[2] == 'fk': 

120 s += bold('&#x26bf;', 'fk') 

121 return f'<TR><TD ALIGN="left" PORT="{row[0]}">{s}</TD></TR>' 

122 

123 def make_table(name, body): 

124 rows = [parse_row(r) for r in body.strip().strip(',').split(',')] 

125 w0 = 2 + max(len(row[0]) for row in rows) 

126 w1 = 2 + max(len(row[1]) for row in rows) 

127 tbody = ''.join(format_row(row, w0, w1) for row in rows) 

128 thead = bold(name, 'head') 

129 c = self.DBGRAPH_COLORS['border'] 

130 return f"""{name} [ label=< 

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

132 <TR><TD>{thead}</TD></TR> 

133 {tbody} 

134 </TABLE> 

135 >]""" 

136 

137 def make_arrow(src, arr, dst): 

138 src = src.replace('.', ':') 

139 dst = dst.replace('.', ':') 

140 mid = (src + '_' + dst).replace(':', '_') 

141 

142 c = self.DBGRAPH_COLORS['arrow'] 

143 d = 0.3 

144 

145 s = f""" 

146 {mid} [shape=point width=0.001] 

147 """ 

148 

149 if arr == '>-': 

150 return s + f""" 

151 {src} -> {mid} [color="{c}", dir="both", arrowtail="crow", arrowhead="none", arrowsize="1"] 

152 {mid} -> {dst} [color="{c}", dir="both", arrowtail="none", arrowhead="dot", arrowsize="{d}"] 

153 """ 

154 if arr == '-<': 

155 return s + f""" 

156 {src} -> {mid} [color="{c}", dir="both", arrowtail="dot", arrowhead="none", arrowsize="{d}"] 

157 {mid} -> {dst} [color="{c}", dir="both", arrowtail="none", arrowhead="crow", arrowsize="1"] 

158 """ 

159 if arr == '--': 

160 return s + f""" 

161 {src} -> {mid} [color="{c}", dir="both", arrowtail="dot", arrowhead="none", arrowsize="{d}"] 

162 {mid} -> {dst} [color="{c}", dir="both", arrowtail="none", arrowhead="dot", arrowsize="{d}"] 

163 """ 

164 

165 def go(): 

166 tables = ''.join( 

167 make_table(name, body) 

168 for name, body in re.findall(r'(?sx) (\w+) \s* \( (.+?) \)', text)) 

169 

170 arrows = ''.join( 

171 make_arrow(src, arr, dst) 

172 for src, arr, dst in re.findall(r'(?sx) ([\w.]+) \s* (>-|-<|--) \s* ([\w.]+)', text)) 

173 

174 dot = f""" 

175 digraph {{ 

176 rankdir="LR" 

177 bgcolor="transparent" 

178 splines="spline" 

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

180 {tables} 

181 {arrows} 

182 }} 

183 """ 

184 

185 return self.box_graph(dot, caption) 

186 

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

188 

189 

190## 

191 

192 

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

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

195 

196 

197def _dedent(text): 

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

199 ind = 100_000 

200 for ln in lines: 

201 n = len(ln.lstrip()) 

202 if n > 0: 

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

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

205 

206 

207def _hash(s): 

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