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
« 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
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 span(s, color_name):
101 c = self.DBGRAPH_COLORS[color_name]
102 return f'<FONT COLOR="{c}">{s}</FONT>'
104 def bold(s, color_name):
105 c = self.DBGRAPH_COLORS[color_name]
106 return f'<FONT COLOR="{c}"><B>{s}</B></FONT>'
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()]
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('⚿', 'pk')
119 if row[2] == 'fk':
120 s += bold('⚿', 'fk')
121 return f'<TR><TD ALIGN="left" PORT="{row[0]}">{s}</TD></TR>'
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 >]"""
137 def make_arrow(src, arr, dst):
138 src = src.replace('.', ':')
139 dst = dst.replace('.', ':')
140 mid = (src + '_' + dst).replace(':', '_')
142 c = self.DBGRAPH_COLORS['arrow']
143 d = 0.3
145 s = f"""
146 {mid} [shape=point width=0.001]
147 """
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 """
165 def go():
166 tables = ''.join(
167 make_table(name, body)
168 for name, body in re.findall(r'(?sx) (\w+) \s* \( (.+?) \)', text))
170 arrows = ''.join(
171 make_arrow(src, arr, dst)
172 for src, arr, dst in re.findall(r'(?sx) ([\w.]+) \s* (>-|-<|--) \s* ([\w.]+)', text))
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 """
185 return self.box_graph(dot, caption)
187 return self.b.cached(_hash(text + caption), go)
190##
193def _error(exc, source_path, source_lineno, env):
194 util.log.error(f'template error: {exc.args[0]} in {source_path!r}:{source_lineno}')
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)
207def _hash(s):
208 return hashlib.sha256(s.encode('utf8')).hexdigest()