codetac 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -5
- package/readme.md +81 -14
- package/src/ai.mjs +4 -2
- package/src/boundaries.mjs +12 -9
- package/src/browser/bar.js +5 -3
- package/src/cli.mjs +251 -33
- package/src/detect-python.mjs +286 -0
- package/src/detect.mjs +21 -6
- package/src/diagnose.mjs +35 -5
- package/src/digest.mjs +18 -4
- package/src/page.mjs +19 -7
- package/src/panel.mjs +56 -14
- package/src/python/codetac_py/__init__.py +88 -0
- package/src/python/codetac_py/boundaries.py +435 -0
- package/src/python/codetac_py/capture.py +351 -0
- package/src/python/codetac_py/context.py +50 -0
- package/src/python/codetac_py/detail.py +333 -0
- package/src/python/codetac_py/files.py +135 -0
- package/src/python/codetac_py/frameworks.py +72 -0
- package/src/python/codetac_py/hooks.py +72 -0
- package/src/python/codetac_py/jinja_map.py +132 -0
- package/src/python/codetac_py/network.py +704 -0
- package/src/python/codetac_py/page.py +513 -0
- package/src/python/codetac_py/project.py +60 -0
- package/src/python/codetac_py/redact.py +104 -0
- package/src/python/codetac_py/servers.py +514 -0
- package/src/python/codetac_py/sitecustomize.py +62 -0
- package/src/python/codetac_py/writer.py +237 -0
- package/src/python/probe.py +99 -0
- package/src/recording.mjs +22 -4
- package/src/runtime.mjs +13 -1
- package/src/sentences.mjs +4 -0
- package/src/store.mjs +61 -9
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Lines of Jinja templates, the Python equivalent of source maps.
|
|
2
|
+
|
|
3
|
+
The code Jinja compiles from a template has the template's path as its file
|
|
4
|
+
name, but its lines are those of the generated Python. The module of each
|
|
5
|
+
template keeps `debug_info`, pairs "template line=generated line", the table
|
|
6
|
+
Template.get_corresponding_lineno reads. The functions of the template (the
|
|
7
|
+
body `root`, each `block_*`, macros) are shown at their line of the .html.
|
|
8
|
+
|
|
9
|
+
When the table cannot be read, or gives a line the file does not have, the
|
|
10
|
+
template is a single opaque step, with a diagnostic: never a wrong line.
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from .project import project_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_template(globals_):
|
|
18
|
+
"""Whether a frame's globals are those of a compiled Jinja template."""
|
|
19
|
+
return '__jinja_template__' in globals_ or ('debug_info' in globals_ and 'environment' in globals_ and 'blocks' in globals_)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def template_file(code, globals_, root):
|
|
23
|
+
"""The real path of the template, when it is a file of the project."""
|
|
24
|
+
filename = globals_.get('__file__') or code.co_filename
|
|
25
|
+
if not isinstance(filename, str) or filename.startswith('<'):
|
|
26
|
+
return None
|
|
27
|
+
try:
|
|
28
|
+
return project_path(os.path.abspath(filename), root, python=False)
|
|
29
|
+
except (OSError, ValueError):
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_table(text):
|
|
34
|
+
""""1=12&2=17" -> [(1, 12), (2, 17)]; None when it is not a table."""
|
|
35
|
+
if not isinstance(text, str):
|
|
36
|
+
return None
|
|
37
|
+
pairs = []
|
|
38
|
+
if text:
|
|
39
|
+
for item in text.split('&'):
|
|
40
|
+
template_line, _, code_line = item.partition('=')
|
|
41
|
+
if not template_line.isdigit() or not code_line.isdigit():
|
|
42
|
+
return None
|
|
43
|
+
pairs.append((int(template_line), int(code_line)))
|
|
44
|
+
return pairs
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def label(code, template):
|
|
48
|
+
if code.co_name == 'root':
|
|
49
|
+
return 'template %s' % template
|
|
50
|
+
if code.co_name.startswith('block_'):
|
|
51
|
+
return 'bloco %s (%s)' % (code.co_name[len('block_'):], template)
|
|
52
|
+
return '%s (%s)' % (code.co_name, template)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def place(code, table, line_count):
|
|
56
|
+
"""(line, endLine) in the template for a function of its compiled code, or None."""
|
|
57
|
+
first = code.co_firstlineno
|
|
58
|
+
# Only the lines this code runs itself: a macro defined inside the body of
|
|
59
|
+
# the template has lines of its own.
|
|
60
|
+
own = set(line for _, _, line in code.co_lines() if line is not None)
|
|
61
|
+
inside = [template_line for template_line, code_line in table if code_line in own]
|
|
62
|
+
at_def = [template_line for template_line, code_line in table if code_line == first]
|
|
63
|
+
if at_def:
|
|
64
|
+
line = at_def[0]
|
|
65
|
+
elif code.co_name == 'root':
|
|
66
|
+
# The body of the template starts at its first line; Jinja records no line for it.
|
|
67
|
+
line = 1
|
|
68
|
+
elif inside:
|
|
69
|
+
line = min(inside)
|
|
70
|
+
else:
|
|
71
|
+
return None
|
|
72
|
+
end = max(inside + [line])
|
|
73
|
+
if not 1 <= line <= end <= line_count:
|
|
74
|
+
return None
|
|
75
|
+
return line, end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _up_to_date(globals_):
|
|
79
|
+
template = globals_.get('__jinja_template__')
|
|
80
|
+
try:
|
|
81
|
+
return bool(getattr(template, 'is_up_to_date', True))
|
|
82
|
+
except Exception:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Templates(object):
|
|
87
|
+
"""What the capture needs to know about each template file, read once."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, root):
|
|
90
|
+
self.root = root
|
|
91
|
+
self.counts = {} # real path -> number of lines, or None
|
|
92
|
+
self.failed = set() # templates announced as opaque
|
|
93
|
+
|
|
94
|
+
def line_count(self, path):
|
|
95
|
+
if path not in self.counts:
|
|
96
|
+
try:
|
|
97
|
+
with open(path, 'rb') as file:
|
|
98
|
+
data = file.read()
|
|
99
|
+
self.counts[path] = data.count(b'\n') + (0 if data.endswith(b'\n') or not data else 1)
|
|
100
|
+
except OSError:
|
|
101
|
+
self.counts[path] = None
|
|
102
|
+
return self.counts[path]
|
|
103
|
+
|
|
104
|
+
def describe(self, code, globals_):
|
|
105
|
+
"""(metadata of the function, limitation event or None), or None when the code is not
|
|
106
|
+
a function of a project template."""
|
|
107
|
+
path = template_file(code, globals_, self.root)
|
|
108
|
+
if path is None:
|
|
109
|
+
return None
|
|
110
|
+
name = globals_.get('name') if isinstance(globals_.get('name'), str) else os.path.basename(path)
|
|
111
|
+
table = read_table(globals_.get('debug_info'))
|
|
112
|
+
count = self.line_count(path)
|
|
113
|
+
# A table with a line the file does not have is not this file's (the
|
|
114
|
+
# template changed on disk, or it is not Jinja's): nothing of it is used.
|
|
115
|
+
# Nor when Jinja itself says the file changed since it was compiled.
|
|
116
|
+
if table is not None and count and all(1 <= line <= count for line, _ in table) and _up_to_date(globals_):
|
|
117
|
+
found = place(code, table, count)
|
|
118
|
+
else:
|
|
119
|
+
found = None
|
|
120
|
+
if found is not None:
|
|
121
|
+
return {'function': label(code, name), 'file': path, 'line': found[0], 'endLine': found[1], 'column': None,
|
|
122
|
+
'mapped': True, 'async': bool(code.co_flags & 0x0380)}, None
|
|
123
|
+
# Opaque: the body of the template is one step without lines; its other
|
|
124
|
+
# functions are not shown (they would need lines to be told apart).
|
|
125
|
+
if code.co_name != 'root':
|
|
126
|
+
return None
|
|
127
|
+
limitation = None
|
|
128
|
+
if path not in self.failed:
|
|
129
|
+
self.failed.add(path)
|
|
130
|
+
limitation = {'type': 'limitation', 'reason': 'template-lines-unmapped', 'file': path}
|
|
131
|
+
return {'function': 'template %s' % name, 'file': path, 'line': None, 'endLine': None, 'column': None,
|
|
132
|
+
'mapped': False, 'async': False, 'opaque': True}, limitation
|