codezoom 0.1.0.dev0__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.
- codezoom/__init__.py +1 -0
- codezoom/__main__.py +5 -0
- codezoom/cli.py +47 -0
- codezoom/detect.py +58 -0
- codezoom/extractors/__init__.py +0 -0
- codezoom/extractors/base.py +20 -0
- codezoom/extractors/java/__init__.py +43 -0
- codezoom/extractors/python/__init__.py +0 -0
- codezoom/extractors/python/ast_symbols.py +120 -0
- codezoom/extractors/python/module_hierarchy.py +171 -0
- codezoom/extractors/python/package_deps.py +100 -0
- codezoom/model.py +46 -0
- codezoom/pipeline.py +96 -0
- codezoom/renderer/__init__.py +0 -0
- codezoom/renderer/html.py +64 -0
- codezoom/renderer/template.html +549 -0
- codezoom-0.1.0.dev0.dist-info/METADATA +127 -0
- codezoom-0.1.0.dev0.dist-info/RECORD +20 -0
- codezoom-0.1.0.dev0.dist-info/WHEEL +4 -0
- codezoom-0.1.0.dev0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>codezoom</title>
|
|
7
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.28.1/cytoscape.min.js"></script>
|
|
8
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
|
|
9
|
+
<script src="https://unpkg.com/cytoscape-dagre@2.2.2/cytoscape-dagre.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
* {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
19
|
+
background: #f5f5f5;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#header {
|
|
23
|
+
background: white;
|
|
24
|
+
padding: 20px;
|
|
25
|
+
border-bottom: 1px solid #ddd;
|
|
26
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#header h1 {
|
|
30
|
+
font-size: 24px;
|
|
31
|
+
margin-bottom: 10px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#header p {
|
|
35
|
+
color: #666;
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#controls {
|
|
40
|
+
padding: 15px;
|
|
41
|
+
background: white;
|
|
42
|
+
border-bottom: 1px solid #ddd;
|
|
43
|
+
display: flex;
|
|
44
|
+
gap: 10px;
|
|
45
|
+
flex-wrap: wrap;
|
|
46
|
+
align-items: center;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
button {
|
|
50
|
+
padding: 8px 16px;
|
|
51
|
+
background: #2c3e50;
|
|
52
|
+
color: white;
|
|
53
|
+
border: none;
|
|
54
|
+
border-radius: 4px;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
font-size: 14px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
button:hover {
|
|
60
|
+
background: #34495e;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
button:disabled {
|
|
64
|
+
background: #bdc3c7;
|
|
65
|
+
cursor: not-allowed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#breadcrumb {
|
|
69
|
+
flex: 1;
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
color: #666;
|
|
72
|
+
font-style: italic;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#breadcrumb a {
|
|
76
|
+
color: #2c3e50;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
text-decoration: underline;
|
|
79
|
+
margin: 0 5px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#breadcrumb a:hover {
|
|
83
|
+
color: #34495e;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#cy {
|
|
87
|
+
width: 100%;
|
|
88
|
+
height: calc(100vh - 200px);
|
|
89
|
+
background: white;
|
|
90
|
+
border-top: 1px solid #ddd;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#info {
|
|
94
|
+
padding: 10px 15px;
|
|
95
|
+
background: #e8f4f8;
|
|
96
|
+
border-left: 4px solid #3498db;
|
|
97
|
+
font-size: 13px;
|
|
98
|
+
color: #333;
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
101
|
+
</head>
|
|
102
|
+
<body>
|
|
103
|
+
<div id="header">
|
|
104
|
+
<h1 id="titleText"></h1>
|
|
105
|
+
<p>Click on any node to zoom into its dependencies. Use breadcrumbs to navigate back.</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div id="info">
|
|
109
|
+
<strong>How to use:</strong> Click on a package name to drill down and see its internal structure and dependencies. Use the breadcrumb navigation at the top to go back up levels.
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div id="controls">
|
|
113
|
+
<button id="backBtn" onclick="goBack()">← Back</button>
|
|
114
|
+
<button id="resetBtn" onclick="reset()">Reset to Top</button>
|
|
115
|
+
<button onclick="applyLayout('dagre', 'cose')">Layout: Hierarchical</button>
|
|
116
|
+
<button onclick="applyLayout('cose')">Layout: Force-Directed</button>
|
|
117
|
+
<button onclick="cy.fit()">Fit View</button>
|
|
118
|
+
<div style="flex: 1; display: flex; align-items: center; gap: 20px; margin-left: 20px;">
|
|
119
|
+
<div style="display: flex; align-items: center; gap: 10px;">
|
|
120
|
+
<label for="sizeSlider" style="color: #666; font-size: 14px;">Node Size:</label>
|
|
121
|
+
<input type="range" id="sizeSlider" min="20" max="100" value="50" style="width: 120px;">
|
|
122
|
+
<span id="sizeValue" style="color: #666; font-size: 14px; min-width: 30px;">50</span>
|
|
123
|
+
</div>
|
|
124
|
+
<div style="display: flex; align-items: center; gap: 10px;">
|
|
125
|
+
<label for="fontSlider" style="color: #666; font-size: 14px;">Font Size:</label>
|
|
126
|
+
<input type="range" id="fontSlider" min="8" max="40" value="12" style="width: 120px;">
|
|
127
|
+
<span id="fontValue" style="color: #666; font-size: 14px; min-width: 30px;">12</span>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<div id="breadcrumb"></div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div id="cy"></div>
|
|
134
|
+
|
|
135
|
+
<script>
|
|
136
|
+
// All data injected as a single JSON blob
|
|
137
|
+
const data = $DATA_JSON;
|
|
138
|
+
|
|
139
|
+
const hierarchy = data.hierarchy;
|
|
140
|
+
const functionData = data.functionData;
|
|
141
|
+
const pythonDeps = data.external_deps;
|
|
142
|
+
const pythonDepsDirect = data.external_deps_direct;
|
|
143
|
+
const pythonDepsGraph = data.external_deps_graph;
|
|
144
|
+
const ROOT = data.root_node_id;
|
|
145
|
+
|
|
146
|
+
// Set page title
|
|
147
|
+
document.getElementById('titleText').textContent = data.project_name + ' Dependency Explorer';
|
|
148
|
+
document.title = data.project_name + ' — codezoom';
|
|
149
|
+
|
|
150
|
+
// Navigation state
|
|
151
|
+
let currentNode = ROOT;
|
|
152
|
+
const history = [ROOT];
|
|
153
|
+
|
|
154
|
+
let cy = null;
|
|
155
|
+
|
|
156
|
+
function applyNodeColors() {
|
|
157
|
+
if (!cy) return;
|
|
158
|
+
cy.nodes().forEach(node => {
|
|
159
|
+
const d = node.data();
|
|
160
|
+
if (d.bgColor) node.style('background-color', d.bgColor);
|
|
161
|
+
if (d.borderWidth) node.style('border-width', d.borderWidth);
|
|
162
|
+
if (d.borderColor) node.style('border-color', d.borderColor);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function initCytoscape() {
|
|
167
|
+
if (cy) cy.destroy();
|
|
168
|
+
|
|
169
|
+
cy = cytoscape({
|
|
170
|
+
container: document.getElementById('cy'),
|
|
171
|
+
elements: buildGraphData(currentNode),
|
|
172
|
+
style: [
|
|
173
|
+
{
|
|
174
|
+
selector: 'node',
|
|
175
|
+
style: {
|
|
176
|
+
'content': 'data(label)',
|
|
177
|
+
'width': 50,
|
|
178
|
+
'height': 50,
|
|
179
|
+
'padding': '5px',
|
|
180
|
+
'text-opacity': 1,
|
|
181
|
+
'color': '#222222',
|
|
182
|
+
'font-size': '12px',
|
|
183
|
+
'font-weight': 'bold',
|
|
184
|
+
'text-valign': 'center',
|
|
185
|
+
'text-halign': 'center',
|
|
186
|
+
'background-color': '#3498db',
|
|
187
|
+
'border-width': 2,
|
|
188
|
+
'border-color': '#2980b9',
|
|
189
|
+
'background-opacity': 0.9,
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
selector: 'edge',
|
|
194
|
+
style: {
|
|
195
|
+
'curve-style': 'bezier',
|
|
196
|
+
'target-arrow-shape': 'triangle',
|
|
197
|
+
'target-arrow-color': '#666',
|
|
198
|
+
'target-arrow-fill': 'filled',
|
|
199
|
+
'arrow-scale': 1.5,
|
|
200
|
+
'line-color': '#95a5a6',
|
|
201
|
+
'width': 2,
|
|
202
|
+
'opacity': 0.7,
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
selector: 'node:hover',
|
|
207
|
+
style: {
|
|
208
|
+
'background-color': '#f1c40f',
|
|
209
|
+
'border-color': '#d68910',
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
layout: {
|
|
214
|
+
name: 'cose',
|
|
215
|
+
directed: true,
|
|
216
|
+
animate: true,
|
|
217
|
+
animationDuration: 500,
|
|
218
|
+
},
|
|
219
|
+
wheelSensitivity: 0.1,
|
|
220
|
+
minZoom: 0.1,
|
|
221
|
+
maxZoom: 3,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
cy.on('tap', 'node', function(evt) {
|
|
225
|
+
const node = evt.target;
|
|
226
|
+
const nodeId = node.id();
|
|
227
|
+
|
|
228
|
+
const hasChildren = hierarchy[nodeId] && hierarchy[nodeId].children && hierarchy[nodeId].children.length > 0;
|
|
229
|
+
const hasFunctions = functionData[nodeId] && Object.keys(functionData[nodeId]).length > 0;
|
|
230
|
+
|
|
231
|
+
let isClickableClass = false;
|
|
232
|
+
if (nodeId.includes(':')) {
|
|
233
|
+
const lastColonIdx = nodeId.lastIndexOf(':');
|
|
234
|
+
const moduleId = nodeId.substring(0, lastColonIdx);
|
|
235
|
+
const classPath = nodeId.substring(lastColonIdx + 1);
|
|
236
|
+
const isMethodNode = classPath.includes('.');
|
|
237
|
+
if (!isMethodNode) {
|
|
238
|
+
const className = classPath;
|
|
239
|
+
if (functionData[moduleId] && functionData[moduleId][className]) {
|
|
240
|
+
const classInfo = functionData[moduleId][className];
|
|
241
|
+
isClickableClass = classInfo.type === 'class' && classInfo.methods && Object.keys(classInfo.methods).length > 0;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (hasChildren || hasFunctions || isClickableClass) {
|
|
247
|
+
drillInto(nodeId);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
applyNodeColors();
|
|
253
|
+
cy.fit();
|
|
254
|
+
}, 100);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function buildGraphData(nodeId) {
|
|
258
|
+
const elements = [];
|
|
259
|
+
|
|
260
|
+
// Method-level nodes (format: "moduleId:className")
|
|
261
|
+
if (nodeId.includes(':')) {
|
|
262
|
+
const lastColonIdx = nodeId.lastIndexOf(':');
|
|
263
|
+
const moduleId = nodeId.substring(0, lastColonIdx);
|
|
264
|
+
const classPath = nodeId.substring(lastColonIdx + 1);
|
|
265
|
+
const className = classPath.split('.')[0];
|
|
266
|
+
|
|
267
|
+
if (functionData[moduleId] && functionData[moduleId][className] && functionData[moduleId][className].methods) {
|
|
268
|
+
return buildMethodGraph(moduleId, className);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Top-level: show external dependencies
|
|
273
|
+
if (nodeId === ROOT && history.length === 1 && pythonDeps.length > 0) {
|
|
274
|
+
const seenEdges = new Set();
|
|
275
|
+
|
|
276
|
+
pythonDeps.forEach(dep => {
|
|
277
|
+
const isDirect = pythonDepsDirect.includes(dep);
|
|
278
|
+
elements.push({
|
|
279
|
+
data: {
|
|
280
|
+
id: 'py-dep-' + dep,
|
|
281
|
+
label: dep,
|
|
282
|
+
bgColor: isDirect ? '#34495e' : '#95a5a6',
|
|
283
|
+
borderWidth: isDirect ? 2 : 1,
|
|
284
|
+
borderColor: isDirect ? '#2c3e50' : '#7f8c8d',
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
pythonDepsDirect.forEach(dep => {
|
|
290
|
+
const edgeId = ROOT + '->py-dep-' + dep;
|
|
291
|
+
if (!seenEdges.has(edgeId)) {
|
|
292
|
+
elements.push({ data: { id: edgeId, source: ROOT, target: 'py-dep-' + dep } });
|
|
293
|
+
seenEdges.add(edgeId);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
pythonDeps.forEach(dep => {
|
|
298
|
+
if (pythonDepsGraph[dep]) {
|
|
299
|
+
pythonDepsGraph[dep].forEach(depDep => {
|
|
300
|
+
if (pythonDeps.includes(depDep)) {
|
|
301
|
+
const edgeId = 'py-dep-' + dep + '->py-dep-' + depDep;
|
|
302
|
+
if (!seenEdges.has(edgeId)) {
|
|
303
|
+
elements.push({ data: { id: edgeId, source: 'py-dep-' + dep, target: 'py-dep-' + depDep } });
|
|
304
|
+
seenEdges.add(edgeId);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Root node (drillable)
|
|
312
|
+
elements.push({
|
|
313
|
+
data: {
|
|
314
|
+
id: ROOT,
|
|
315
|
+
label: data.project_name,
|
|
316
|
+
bgColor: '#e74c3c',
|
|
317
|
+
borderWidth: 3,
|
|
318
|
+
borderColor: '#c0392b',
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return elements;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Regular hierarchy node
|
|
326
|
+
const node = hierarchy[nodeId];
|
|
327
|
+
if (!node) return elements;
|
|
328
|
+
|
|
329
|
+
// Leaf module with function data
|
|
330
|
+
if (functionData[nodeId]) {
|
|
331
|
+
return buildFunctionGraph(nodeId);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const children = node.children || [];
|
|
335
|
+
const childSet = new Set(children);
|
|
336
|
+
|
|
337
|
+
if (children.length > 0) {
|
|
338
|
+
children.forEach(childId => {
|
|
339
|
+
const childNode = hierarchy[childId];
|
|
340
|
+
const hasChildren = childNode && childNode.children && childNode.children.length > 0;
|
|
341
|
+
const hasFunctions = functionData[childId];
|
|
342
|
+
const isClickable = hasChildren || hasFunctions;
|
|
343
|
+
|
|
344
|
+
elements.push({
|
|
345
|
+
data: {
|
|
346
|
+
id: childId,
|
|
347
|
+
label: childId.split('.').pop(),
|
|
348
|
+
bgColor: isClickable ? '#f39c12' : '#3498db',
|
|
349
|
+
borderWidth: isClickable ? 3 : 2,
|
|
350
|
+
borderColor: isClickable ? '#d68910' : '#2980b9',
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
children.forEach(childId => {
|
|
356
|
+
const childNode = hierarchy[childId];
|
|
357
|
+
if (childNode && childNode.imports_to) {
|
|
358
|
+
childNode.imports_to.forEach(targetId => {
|
|
359
|
+
if (childSet.has(targetId) && childId !== targetId) {
|
|
360
|
+
const edgeId = childId + '->' + targetId;
|
|
361
|
+
if (!elements.find(el => el.data.id === edgeId)) {
|
|
362
|
+
elements.push({ data: { id: edgeId, source: childId, target: targetId } });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return elements;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function buildFunctionGraph(moduleId) {
|
|
374
|
+
const elements = [];
|
|
375
|
+
const functions = functionData[moduleId];
|
|
376
|
+
if (!functions) return elements;
|
|
377
|
+
|
|
378
|
+
Object.entries(functions).forEach(([name, info]) => {
|
|
379
|
+
const hasMethodsOrCalls = (info.methods && Object.keys(info.methods).length > 0) || (info.calls && info.calls.length > 0);
|
|
380
|
+
let bgColor = info.type === 'class' ? '#9b59b6' : '#16a085';
|
|
381
|
+
let borderWidth = hasMethodsOrCalls ? 3 : 2;
|
|
382
|
+
|
|
383
|
+
elements.push({
|
|
384
|
+
data: {
|
|
385
|
+
id: moduleId + ':' + name,
|
|
386
|
+
label: name,
|
|
387
|
+
bgColor: bgColor,
|
|
388
|
+
borderWidth: borderWidth,
|
|
389
|
+
borderColor: info.type === 'class' ? '#8e44ad' : '#117a65',
|
|
390
|
+
hasChildren: info.type === 'class' && info.methods && Object.keys(info.methods).length > 0,
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
Object.entries(functions).forEach(([name, info]) => {
|
|
396
|
+
if (info.calls && info.calls.length > 0) {
|
|
397
|
+
info.calls.forEach(calledName => {
|
|
398
|
+
if (functions[calledName]) {
|
|
399
|
+
const edgeId = moduleId + ':' + name + '->' + calledName;
|
|
400
|
+
if (!elements.find(el => el.data.id === edgeId)) {
|
|
401
|
+
elements.push({ data: { id: edgeId, source: moduleId + ':' + name, target: moduleId + ':' + calledName } });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (info.inherits && info.inherits.length > 0) {
|
|
408
|
+
info.inherits.forEach(parentName => {
|
|
409
|
+
if (functions[parentName]) {
|
|
410
|
+
const edgeId = moduleId + ':' + name + '-inherits-' + parentName;
|
|
411
|
+
if (!elements.find(el => el.data.id === edgeId)) {
|
|
412
|
+
elements.push({ data: { id: edgeId, source: moduleId + ':' + name, target: moduleId + ':' + parentName } });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return elements;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function buildMethodGraph(moduleId, className) {
|
|
423
|
+
const elements = [];
|
|
424
|
+
const functions = functionData[moduleId];
|
|
425
|
+
if (!functions || !functions[className]) return elements;
|
|
426
|
+
|
|
427
|
+
const classInfo = functions[className];
|
|
428
|
+
const methods = classInfo.methods || {};
|
|
429
|
+
|
|
430
|
+
Object.entries(methods).forEach(([methodName, methodInfo]) => {
|
|
431
|
+
elements.push({
|
|
432
|
+
data: {
|
|
433
|
+
id: moduleId + ':' + className + '.' + methodName,
|
|
434
|
+
label: methodName,
|
|
435
|
+
bgColor: '#e74c3c',
|
|
436
|
+
borderWidth: 2,
|
|
437
|
+
borderColor: '#c0392b',
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
Object.entries(methods).forEach(([methodName, methodInfo]) => {
|
|
443
|
+
if (methodInfo.calls && methodInfo.calls.length > 0) {
|
|
444
|
+
methodInfo.calls.forEach(calledName => {
|
|
445
|
+
if (methods[calledName]) {
|
|
446
|
+
const edgeId = moduleId + ':' + className + '.' + methodName + '->' + calledName;
|
|
447
|
+
if (!elements.find(el => el.data.id === edgeId)) {
|
|
448
|
+
elements.push({ data: { id: edgeId, source: moduleId + ':' + className + '.' + methodName, target: moduleId + ':' + className + '.' + calledName } });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
return elements;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function drillInto(nodeId) {
|
|
459
|
+
if (!nodeId.includes(':') && !hierarchy[nodeId]) return;
|
|
460
|
+
currentNode = nodeId;
|
|
461
|
+
history.push(nodeId);
|
|
462
|
+
updateUI();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function goBack() {
|
|
466
|
+
if (history.length > 1) {
|
|
467
|
+
history.pop();
|
|
468
|
+
currentNode = history[history.length - 1];
|
|
469
|
+
updateUI();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function reset() {
|
|
474
|
+
currentNode = ROOT;
|
|
475
|
+
history.length = 1;
|
|
476
|
+
history[0] = ROOT;
|
|
477
|
+
updateUI();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function updateUI() {
|
|
481
|
+
updateBreadcrumb();
|
|
482
|
+
updateBackButton();
|
|
483
|
+
initCytoscape();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function updateBreadcrumb() {
|
|
487
|
+
const breadcrumb = document.getElementById('breadcrumb');
|
|
488
|
+
const parts = [];
|
|
489
|
+
|
|
490
|
+
history.forEach((nodeId, idx) => {
|
|
491
|
+
const label = idx === 0 ? data.project_name : nodeId;
|
|
492
|
+
if (idx === history.length - 1) {
|
|
493
|
+
parts.push('<strong>' + label + '</strong>');
|
|
494
|
+
} else {
|
|
495
|
+
parts.push('<a onclick="jumpTo(' + idx + ')">' + label + '</a>');
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
breadcrumb.innerHTML = '<strong>Navigation:</strong> ' + parts.join(' / ');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function jumpTo(idx) {
|
|
503
|
+
history.length = idx + 1;
|
|
504
|
+
currentNode = history[history.length - 1];
|
|
505
|
+
updateUI();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function updateBackButton() {
|
|
509
|
+
document.getElementById('backBtn').disabled = history.length <= 1;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function applyLayout(name, fallback) {
|
|
513
|
+
try {
|
|
514
|
+
cy.layout({name: name}).run();
|
|
515
|
+
} catch(e) {
|
|
516
|
+
if (fallback) cy.layout({name: fallback}).run();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Node size slider
|
|
521
|
+
const sizeSlider = document.getElementById('sizeSlider');
|
|
522
|
+
const sizeValue = document.getElementById('sizeValue');
|
|
523
|
+
|
|
524
|
+
sizeSlider.addEventListener('input', function() {
|
|
525
|
+
const newSize = this.value;
|
|
526
|
+
sizeValue.textContent = newSize;
|
|
527
|
+
if (cy) {
|
|
528
|
+
cy.nodes().style('width', newSize);
|
|
529
|
+
cy.nodes().style('height', newSize);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Font size slider
|
|
534
|
+
const fontSlider = document.getElementById('fontSlider');
|
|
535
|
+
const fontValue = document.getElementById('fontValue');
|
|
536
|
+
|
|
537
|
+
fontSlider.addEventListener('input', function() {
|
|
538
|
+
const newFontSize = this.value;
|
|
539
|
+
fontValue.textContent = newFontSize;
|
|
540
|
+
if (cy) {
|
|
541
|
+
cy.nodes().style('font-size', newFontSize + 'px');
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Initialize
|
|
546
|
+
updateUI();
|
|
547
|
+
</script>
|
|
548
|
+
</body>
|
|
549
|
+
</html>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codezoom
|
|
3
|
+
Version: 0.1.0.dev0
|
|
4
|
+
Summary: Multi-level code structure explorer — interactive drill-down HTML visualizations
|
|
5
|
+
Author: Curtis Rueden
|
|
6
|
+
License-Expression: Unlicense
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# codezoom
|
|
14
|
+
|
|
15
|
+
Multi-level code structure explorer — interactive drill-down HTML visualizations.
|
|
16
|
+
|
|
17
|
+
codezoom generates a standalone HTML file that lets you explore a project's
|
|
18
|
+
structure at multiple levels of detail:
|
|
19
|
+
|
|
20
|
+
1. **External dependencies** — direct and transitive packages from `pyproject.toml` + `uv.lock`
|
|
21
|
+
2. **Package hierarchy** — sub-packages and modules (via [pydeps](https://github.com/thebjorn/pydeps) or file-tree fallback)
|
|
22
|
+
3. **Module internals** — functions and classes extracted from the AST
|
|
23
|
+
4. **Class internals** — methods and their call relationships
|
|
24
|
+
|
|
25
|
+
Click any node to drill down. Use breadcrumb navigation to go back up.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
<table>
|
|
30
|
+
<tr>
|
|
31
|
+
<td align="center"><a href="screenshots/dependencies.png"><img src="screenshots/jgo-dependencies.png" width="400"></a><br><em>External dependencies</em></td>
|
|
32
|
+
<td align="center"><a href="screenshots/modules-1.png"><img src="screenshots/jgo-submodules.png" width="400"></a><br><em>Project submodules</em></td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<td align="center"><a href="screenshots/modules-1a.png"><img src="screenshots/jgo-env.png" width="400"></a><br><em>A submodule's children</em></td>
|
|
36
|
+
<td align="center"><a href="screenshots/modules-2.png"><img src="screenshots/jgo-cli.png" width="400"></a><br><em>Another submodule's children</em></td>
|
|
37
|
+
</tr>
|
|
38
|
+
<tr>
|
|
39
|
+
<td align="center"><a href="screenshots/modules-3.png"><img src="screenshots/jgo-cli-rich-formatters.png" width="400"></a><br><em>Single-file view</em></td>
|
|
40
|
+
<td></td>
|
|
41
|
+
</tr>
|
|
42
|
+
</table>
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
<details><summary><strong>Installing codezoom with uv</strong></summary>
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
uv tool install codezoom
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
</details>
|
|
53
|
+
<details><summary><strong>Installing codezoom with pip</strong></summary>
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
pip install codezoom
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
</details>
|
|
60
|
+
<details><summary><strong>Installing codezoom from source</strong></summary>
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
git clone https://github.com/apposed/codezoom
|
|
64
|
+
uv tool install --with-editable codezoom codezoom
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
When installed in this fashion, changes to the codezoom source code will be immediately reflected when running `codezoom` from the command line.
|
|
68
|
+
|
|
69
|
+
</details>
|
|
70
|
+
<details><summary><strong>Using codezoom as a dependency</strong></summary>
|
|
71
|
+
|
|
72
|
+
```shell
|
|
73
|
+
uv add codezoom
|
|
74
|
+
```
|
|
75
|
+
or
|
|
76
|
+
```shell
|
|
77
|
+
pixi add --pypi codezoom
|
|
78
|
+
```
|
|
79
|
+
Not sure which to use? [Read this](https://jacobtomlinson.dev/posts/2025/python-package-managers-uv-vs-pixi/#so-what-do-i-use).
|
|
80
|
+
|
|
81
|
+
</details>
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
codezoom /path/to/project # auto-detect, output to <project>_deps.html
|
|
87
|
+
codezoom /path/to/project -o output.html # custom output path
|
|
88
|
+
codezoom /path/to/project --name "My Project" # custom display name
|
|
89
|
+
codezoom /path/to/project --open # open in browser after generating
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Also works as a module:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python -m codezoom /path/to/project
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
|
|
100
|
+
- Python 3.11+
|
|
101
|
+
- No mandatory runtime dependencies beyond the standard library
|
|
102
|
+
- Optional: [pydeps](https://github.com/thebjorn/pydeps) for richer module-level
|
|
103
|
+
import analysis (falls back to file-tree scanning without it)
|
|
104
|
+
|
|
105
|
+
## Per-project configuration
|
|
106
|
+
|
|
107
|
+
Projects can include a `.codezoom.toml` or a `[tool.codezoom]` section in
|
|
108
|
+
`pyproject.toml`:
|
|
109
|
+
|
|
110
|
+
```toml
|
|
111
|
+
[tool.codezoom]
|
|
112
|
+
exclude = ["tests", "docs", "__pycache__"]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The `exclude` list is passed to pydeps via `-xx` to omit modules from the
|
|
116
|
+
hierarchy.
|
|
117
|
+
|
|
118
|
+
## Language support
|
|
119
|
+
|
|
120
|
+
| Language | Status |
|
|
121
|
+
|----------|--------|
|
|
122
|
+
| Python | Supported |
|
|
123
|
+
| Java | Stub extractors present — not yet implemented |
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[UNLICENSE](UNLICENSE) - All copyright disclaimed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
codezoom/__init__.py,sha256=jIjMCRyGDoY_6AwiQaoNO7pAoZokUAE9LPOQnKQBH0Y,56
|
|
2
|
+
codezoom/__main__.py,sha256=YzYrHTiuIa4igLWbSSaGkZWv0aKySM-EgEPy8s7OTso,84
|
|
3
|
+
codezoom/cli.py,sha256=gK-u1AKBjW1nREW-WRmvNHh0OSOLHqXG5L4VEQJrZyo,1161
|
|
4
|
+
codezoom/detect.py,sha256=qTpj8paVv8jzRk3DUePDrNDV1Q9ULJ-59C4wf1IKh0g,2037
|
|
5
|
+
codezoom/model.py,sha256=SlGYSACCjvXO103b7C8XoA4TX80AEIo_Sy9bXMTp4Qo,1261
|
|
6
|
+
codezoom/pipeline.py,sha256=BwjX4Haw6TZFJH-hANWUEGXLq09UCGU1mGw65BjgSME,3089
|
|
7
|
+
codezoom/extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
codezoom/extractors/base.py,sha256=-dBj5KO00bQkRMh9i5S5DwEkK_o3kX_YnE2ztuh2-zU,579
|
|
9
|
+
codezoom/extractors/java/__init__.py,sha256=nDWD0ocQw_Au_yynWSqvkoiWaEKvv-3XndKdh0MU9oY,1325
|
|
10
|
+
codezoom/extractors/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
codezoom/extractors/python/ast_symbols.py,sha256=y42gNw2lThOs5eoLqPz23tjnRwRzMmp9hgwR8wA1L7g,3973
|
|
12
|
+
codezoom/extractors/python/module_hierarchy.py,sha256=cmT-_48LHwZkDrQZd78b7ukvvoPi7JD1602QnOnxxNA,5559
|
|
13
|
+
codezoom/extractors/python/package_deps.py,sha256=38cW_ICfs-0vQ0emJOlQAMjtnprSf2BJ0R1gxRQ76G0,3519
|
|
14
|
+
codezoom/renderer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
codezoom/renderer/html.py,sha256=aACi7gWggy8R59bGlNdSTvvVv0GLS9b-MQf7dhvNQWI,2092
|
|
16
|
+
codezoom/renderer/template.html,sha256=w_Xd5lbArL4_AG9BBjIm9-WBXVQRS0qEDHjhEXQShAY,20526
|
|
17
|
+
codezoom-0.1.0.dev0.dist-info/METADATA,sha256=xAYckpyj9eKM7HSRbMRVgjkj-jPUAzDTyslaPqg7lNA,3981
|
|
18
|
+
codezoom-0.1.0.dev0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
19
|
+
codezoom-0.1.0.dev0.dist-info/entry_points.txt,sha256=XjcJlO6dz0CVGuAF2IFqYE9-i9AXdu-LXJKbKJuDv_g,47
|
|
20
|
+
codezoom-0.1.0.dev0.dist-info/RECORD,,
|