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
« 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
7import jump
9from . import util, markdown
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)
21GENERATED_NODE = '__DG__'
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]}')
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]}')
38def call(builder, tpl, args):
39 return Engine(builder).call(tpl, args)
42##
45class Engine(jump.Engine):
46 def __init__(self, builder):
47 self.b = builder
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'
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]
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 )
70 def box_info(self, text):
71 return self.wrap_html('<div class="admonition_info">', text, '</div>')
73 def box_warn(self, text):
74 return self.wrap_html('<div class="admonition_warn">', text, '</div>')
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})
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'
88 return self.b.cached(_hash(text + caption), go)
90 DBGRAPH_COLORS = {
91 'text': '#455a64',
92 'arrow': '#b0bec5',
93 'head': '#1565c0',
94 'border': '#b0bec5',
95 'pk': '#ff8f00',
96 'fk': '#00c5cf',
97 }
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)
107 return self.b.cached(_hash(text + caption), go)
110##
113def _dbgraph_to_dot(text, colors):
114 nl = '\n'.join
116 def span(s, color_name):
117 c = colors[color_name]
118 return f'<FONT COLOR="{c}">{s}</FONT>'
120 def bold(s, color_name):
121 c = colors[color_name]
122 return f'<FONT COLOR="{c}"><B>{s}</B></FONT>'
124 def make_table(rows, tab_name):
125 w_col = 0
126 tbody = []
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))
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('⬥', 'pk')
139 elif ref_tab:
140 s += bold('⬥', 'fk')
141 else:
142 s += bold('⬥', 'arrow')
143 s += ' '
144 s += span((col or ' ').ljust(w_col), 'text')
145 s += ' ' * 2
146 s += bold(typ or ' ', 'text')
148 tbody.append(f'<TR><TD ALIGN="left" PORT="{col}">{s}</TD></TR>')
150 c = colors['border']
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 >]"""
159 def make_arrow(tab, col, ref_tab, ref_col):
160 src = f'{tab}:{col}'
161 dst = f'{ref_tab}:{ref_col}'
163 c = colors['arrow']
164 d = 0.5
166 return f"""
167 {src} -> {dst} [
168 color="{c}",
169 dir="both",
170 arrowtail="none",
171 arrowhead="vee",
172 arrowsize="{d}"
173 ]
174 """
176 def parse(text):
177 rows = []
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))
197 return rows
199 rows = parse(text)
201 tables = []
202 arrows = []
204 for tab_name in set(r[0] for r in rows):
205 tables.append(make_table(rows, tab_name))
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))
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 """
224def _error(exc, source_path, source_lineno, env):
225 util.log.error(f'template error: {exc.args[0]} in {source_path!r}:{source_lineno}')
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)
238def _hash(s):
239 return hashlib.sha256(s.encode('utf8')).hexdigest()