code-node-graph 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
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.
- code_node/__init__.py +1 -1
- code_node/analysis/model.py +12 -0
- code_node/history.py +6 -1
- code_node/report/changes.py +76 -2
- code_node/report/graph.py +91 -37
- code_node/report/style.py +53 -14
- {code_node_graph-0.5.0.dist-info → code_node_graph-0.6.0.dist-info}/METADATA +1 -1
- {code_node_graph-0.5.0.dist-info → code_node_graph-0.6.0.dist-info}/RECORD +11 -11
- {code_node_graph-0.5.0.dist-info → code_node_graph-0.6.0.dist-info}/WHEEL +0 -0
- {code_node_graph-0.5.0.dist-info → code_node_graph-0.6.0.dist-info}/entry_points.txt +0 -0
- {code_node_graph-0.5.0.dist-info → code_node_graph-0.6.0.dist-info}/licenses/LICENSE +0 -0
code_node/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ from .libraries import Library, register_library
|
|
|
13
13
|
from .platforms import Platform, get_platform, register_platform
|
|
14
14
|
from .project import EntryPoint, Project, entry_points, read_project
|
|
15
15
|
|
|
16
|
-
__version__ = "0.
|
|
16
|
+
__version__ = "0.6.0"
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"CodeGraph",
|
code_node/analysis/model.py
CHANGED
|
@@ -204,6 +204,8 @@ class History:
|
|
|
204
204
|
first: int # when the oldest of them was made (about when the function was written), in seconds since 1970
|
|
205
205
|
uncommitted: int # its lines changed since the last commit
|
|
206
206
|
now: int # when this was read, for 'three days ago'
|
|
207
|
+
lines: dict[int, Commit | None] = field(default_factory=dict) # each of its lines -> the commit it comes from,
|
|
208
|
+
# None for one changed since the last commit
|
|
207
209
|
|
|
208
210
|
def ago(self, time: int) -> str:
|
|
209
211
|
"""
|
|
@@ -216,6 +218,16 @@ class History:
|
|
|
216
218
|
return f"{count} {unit}{'' if count == 1 else 's'} ago"
|
|
217
219
|
return "today"
|
|
218
220
|
|
|
221
|
+
def since(self, time: int) -> str:
|
|
222
|
+
"""
|
|
223
|
+
The same, short, for beside a line: 'today', '3d', '2w', '5mo', '1y'.
|
|
224
|
+
"""
|
|
225
|
+
days = max(0, self.now - time) // 86_400
|
|
226
|
+
for size, unit in ((365, "y"), (30, "mo"), (7, "w"), (1, "d")):
|
|
227
|
+
if days >= size:
|
|
228
|
+
return f"{days // size}{unit}"
|
|
229
|
+
return "today"
|
|
230
|
+
|
|
219
231
|
|
|
220
232
|
@dataclass
|
|
221
233
|
class CodeFunction:
|
code_node/history.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
What git says about the functions a map shows: `git blame` on the lines of each one, once per file, for the commit
|
|
3
|
-
that last changed it, how many commits and people its lines come from,
|
|
3
|
+
that last changed it, how many commits and people its lines come from, the lines changed since the last commit, and
|
|
4
|
+
the commit each line comes from.
|
|
4
5
|
|
|
5
6
|
git is run on the folder, never the code in it, and only to read: blame with no text conversion and no file system
|
|
6
7
|
monitor. A folder whose own git config names a filter command (one git would run on the files) isn't read at all.
|
|
@@ -98,12 +99,14 @@ def _history(blamed: dict[int, tuple[str, dict[str, str]]], start: int, end: int
|
|
|
98
99
|
commits: dict[str, Commit] = {}
|
|
99
100
|
by_author: Counter[str] = Counter()
|
|
100
101
|
uncommitted = 0
|
|
102
|
+
lines: dict[int, Commit | None] = {}
|
|
101
103
|
for k in range(start, end + 1):
|
|
102
104
|
if k not in blamed:
|
|
103
105
|
continue
|
|
104
106
|
sha, said = blamed[k]
|
|
105
107
|
if sha == _UNCOMMITTED:
|
|
106
108
|
uncommitted += 1
|
|
109
|
+
lines[k] = None
|
|
107
110
|
continue
|
|
108
111
|
if sha not in commits:
|
|
109
112
|
commits[sha] = Commit(
|
|
@@ -114,6 +117,7 @@ def _history(blamed: dict[int, tuple[str, dict[str, str]]], start: int, end: int
|
|
|
114
117
|
page.format(sha) if page else "",
|
|
115
118
|
)
|
|
116
119
|
by_author[commits[sha].author] += 1
|
|
120
|
+
lines[k] = commits[sha]
|
|
117
121
|
last = max(commits.values(), key=lambda c: c.time, default=None)
|
|
118
122
|
return History(
|
|
119
123
|
last=last,
|
|
@@ -122,6 +126,7 @@ def _history(blamed: dict[int, tuple[str, dict[str, str]]], start: int, end: int
|
|
|
122
126
|
first=min((c.time for c in commits.values()), default=0),
|
|
123
127
|
uncommitted=uncommitted,
|
|
124
128
|
now=now,
|
|
129
|
+
lines=lines,
|
|
125
130
|
)
|
|
126
131
|
|
|
127
132
|
|
code_node/report/changes.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
What git says about a function, as HTML: who changed it last and when, with a link to the commit when the repository
|
|
3
3
|
is on GitHub or GitLab, how many commits and people its lines come from, and its lines not committed yet. The same
|
|
4
|
-
in the graph's
|
|
4
|
+
in the graph's hover card and the walk's card, and in the graph's panel line by line, beside its code.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -10,7 +10,7 @@ from datetime import datetime, timezone
|
|
|
10
10
|
|
|
11
11
|
from ..kit.text import _esc
|
|
12
12
|
|
|
13
|
-
from ..analysis.model import History
|
|
13
|
+
from ..analysis.model import Commit, History
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def history_html(history: History | None, short: bool = False) -> str:
|
|
@@ -40,3 +40,77 @@ def history_html(history: History | None, short: bool = False) -> str:
|
|
|
40
40
|
changed = f"{history.uncommitted} line{'' if history.uncommitted == 1 else 's'} changed since the last commit"
|
|
41
41
|
said.append(f'<span class="cnhu">{changed if last else "Not committed yet"}</span>')
|
|
42
42
|
return '<div class="cnh">' + "".join(f"<p>{line}</p>" for line in said) + "</div>"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_AGES = 4 # shades of a line's bar, oldest to newest
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def blame_keys(history: History) -> dict[str, int]:
|
|
49
|
+
"""
|
|
50
|
+
The commits its lines come from, oldest first, each with its place in that order: what a line's mark names to
|
|
51
|
+
find its commit in the panel's last row.
|
|
52
|
+
"""
|
|
53
|
+
commits = {c.sha: c for c in history.lines.values() if c}
|
|
54
|
+
return {sha: k for k, sha in enumerate(sorted(commits, key=lambda sha: (commits[sha].time, sha)))}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def blame_mark(history: History, line: int, keys: dict[str, int], same: bool) -> str:
|
|
58
|
+
"""
|
|
59
|
+
What goes beside a line of the panel's code: who changed it last and how long ago (left out when the line above
|
|
60
|
+
says the same, same), over a bar darker the newer the line, amber for one not committed yet. Pointing at it says
|
|
61
|
+
the commit; '' for a line blame didn't cover, so the code stays in line.
|
|
62
|
+
"""
|
|
63
|
+
if line not in history.lines:
|
|
64
|
+
return '<span class="sb"></span>'
|
|
65
|
+
commit = history.lines[line]
|
|
66
|
+
if commit is None:
|
|
67
|
+
return (
|
|
68
|
+
f'<span class="sb au{" same" if same else ""}" data-k="u" title="Changed since the last commit">'
|
|
69
|
+
"<span>Uncommitted</span></span>"
|
|
70
|
+
)
|
|
71
|
+
k = keys[commit.sha]
|
|
72
|
+
shade = _AGES - 1 if len(keys) == 1 else round(k * (_AGES - 1) / (len(keys) - 1))
|
|
73
|
+
said = f"{commit.subject} · {commit.author}, {history.ago(commit.time)} · {commit.short}"
|
|
74
|
+
return (
|
|
75
|
+
f'<span class="sb a{shade}{" same" if same else ""}" data-k="{k}" title="{_esc(said)}">'
|
|
76
|
+
f"<span>{_esc(commit.author.split(' ')[0] or commit.author)} · {_esc(history.since(commit.time))}</span></span>"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def blame_row(history: History, keys: dict[str, int]) -> str:
|
|
81
|
+
"""
|
|
82
|
+
The panel's last row: when its code was last changed and by whom, then hidden, a row for each commit its lines come
|
|
83
|
+
from, which a script shows in its place while a line from that commit is pointed at.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def sha(commit: Commit) -> str:
|
|
87
|
+
short = f"<code>{_esc(commit.short)}</code>"
|
|
88
|
+
if not commit.url:
|
|
89
|
+
return f'<span class="csfh">{short}</span>'
|
|
90
|
+
return (
|
|
91
|
+
f'<a class="csfh" href="{_esc(commit.url)}" target="_blank" rel="noopener" title="The commit">{short}</a>'
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
last = history.last
|
|
95
|
+
if last:
|
|
96
|
+
day = datetime.fromtimestamp(last.time, timezone.utc).strftime("%Y-%m-%d")
|
|
97
|
+
said = f'Changed <b title="{day}">{_esc(history.ago(last.time))}</b> by {_esc(last.author)}'
|
|
98
|
+
if history.commits > 1:
|
|
99
|
+
people = len(history.authors)
|
|
100
|
+
said += f" · {history.commits} commits" + (f" by {people} people" if people > 1 else "")
|
|
101
|
+
else:
|
|
102
|
+
said = "Not committed yet"
|
|
103
|
+
if history.uncommitted and last:
|
|
104
|
+
lines = f"{history.uncommitted} line{'' if history.uncommitted == 1 else 's'}"
|
|
105
|
+
said += f' · <span class="cnhu">{lines} not committed</span>'
|
|
106
|
+
rows = [f'<p class="csfs"><span>{said}</span>{sha(last) if last else ""}</p>']
|
|
107
|
+
commits = {c.sha: c for c in history.lines.values() if c}
|
|
108
|
+
for found, k in keys.items():
|
|
109
|
+
commit = commits[found]
|
|
110
|
+
rows.append(
|
|
111
|
+
f'<p data-k="{k}" hidden><span><b>{_esc(commit.subject or commit.short)}</b> · {_esc(commit.author)}, '
|
|
112
|
+
f"{_esc(history.ago(commit.time))}</span>{sha(commit)}</p>"
|
|
113
|
+
)
|
|
114
|
+
if history.uncommitted:
|
|
115
|
+
rows.append('<p data-k="u" hidden><span class="cnhu">Changed since the last commit</span></p>')
|
|
116
|
+
return f'<div class="csf">{"".join(rows)}</div>'
|
code_node/report/graph.py
CHANGED
|
@@ -19,7 +19,7 @@ from ..kit.text import _esc
|
|
|
19
19
|
|
|
20
20
|
from ..analysis.findings import _short_name
|
|
21
21
|
from ..analysis.graph import CodeGraph, GraphEdge, GraphNode, GraphView
|
|
22
|
-
from .changes import history_html
|
|
22
|
+
from .changes import blame_keys, blame_mark, blame_row, history_html
|
|
23
23
|
from .brands import AWS_LOGOS, SERVICE_LOGOS, fills, logo, small_logo
|
|
24
24
|
from .editor import editor_name, open_at
|
|
25
25
|
from .style import _ROOT
|
|
@@ -1131,6 +1131,18 @@ _G_PANEL_KINDS = {
|
|
|
1131
1131
|
}
|
|
1132
1132
|
|
|
1133
1133
|
|
|
1134
|
+
_G_GEAR = ( # the panel's settings
|
|
1135
|
+
'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" '
|
|
1136
|
+
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/>'
|
|
1137
|
+
'<path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21'
|
|
1138
|
+
"a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 "
|
|
1139
|
+
"1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1"
|
|
1140
|
+
"a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1"
|
|
1141
|
+
'a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/>'
|
|
1142
|
+
"</svg>"
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
|
|
1134
1146
|
def _g_panel_id(n: GraphNode) -> str:
|
|
1135
1147
|
return f"cn-code-{n.id}"
|
|
1136
1148
|
|
|
@@ -1146,9 +1158,12 @@ def _g_panel(n: GraphNode, lines_of: Callable[[str], list[str]], editor: str = "
|
|
|
1146
1158
|
"""
|
|
1147
1159
|
A box's code, in a panel that slides in from the right when the box is clicked (its id, panel or the box's own, is
|
|
1148
1160
|
the link's target, so it's CSS's :target that opens it): what the box is, then each place it's about with its
|
|
1149
|
-
lines numbered, the lines worth pointing at lit. '
|
|
1161
|
+
lines numbered, the lines worth pointing at lit. A function's lines each say who changed them last and when, and
|
|
1162
|
+
its last row what git says about it, when the folder is in git. '' when none of its code is in the files read.
|
|
1150
1163
|
"""
|
|
1151
|
-
|
|
1164
|
+
history = n.history if n.history and n.history.lines else None
|
|
1165
|
+
keys = blame_keys(history) if history else {}
|
|
1166
|
+
places = [] # (file, first line, last line, lines left out, the editor's link, its rows)
|
|
1152
1167
|
for span in n.code[:_G_PANEL_SPANS]:
|
|
1153
1168
|
lines = lines_of(span.file)
|
|
1154
1169
|
start, end = max(1, span.start), min(len(lines), span.end)
|
|
@@ -1163,48 +1178,66 @@ def _g_panel(n: GraphNode, lines_of: Callable[[str], list[str]], editor: str = "
|
|
|
1163
1178
|
continue
|
|
1164
1179
|
more = end - start + 1 - _G_PANEL_LINES
|
|
1165
1180
|
end = min(end, start + _G_PANEL_LINES - 1)
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1181
|
+
rows, above = [], ""
|
|
1182
|
+
for k in range(start, end + 1):
|
|
1183
|
+
mark = ""
|
|
1184
|
+
if history:
|
|
1185
|
+
commit = history.lines.get(k)
|
|
1186
|
+
said = commit.sha if commit else "u" if k in history.lines else "" # the commit, to say it once a run
|
|
1187
|
+
mark = blame_mark(history, k, keys, bool(said) and said == above)
|
|
1188
|
+
above = said
|
|
1189
|
+
rows.append(
|
|
1190
|
+
f'<span class="sl{" lit" if k in span.lit else ""}">{mark}<span class="sn">{k}</span>'
|
|
1191
|
+
f'<span class="sc">{lines[k - 1]}</span></span>'
|
|
1192
|
+
)
|
|
1193
|
+
places.append((span.file, start, end, more, open_at(editor, span.file, min(span.lit, default=start)), rows))
|
|
1194
|
+
if not places:
|
|
1195
|
+
return ""
|
|
1196
|
+
|
|
1197
|
+
def opens(href: str) -> str:
|
|
1198
|
+
return f'<a class="ced" href="{_esc(href)}">Open in {_esc(editor_name(editor))}</a>' if href else ""
|
|
1199
|
+
|
|
1200
|
+
shown = []
|
|
1201
|
+
for file, start, end, more, href, rows in places:
|
|
1202
|
+
if len(places) > 1: # one place is in the panel's head
|
|
1203
|
+
where = f"line {start}" if start == end else f"lines {start}–{end}"
|
|
1204
|
+
shown.append(f'<div class="csw">{_esc(file)} <span>{where}</span>{opens(href)}</div>')
|
|
1178
1205
|
shown.append(
|
|
1179
|
-
f'<
|
|
1180
|
-
f'<pre class="csc">{rows}</pre>'
|
|
1206
|
+
f'<pre class="csc{" blame" if history else ""}">{"".join(rows)}</pre>'
|
|
1181
1207
|
+ (f'<div class="csm">+{_plural(more, "more line")}</div>' if more > 0 else "")
|
|
1182
1208
|
)
|
|
1183
|
-
if not shown:
|
|
1184
|
-
return ""
|
|
1185
1209
|
rest = len(n.code) - _G_PANEL_SPANS
|
|
1186
1210
|
if rest > 0:
|
|
1187
1211
|
shown.append(f'<div class="csm">+{_plural(rest, "more place")}: the tables below list them all</div>')
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
if line
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1212
|
+
mono = n.kind in ("handler", "function")
|
|
1213
|
+
said = []
|
|
1214
|
+
for line in (n.tip or "").split("\n"):
|
|
1215
|
+
if not line or (n.more and line.startswith("It calls ") and "the graph leaves out" in line):
|
|
1216
|
+
continue # not the calls this graph leaves out: a function's panel is the same in every graph it's in
|
|
1217
|
+
if mono and not said: # 'name(args) in file, 3 lines: its doc': its def is the code's first line
|
|
1218
|
+
head = re.fullmatch(r".+? in .+?, \d+ lines?(?:: (.*))?", line)
|
|
1219
|
+
if head:
|
|
1220
|
+
line = head.group(1) or ""
|
|
1221
|
+
if line:
|
|
1222
|
+
said.append(f'<p class="csd">{_prose(line)}</p>')
|
|
1223
|
+
about = [_esc(_G_PANEL_KINDS.get(n.kind, n.kind))] + ([_esc(n.sub)] if n.sub else [])
|
|
1224
|
+
if len(places) == 1 and places[0][4]:
|
|
1225
|
+
about.append(opens(places[0][4]))
|
|
1226
|
+
key = panel or _g_panel_id(n)
|
|
1227
|
+
opens_bar = bar = gear = ""
|
|
1228
|
+
if history: # its settings: a bar under its head that the gear opens, the checkbox before it so CSS can
|
|
1229
|
+
opens_bar = f'<input type="checkbox" class="cso" id="{_esc(key)}-set" aria-label="Settings">'
|
|
1230
|
+
gear = f'<label class="csx csgr" for="{_esc(key)}-set" title="Settings">{_G_GEAR}</label>'
|
|
1231
|
+
bar = (
|
|
1232
|
+
'<div class="csob"><label><input type="checkbox" class="csg" checked> '
|
|
1233
|
+
"Who changed each line, from git</label></div>"
|
|
1198
1234
|
)
|
|
1199
|
-
said += history_html(n.history)
|
|
1200
|
-
mono = " mono" if n.kind in ("handler", "function") else ""
|
|
1201
1235
|
return (
|
|
1202
|
-
f'<aside class="cside" id="{_esc(
|
|
1203
|
-
f'<div class="csh"><div class="cst"><
|
|
1204
|
-
f'<
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
f"{said}{''.join(shown)}</aside>"
|
|
1236
|
+
f'<aside class="cside" id="{_esc(key)}" aria-label="{_esc("The code of " + n.label)}">{opens_bar}'
|
|
1237
|
+
f'<div class="csh"><div class="cst"><b class="csn{" mono" if mono else ""}">{_esc(n.label)}</b>'
|
|
1238
|
+
f'<span class="css">{" · ".join(about)}</span></div>{gear}'
|
|
1239
|
+
f'<a class="csx" href="#cn-closed" aria-label="Close" title="Close">×</a></div>{bar}'
|
|
1240
|
+
f"{''.join(said)}{''.join(shown)}{blame_row(history, keys) if history else ''}</aside>"
|
|
1208
1241
|
)
|
|
1209
1242
|
|
|
1210
1243
|
|
|
@@ -1446,6 +1479,27 @@ _G_SCRIPT = """<script>
|
|
|
1446
1479
|
if (open && !open.contains(e.target) && !e.target.closest("a[href], label, input, .cgt")) close();
|
|
1447
1480
|
});
|
|
1448
1481
|
document.addEventListener("keydown", (e) => { if (e.key === "Escape") close(); });
|
|
1482
|
+
// A function's code: pointing at a line puts the commit it comes from in its panel's last row, in place of what git
|
|
1483
|
+
// says about all of it.
|
|
1484
|
+
// Its settings, one for every panel: turning git off in one turns it off in all, and the page remembers it.
|
|
1485
|
+
const blame = (on) => document.querySelectorAll(".csg").forEach((box) => { box.checked = on; });
|
|
1486
|
+
try { if (localStorage.getItem("code-node.blame") === "off") blame(false); } catch (e) {}
|
|
1487
|
+
document.addEventListener("change", (e) => {
|
|
1488
|
+
if (!e.target.matches(".csg")) return;
|
|
1489
|
+
blame(e.target.checked);
|
|
1490
|
+
try { localStorage.setItem("code-node.blame", e.target.checked ? "on" : "off"); } catch (e) {}
|
|
1491
|
+
});
|
|
1492
|
+
let blamed = "";
|
|
1493
|
+
document.addEventListener("mouseover", (e) => {
|
|
1494
|
+
const mark = e.target.closest(".cside .sl")?.querySelector(".sb[data-k]");
|
|
1495
|
+
const panel = mark && mark.closest(".cside"), key = mark ? mark.dataset.k : "";
|
|
1496
|
+
const row = (panel || document).querySelector(".csf");
|
|
1497
|
+
if (key + (panel && panel.id) === blamed) return;
|
|
1498
|
+
document.querySelectorAll(".csf p").forEach((p) => {
|
|
1499
|
+
p.hidden = p.closest(".csf") === row && key ? p.dataset.k !== key : p.hasAttribute("data-k");
|
|
1500
|
+
});
|
|
1501
|
+
blamed = key + (panel && panel.id);
|
|
1502
|
+
});
|
|
1449
1503
|
// Moving about a graph as on a canvas: drag it to pan, Ctrl or ⌘ with the wheel (or a trackpad's pinch) to zoom
|
|
1450
1504
|
// around the pointer, and + - 0 when it has focus. A zoom of its own unchecks the bar's buttons, so the plain rule
|
|
1451
1505
|
// (the SVG's width times --z) draws it; picking a button again takes it back. Its box keeps one size, and Fit
|
code_node/report/style.py
CHANGED
|
@@ -31,7 +31,7 @@ FAVICON_LINK = f'<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,
|
|
|
31
31
|
_ROOT = "cnd" # the class around each page's HTML: its stylesheet's rules are scoped to it
|
|
32
32
|
|
|
33
33
|
_EXTRA_CSS = """\
|
|
34
|
-
.cnd{--mono:"Cascadia
|
|
34
|
+
.cnd{--mono:"Cascadia Code","Cascadia Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace}
|
|
35
35
|
.cnd code,.cnd table.t td.tree{font-family:var(--mono)}
|
|
36
36
|
.cnd .wire{display:grid;grid-template-columns:minmax(0,1fr) 26px minmax(0,.9fr) 26px minmax(0,1fr);align-items:center;margin:6px 0 10px;max-width:980px}
|
|
37
37
|
.cnd .wire .wc{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
@@ -280,25 +280,64 @@ _EXTRA_CSS = """\
|
|
|
280
280
|
.cnd .cg .ctt .ctw li{margin:4px 0 0;overflow-wrap:anywhere}
|
|
281
281
|
.cnd .cside{position:fixed;top:0;right:0;bottom:0;z-index:30;box-sizing:border-box;width:min(660px,94vw);overflow:auto;padding:0 20px 28px;background:var(--jp-layout-color0,var(--vscode-editor-background,Canvas));color:CanvasText;border-left:1px solid rgba(127,127,127,.3);box-shadow:-14px 0 36px rgba(15,23,42,.14);transform:translateX(104%);visibility:hidden;transition:transform .22s ease,visibility 0s linear .22s}
|
|
282
282
|
.cnd .cside:target{transform:none;visibility:visible;transition:transform .22s ease}
|
|
283
|
-
.cnd .cside .csh{display:flex;align-items:flex-start;gap:12px;position:sticky;top:0;z-index:1;margin:0 -20px 12px;padding:16px 20px
|
|
283
|
+
.cnd .cside .csh{display:flex;align-items:flex-start;gap:12px;position:sticky;top:0;z-index:1;margin:0 -20px 12px;padding:18px 18px 16px 20px;background:var(--jp-layout-color0,var(--vscode-editor-background,Canvas));border-bottom:1px solid rgba(127,127,127,.2)}
|
|
284
284
|
.cnd .cside .cst{flex:1;min-width:0}
|
|
285
|
-
.cnd .cside .
|
|
286
|
-
.cnd .cside .csn{
|
|
287
|
-
.cnd .cside .
|
|
288
|
-
.cnd .cside .
|
|
289
|
-
.cnd .cside
|
|
290
|
-
.cnd .cside
|
|
285
|
+
.cnd .cside .csn{display:block;font-size:17px;font-weight:650;overflow-wrap:anywhere;margin:0 0 3px}
|
|
286
|
+
.cnd .cside .csn.mono{font:600 17px var(--mono)}
|
|
287
|
+
.cnd .cside .css{display:block;font-size:12.5px;overflow-wrap:anywhere;color:color-mix(in srgb,currentColor 65%,transparent)}
|
|
288
|
+
.cnd .cside .csx{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin:-4px -4px 0 0;border-radius:7px;font-size:20px;line-height:1;color:inherit;text-decoration:none;opacity:.5}
|
|
289
|
+
.cnd .cside .csx:hover,.cnd .cside .csx:focus-visible{opacity:1;background:rgba(127,127,127,.14)}
|
|
290
|
+
.cnd .cside .cso{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
|
|
291
|
+
.cnd .cside label.csgr{cursor:pointer;margin-right:-8px}
|
|
292
|
+
.cnd .cside .cso:focus-visible+.csh label.csgr{opacity:1;outline:2px solid light-dark(#2563eb,#6ea8fe)}
|
|
293
|
+
.cnd .cside .cso:checked+.csh label.csgr{opacity:1;background:rgba(127,127,127,.14)}
|
|
294
|
+
.cnd .cside .csob{display:none;align-items:center;gap:18px;flex-wrap:wrap;margin:-12px -20px 12px;padding:9px 20px;border-bottom:1px solid rgba(127,127,127,.2);background:rgba(127,127,127,.05);font-size:12.5px}
|
|
295
|
+
.cnd .cside .cso:checked~.csob{display:flex}
|
|
296
|
+
.cnd .cside .csob label{display:inline-flex;align-items:center;gap:7px;cursor:pointer}
|
|
297
|
+
.cnd .cside .csob input{margin:0;accent-color:light-dark(#2563eb,#6ea8fe)}
|
|
298
|
+
.cnd .cside:has(.csg:not(:checked)) :is(.sb,.csf){display:none}
|
|
299
|
+
.cnd .cside:has(.csg:not(:checked)) .blame .sn{padding-left:20px}
|
|
300
|
+
.cnd .cside:has(.csg:not(:checked)) .blame .sl:hover>*{background:none}
|
|
301
|
+
.cnd .cside:has(.csg:not(:checked)) .blame .sl.lit>:is(.sn,.sc){background:rgba(245,158,11,.14)}
|
|
291
302
|
.cnd .cside .csd{font-size:13px;line-height:1.5;margin:0 0 6px;opacity:.82;overflow-wrap:anywhere}
|
|
292
|
-
.cnd .cside .csl{font-size:12px;opacity:.6}
|
|
293
303
|
.cnd .cside .csw{font:12px var(--mono);margin:16px 0 5px;overflow-wrap:anywhere}
|
|
294
304
|
.cnd .cside .csw span{opacity:.55}
|
|
295
|
-
.cnd .cside .ced{margin-left:
|
|
305
|
+
.cnd .cside .csw .ced{margin-left:8px;font-family:inherit}
|
|
306
|
+
.cnd .cside .ced{color:light-dark(#2563eb,#6ea8fe);text-decoration:none}
|
|
296
307
|
.cnd .cside .ced:hover{text-decoration:underline}
|
|
297
|
-
.cnd .cside pre.csc{margin:0;padding:
|
|
298
|
-
.cnd .cside .
|
|
299
|
-
.cnd .cside .sl
|
|
300
|
-
.cnd .cside .sn{
|
|
308
|
+
.cnd .cside pre.csc{margin:12px -20px 0;padding:6px 0 10px;border:0;border-block:1px solid rgba(127,127,127,.2);border-radius:0;max-height:none;overflow:auto;font:12.5px/1.7 var(--mono)}
|
|
309
|
+
.cnd .cside :is(.csh,.csob)+pre.csc{margin-top:-12px;border-top:0}
|
|
310
|
+
.cnd .cside .sl{display:flex;width:max-content;min-width:100%}
|
|
311
|
+
.cnd .cside .sn{flex:none;width:2.4em;padding:0 16px 0 20px;text-align:right;opacity:.42;user-select:none;-webkit-user-select:none}
|
|
312
|
+
.cnd .cside .blame .sn{width:2.4em;padding-left:0}
|
|
313
|
+
.cnd .cside .sc{flex:1;padding-right:20px;white-space:pre}
|
|
314
|
+
.cnd .cside .sl.lit>:is(.sn,.sc){background:rgba(245,158,11,.14)}
|
|
315
|
+
.cnd .cside .sb{flex:none;display:flex;align-items:center;gap:10px;width:112px;box-sizing:border-box;padding-left:20px;font:11.5px -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;white-space:nowrap;user-select:none;-webkit-user-select:none}
|
|
316
|
+
.cnd .cside .sb span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;opacity:.5}
|
|
317
|
+
.cnd .cside .sb::after{content:"";flex:none;width:2px;align-self:stretch;margin-right:2px;background:var(--age)}
|
|
318
|
+
.cnd .cside .sb:not(.same)::after{margin-top:3px}
|
|
319
|
+
.cnd .cside .sl:first-child .sb::after{margin-top:0}
|
|
320
|
+
.cnd .cside .sb.same span{visibility:hidden}
|
|
321
|
+
.cnd .cside .sb.a0{--age:color-mix(in srgb,light-dark(#3b6fe0,#6ea8fe) 18%,transparent)}
|
|
322
|
+
.cnd .cside .sb.a1{--age:color-mix(in srgb,light-dark(#3b6fe0,#6ea8fe) 45%,transparent)}
|
|
323
|
+
.cnd .cside .sb.a2{--age:color-mix(in srgb,light-dark(#3b6fe0,#6ea8fe) 72%,transparent)}
|
|
324
|
+
.cnd .cside .sb.a3{--age:light-dark(#3b6fe0,#6ea8fe)}
|
|
325
|
+
.cnd .cside .sb.au{--age:light-dark(#c2680c,#f2b441)}
|
|
326
|
+
.cnd .cside .sb.au span{color:light-dark(#c2680c,#f2b441);opacity:1}
|
|
327
|
+
.cnd .cside .blame .sl:hover>*{background:rgba(127,127,127,.09)}
|
|
328
|
+
.cnd .cside .blame .sl.lit:hover>:is(.sn,.sc){background:rgba(245,158,11,.22)}
|
|
329
|
+
.cnd .cside .blame .sl:hover .sb span{visibility:visible;opacity:.9}
|
|
301
330
|
.cnd .cside .csm{font-size:12px;font-style:italic;opacity:.6;margin:6px 2px 0}
|
|
331
|
+
.cnd .cside .csf{position:sticky;bottom:-28px;z-index:1;margin:0 -20px -28px;padding:11px 20px;background:var(--jp-layout-color0,var(--vscode-editor-background,Canvas));font-size:12.5px}
|
|
332
|
+
.cnd .cside .csf p{margin:0}
|
|
333
|
+
.cnd .cside .csf p:not([hidden]){display:flex;align-items:baseline;gap:12px}
|
|
334
|
+
.cnd .cside .csf p>span:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:color-mix(in srgb,currentColor 70%,transparent)}
|
|
335
|
+
.cnd .cside .csf b{font-weight:600}
|
|
336
|
+
.cnd .cside .csf .csfh{flex:none;color:light-dark(#2563eb,#6ea8fe);text-decoration:none}
|
|
337
|
+
.cnd .cside .csf a.csfh:hover{text-decoration:underline}
|
|
338
|
+
.cnd .cside .csf span.csfh{color:inherit;opacity:.6}
|
|
339
|
+
.cnd .cside .csf code{font:12px var(--mono);background:none;padding:0}
|
|
340
|
+
.cnd .cside .cnhu{color:light-dark(#b45309,#fbbf24)}
|
|
302
341
|
@media (prefers-reduced-motion:reduce){.cnd .cside,.cnd .cside:target{transition:none}}
|
|
303
342
|
.cnd .tabs{position:relative;margin:0 0 8px}
|
|
304
343
|
.cnd .tbar{position:sticky;top:0;z-index:20;display:flex;flex-wrap:wrap;align-items:flex-end;gap:2px;padding-top:6px;margin:0 0 18px;border-bottom:1px solid rgba(127,127,127,.22);background:var(--jp-layout-color0,var(--vscode-editor-background,Canvas))}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: code-node-graph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: See how Python code works without running it: a graph of who calls whom from an entry point, and what each path reaches.
|
|
5
5
|
Project-URL: Homepage, https://github.com/utkarsh5026/code-node
|
|
6
6
|
Project-URL: Documentation, https://utkarsh5026.github.io/code-node/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
code_node/__init__.py,sha256=
|
|
1
|
+
code_node/__init__.py,sha256=XIYIBqbSGZWBP2j54pxOb7oY5thbAHRJwOliWlBU8fs,1106
|
|
2
2
|
code_node/__main__.py,sha256=0W2bmerF1S96rjlTNJZKVzO4FvVhN6ENzAl451A08sQ,101
|
|
3
3
|
code_node/cli.py,sha256=QaieNNhj6_mQTK653jeAxnraZ4hn6CiKa5ju2gVRGfY,12510
|
|
4
|
-
code_node/history.py,sha256=
|
|
4
|
+
code_node/history.py,sha256=YaaEQywgLZ2w2XvtQrQOWg5yBKDQuA5aT030BxQWfUA,6103
|
|
5
5
|
code_node/pages.py,sha256=BweJffESNV-cIKET5pGVp_9edFkGyqhePKIObz0zgi4,3484
|
|
6
6
|
code_node/project.py,sha256=1s6pBwQ-hiEf9v0YzE8b6TETT9E0XBnzQPWGjYDJsnw,16361
|
|
7
7
|
code_node/serve.py,sha256=4yvzDaaL1XpsVinLmvlm1W2bM6WJRGe5GI8wG3XuHic,11456
|
|
@@ -10,7 +10,7 @@ code_node/analysis/aws_model.py,sha256=R2sHppdu9PmlsOGDXf6_6JHumbA5PqbiJ0JgnyB9X
|
|
|
10
10
|
code_node/analysis/findings.py,sha256=S1-VQ1iNi1PkC-2eigNzbJrULa_2Y46avmtwAlV9iqU,19010
|
|
11
11
|
code_node/analysis/graph.py,sha256=loAGXJI0bTMr8s5q6yCm613XOQd1MvE8gHOsIg-NVuo,39503
|
|
12
12
|
code_node/analysis/mapping.py,sha256=6wYkUqaQdM-Vio8XGf_Lw8N9dP_90YtbUxauH4IQ8Tg,5102
|
|
13
|
-
code_node/analysis/model.py,sha256=
|
|
13
|
+
code_node/analysis/model.py,sha256=_HxrCZHl-deFOX3LihfacLpgeNgRqtSYv2qA_TrKyVA,15796
|
|
14
14
|
code_node/analysis/reader.py,sha256=QIqVN2RV477ON69_5YVLXhfnfL3seS8kvP7krpmZPNI,89304
|
|
15
15
|
code_node/analysis/syntax.py,sha256=wyeUr68IDjGp3i9HYYYzcjw8-CvL6a6mmZdWz5LY8j8,7952
|
|
16
16
|
code_node/analysis/terms.py,sha256=ec0f51DkfDRixJSRX1hnyq9Hr5aLRhXuBRHlNJBlZD8,13782
|
|
@@ -34,11 +34,11 @@ code_node/platforms/web.py,sha256=lNjwkz3D0zJkFcJhqD_ShcfTj4uF8miLNqKMtXu-t1U,11
|
|
|
34
34
|
code_node/report/__init__.py,sha256=l3dfZFbLEo-7Fl7O_Rk2lvyAElQnyugSZPI_PzHrG_Q,76
|
|
35
35
|
code_node/report/brands.py,sha256=n32BfFz2d02ZVUHvy8L4wPHBtiD_1FCTKzdR4NyvU88,8372
|
|
36
36
|
code_node/report/build.py,sha256=xnPK_6FMTSV2nyS-N6KcApdq3ZNBA_0Hy8AUZDVIea0,4884
|
|
37
|
-
code_node/report/changes.py,sha256=
|
|
37
|
+
code_node/report/changes.py,sha256=ciD322LsDvdJlhyNOwXKC5AuKddnpfobL7I9mrpF3XE,5481
|
|
38
38
|
code_node/report/editor.py,sha256=TRUX6BTB-oe-m5Vf3xM9PppnC99sCLL4PObXkYAGKoU,1466
|
|
39
|
-
code_node/report/graph.py,sha256=
|
|
39
|
+
code_node/report/graph.py,sha256=tvyhTm2iUlzJlwx3NIyVdVFsX-m66eRxuhvcVvZlwx4,84223
|
|
40
40
|
code_node/report/outline.py,sha256=3RRxU4FMUZKBVjFDsscKZkvDlj_W4ZeW4DwUztg4wd0,8539
|
|
41
|
-
code_node/report/style.py,sha256=
|
|
41
|
+
code_node/report/style.py,sha256=eZud8ieCCBNZu_pELY9zJgavZUaOzypOR4GCrMP6m9w,41825
|
|
42
42
|
code_node/report/tabs.py,sha256=K2XR16owfgEL1kfux61PI9nBcRsgjQurTx1ptMM5Mac,3016
|
|
43
43
|
code_node/report/walk.py,sha256=xPQCWSfE8XnV5yMzSw5MzXH88-xRgHIalbiO2OHz9Ao,29828
|
|
44
44
|
code_node/report/logos/README.md,sha256=8Evw8eApl_56QJrwY7LAtGgJMZE0mp0-HIpRz9bu1ME,613
|
|
@@ -173,8 +173,8 @@ code_node/report/logos/small/aws-systems-manager.svg,sha256=9VzzFdTjaRZO9ulR8Ulp
|
|
|
173
173
|
code_node/report/logos/small/aws-timestream.svg,sha256=yKdKIj7pbooFqDKClEQxW9h7dYV5mnDn0RCHnLUDf8Q,639
|
|
174
174
|
code_node/report/logos/small/aws-waf.svg,sha256=l9ZzBZAl2g8bK1-m6A50ga37l5g1t-9Mj70bxWDykWE,591
|
|
175
175
|
code_node/report/logos/small/aws-xray.svg,sha256=VKg_QtKGZKDCbZvKBRDn7eReuNVbJpduBUVV8MQrZZU,591
|
|
176
|
-
code_node_graph-0.
|
|
177
|
-
code_node_graph-0.
|
|
178
|
-
code_node_graph-0.
|
|
179
|
-
code_node_graph-0.
|
|
180
|
-
code_node_graph-0.
|
|
176
|
+
code_node_graph-0.6.0.dist-info/METADATA,sha256=EEXXZjyeR0kuMOiCO8S01_uDapt0en_DRO0Br1sDlsQ,7725
|
|
177
|
+
code_node_graph-0.6.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
178
|
+
code_node_graph-0.6.0.dist-info/entry_points.txt,sha256=eEArp-uk7ZzODIWr6l62rI0-mIA0-mLDaqJEyKikPJw,49
|
|
179
|
+
code_node_graph-0.6.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
180
|
+
code_node_graph-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|