codebread 1.0.1__py3-none-any.whl → 1.0.3__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.
- codebread/__init__.py +1 -1
- codebread/analyzer.py +5 -1
- codebread/parsers/generic_parser.py +14 -0
- codebread/server.py +12 -3
- codebread/web/app.js +43 -18
- {codebread-1.0.1.dist-info → codebread-1.0.3.dist-info}/METADATA +34 -10
- {codebread-1.0.1.dist-info → codebread-1.0.3.dist-info}/RECORD +11 -11
- {codebread-1.0.1.dist-info → codebread-1.0.3.dist-info}/WHEEL +0 -0
- {codebread-1.0.1.dist-info → codebread-1.0.3.dist-info}/entry_points.txt +0 -0
- {codebread-1.0.1.dist-info → codebread-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {codebread-1.0.1.dist-info → codebread-1.0.3.dist-info}/top_level.txt +0 -0
codebread/__init__.py
CHANGED
codebread/analyzer.py
CHANGED
|
@@ -34,8 +34,12 @@ def analyze(root: str,
|
|
|
34
34
|
info.warnings.append(err)
|
|
35
35
|
info.layer = classify(info, text)
|
|
36
36
|
if info.parsed or info.language in ("html", "css"):
|
|
37
|
-
|
|
37
|
+
src = text if len(text) <= 300_000 else \
|
|
38
38
|
text[:300_000] + "\n… (truncated)"
|
|
39
|
+
if info.language == "config":
|
|
40
|
+
from .parsers.generic_parser import redact_secrets
|
|
41
|
+
src = redact_secrets(src)
|
|
42
|
+
info.source = src
|
|
39
43
|
parsed.append(info)
|
|
40
44
|
for w in info.warnings:
|
|
41
45
|
if w.startswith("Unsupported:"):
|
|
@@ -286,3 +286,17 @@ def parse_config(info: FileInfo, text: str) -> None:
|
|
|
286
286
|
value = value[:57] + "..."
|
|
287
287
|
info.db_config.append(f"{key} = {value}")
|
|
288
288
|
info.db_config = info.db_config[:20]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def redact_secrets(text: str) -> str:
|
|
292
|
+
"""Mask secret-looking values line by line. Used on config-file source
|
|
293
|
+
before it's stored/exported, so the raw "IDE view" / JSON export can't
|
|
294
|
+
leak what the DB-config summary already redacts."""
|
|
295
|
+
out = []
|
|
296
|
+
for line in text.splitlines():
|
|
297
|
+
m = re.match(r"^(\s*[\"']?[\w.\-]+[\"']?\s*[:=]\s*)(.+)$", line)
|
|
298
|
+
if m and SECRET_KEY_RE.search(m.group(1)):
|
|
299
|
+
out.append(m.group(1) + "•••masked•••")
|
|
300
|
+
else:
|
|
301
|
+
out.append(URL_CREDS_RE.sub("://***:***@", line))
|
|
302
|
+
return "\n".join(out)
|
codebread/server.py
CHANGED
|
@@ -10,6 +10,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
10
10
|
from typing import Dict
|
|
11
11
|
|
|
12
12
|
WEB_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web")
|
|
13
|
+
WEB_ROOT = os.path.realpath(WEB_DIR)
|
|
13
14
|
|
|
14
15
|
MIME = {".html": "text/html; charset=utf-8",
|
|
15
16
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -45,11 +46,19 @@ def serve(graph: Dict, port: int = 8137, open_browser: bool = True) -> None:
|
|
|
45
46
|
self._file(path.lstrip("/"))
|
|
46
47
|
|
|
47
48
|
def _file(self, rel: str):
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
# Resolve against WEB_DIR and verify the *result* is still inside
|
|
50
|
+
# it. String-prefix checks on `rel` alone aren't enough: on
|
|
51
|
+
# Windows, os.path.join(WEB_DIR, "C:/some/file") silently
|
|
52
|
+
# discards WEB_DIR because the second argument is drive-absolute,
|
|
53
|
+
# which would otherwise let a request read any file on disk.
|
|
54
|
+
full = os.path.realpath(os.path.join(WEB_DIR, rel))
|
|
55
|
+
try:
|
|
56
|
+
inside = os.path.commonpath([full, WEB_ROOT]) == WEB_ROOT
|
|
57
|
+
except ValueError:
|
|
58
|
+
inside = False # e.g. different drives on Windows
|
|
59
|
+
if not inside:
|
|
50
60
|
self.send_error(403)
|
|
51
61
|
return
|
|
52
|
-
full = os.path.join(WEB_DIR, rel)
|
|
53
62
|
if not os.path.isfile(full):
|
|
54
63
|
self.send_error(404)
|
|
55
64
|
return
|
codebread/web/app.js
CHANGED
|
@@ -348,36 +348,61 @@ function visibleEdges() {
|
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
/* ---------------- DOM (re)build ---------------- */
|
|
351
|
+
/* Incremental: only the nodes/edges that actually entered or left the
|
|
352
|
+
visible set touch the DOM. A full teardown+rebuild here would mean every
|
|
353
|
+
single click in a large, densely-linked graph re-creates *everything*
|
|
354
|
+
already on screen (elements + listeners) just to add one more node —
|
|
355
|
+
that's the "too many links = lag" complaint this replaced. */
|
|
351
356
|
function rebuild() {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
S.drawnEdges = [];
|
|
357
|
+
const newEdges = visibleEdges();
|
|
358
|
+
const newEdgeKeys = new Set(newEdges.map(edgeKey));
|
|
355
359
|
|
|
356
|
-
for (const
|
|
360
|
+
for (const { key } of S.drawnEdges) {
|
|
361
|
+
if (newEdgeKeys.has(key)) continue;
|
|
362
|
+
const els = S.elEdges.get(key);
|
|
363
|
+
if (!els) continue;
|
|
364
|
+
els.vis.remove(); els.hit.remove();
|
|
365
|
+
S.elEdges.delete(key);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const drawnEdges = [];
|
|
369
|
+
for (const e of newEdges) {
|
|
357
370
|
const key = edgeKey(e);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
+
let els = S.elEdges.get(key);
|
|
372
|
+
if (!els) {
|
|
373
|
+
const vis = document.createElementNS(SVGNS, "path");
|
|
374
|
+
vis.setAttribute("class", "edge k-" + e.kind + (e.cycle ? " cycle" : ""));
|
|
375
|
+
const hit = document.createElementNS(SVGNS, "path");
|
|
376
|
+
hit.setAttribute("class", "edge-hit");
|
|
377
|
+
hit.addEventListener("mousemove", (ev) => showEdgeTooltip(e, ev));
|
|
378
|
+
hit.addEventListener("mouseleave", hideTooltip);
|
|
379
|
+
hit.addEventListener("contextmenu", (ev) => {
|
|
380
|
+
ev.preventDefault(); ev.stopPropagation();
|
|
381
|
+
onEdgeContextMenu(e, ev);
|
|
382
|
+
});
|
|
383
|
+
edgesG.appendChild(vis); edgesG.appendChild(hit);
|
|
384
|
+
els = { vis, hit };
|
|
385
|
+
S.elEdges.set(key, els);
|
|
386
|
+
}
|
|
387
|
+
drawnEdges.push({ edge: e, key });
|
|
371
388
|
}
|
|
389
|
+
S.drawnEdges = drawnEdges;
|
|
372
390
|
|
|
391
|
+
for (const [id, g] of S.elNodes) {
|
|
392
|
+
const n = S.nodesById.get(id);
|
|
393
|
+
if (S.visible.has(id) && n && passesFilter(n)) continue;
|
|
394
|
+
g.remove();
|
|
395
|
+
S.elNodes.delete(id);
|
|
396
|
+
}
|
|
373
397
|
for (const id of S.visible) {
|
|
374
398
|
const n = S.nodesById.get(id);
|
|
375
|
-
if (!n || !passesFilter(n)) continue;
|
|
399
|
+
if (!n || !passesFilter(n) || S.elNodes.has(id)) continue;
|
|
376
400
|
ensurePos(id);
|
|
377
401
|
const g = makeNodeEl(n);
|
|
378
402
|
nodesG.appendChild(g);
|
|
379
403
|
S.elNodes.set(id, g);
|
|
380
404
|
}
|
|
405
|
+
|
|
381
406
|
applyHighlight();
|
|
382
407
|
updatePositions();
|
|
383
408
|
renderMinimap();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codebread
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Interactive codebase analyzer & visualizer — slice open a project and see how it's wired together.
|
|
5
5
|
Author: Danial Irsyad
|
|
6
6
|
License: MIT
|
|
@@ -22,7 +22,7 @@ Dynamic: license-file
|
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
25
|
-
<img alt="version" src="https://img.shields.io/badge/version-1.0.
|
|
25
|
+
<img alt="version" src="https://img.shields.io/badge/version-1.0.2-2dd4bf">
|
|
26
26
|
<img alt="python" src="https://img.shields.io/badge/python-3.9%2B-0284c7">
|
|
27
27
|
<img alt="deps" src="https://img.shields.io/badge/required%20dependencies-none-2dd4bf">
|
|
28
28
|
<img alt="license" src="https://img.shields.io/badge/license-MIT-8b5cf6">
|
|
@@ -164,14 +164,38 @@ the map already scanned.
|
|
|
164
164
|
|
|
165
165
|
## Language support
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
|
171
|
-
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
167
|
+
Parser used per language, then exactly what gets extracted — checked feature
|
|
168
|
+
by feature against the actual parser code, not a marketing table:
|
|
169
|
+
|
|
170
|
+
| Language | Parser | Functions / methods | Classes / structs | Backend routes | Outgoing API calls | ORM / DB models | Raw SQL | Call graph | Page-nav links |
|
|
171
|
+
|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
172
|
+
| Python | stdlib `ast` (precise) | ✅ | ✅ | ✅ Flask / FastAPI / Django | ✅ `requests` / `httpx` / `aiohttp` / `urllib` | ✅ SQLAlchemy / Django / peewee / tortoise | ✅ | ✅ | – |
|
|
173
|
+
| JavaScript / TypeScript / JSX / TSX | regex + brace matching | ✅ | ✅ | ✅ Express / Fastify / Koa / NestJS | ✅ `fetch` / `axios` | ✅ Mongoose / Prisma / Sequelize / Knex / TypeORM | ✅ | ✅ | – |
|
|
174
|
+
| Vue / Svelte | regex, `<script>` block only¹ | ✅ | ✅ | ✅ (same as JS) | ✅ (same as JS) | ✅ (same as JS) | ✅ | ✅ | – |
|
|
175
|
+
| Java | regex | ✅ | ✅ | ✅ Spring `@*Mapping` | – | – | ✅ | ✅ | – |
|
|
176
|
+
| C# | regex | ✅ | ✅ | ✅ ASP.NET `[Http*]` | – | – | ✅ | ✅ | – |
|
|
177
|
+
| Go | regex | ✅ | ✅ (structs) | ✅ Gin / net-http style | – | – | ✅ | ✅ | – |
|
|
178
|
+
| PHP | regex | ✅ | ✅ | ✅ Laravel `Route::` | – | – | ✅ | ✅ | ✅ |
|
|
179
|
+
| Ruby | regex | ✅ | ✅ | ✅ Rails / Sinatra | – | – | ✅ | ✅ | ✅ |
|
|
180
|
+
| SQL (`.sql` files) | regex | – | – | – | – | ✅ `CREATE TABLE` schemas + columns | ✅ | – | – |
|
|
181
|
+
| Config (`.env`, json/yaml/toml/ini/xml…)² | key scan | – | – | – | – | – | – | – | – |
|
|
182
|
+
| Anything else (Rust, Kotlin, Swift, C/C++, Scala, Elixir, Erlang, Lua, R, Perl, Dart, Zig…) | – | – | – | – | – | – | – | – | – |
|
|
183
|
+
|
|
184
|
+
Unsupported languages still show up in the Explorer tree with an
|
|
185
|
+
"⚠ Unsupported — parsing skipped" badge — never silently dropped — they just
|
|
186
|
+
have no functions/edges to draw.
|
|
187
|
+
|
|
188
|
+
¹ Vue/Svelte only parse the `<script>` block — template-only bindings
|
|
189
|
+
(`@click`, `v-on`, Svelte reactive markup) aren't extracted.
|
|
190
|
+
² Config-format files get a DB-connection-settings scan instead of code
|
|
191
|
+
extraction — **credentials always masked**, see [export.py](codebread/export.py)-embedded
|
|
192
|
+
source too. Files like `settings.py` or `config.js` are parsed as their real
|
|
193
|
+
language (full extraction above), not this bucket — only non-code formats
|
|
194
|
+
(json/yaml/toml/ini/`.env`/xml/properties) land here.
|
|
195
|
+
|
|
196
|
+
Only Python's `ast`-based parser captures real parameter type annotations,
|
|
197
|
+
return types, and docstrings — every regex-based parser above extracts
|
|
198
|
+
parameter *names* only.
|
|
175
199
|
|
|
176
200
|
## How it classifies layers
|
|
177
201
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
codebread/__init__.py,sha256=
|
|
2
|
-
codebread/analyzer.py,sha256=
|
|
1
|
+
codebread/__init__.py,sha256=InwUkpEmPv-mS3-33Vu1RXBTw1Zm4tYbe9RB4DAsW5s,97
|
|
2
|
+
codebread/analyzer.py,sha256=tCsV0Cwi1l-If5zbNLbMtsuKW3o-rl8mekNG-B7MXpY,3234
|
|
3
3
|
codebread/classifier.py,sha256=jehD-XQ80CP_O4x9YRi4JQqzd4AaHxKpzI83wLG1yvM,4067
|
|
4
4
|
codebread/cli.py,sha256=AMvwk29cOJDjsI-I07pCGbqogdtq1Y6HWnT52FHYyLs,4072
|
|
5
5
|
codebread/connections.py,sha256=FN0c-W9_tnQUVetnF0eVyC6lllyBOyZ3512sXShpy7Q,16609
|
|
@@ -8,17 +8,17 @@ codebread/export.py,sha256=rKYmzuSEXYDrgcItnhdyXI_lo0LN2PSoMe0hYElzU88,1512
|
|
|
8
8
|
codebread/languages.py,sha256=SKHy9v7QPreWZG6FbuX3B0-kq1jqVn-rmwNqJ9ESFqo,4494
|
|
9
9
|
codebread/models.py,sha256=Sw2PJW63dKyl5RpWwfY2ZvCJLYttmjoQg4O5H5DtLgM,4214
|
|
10
10
|
codebread/scanner.py,sha256=bEXSaoENmJ-C_oabj2IF1aWs9-LaIqMc80xXQvo7-eg,8020
|
|
11
|
-
codebread/server.py,sha256=
|
|
11
|
+
codebread/server.py,sha256=H3kUXJaH8FkExNUwdwSD2HxBMceC_24o6pVSjJjFh8g,3543
|
|
12
12
|
codebread/parsers/__init__.py,sha256=ovsvmXGClshiUlDdx6mkBmRu8__OZIqYos6-1eLb9eM,2285
|
|
13
|
-
codebread/parsers/generic_parser.py,sha256=
|
|
13
|
+
codebread/parsers/generic_parser.py,sha256=lz-wLsmQYERoDRM27lhEP3cvNYbomHfA--VI_3LmJ5E,11966
|
|
14
14
|
codebread/parsers/javascript_parser.py,sha256=lVIlydQ3--Owd78816XIM77NntfQ97DeLzYvPrjd5GE,15916
|
|
15
15
|
codebread/parsers/python_parser.py,sha256=QnhCLx01bKYLYqav33cI27qbEQNPUiXnS5uN7cb8poQ,11671
|
|
16
|
-
codebread/web/app.js,sha256=
|
|
16
|
+
codebread/web/app.js,sha256=bsm7cpji0mJrnpr0TPNhsL6w2bQw4RMwA8lS_T57G1s,66567
|
|
17
17
|
codebread/web/index.html,sha256=6YqjC_wO3pfqyG_11uIhvRM16Wagu2l8eSOs1FNc-UY,8693
|
|
18
18
|
codebread/web/style.css,sha256=fiq1bC-I1bC6-6MxRn0blQwGOjO_fdClPrhf5W7zqPw,23729
|
|
19
|
-
codebread-1.0.
|
|
20
|
-
codebread-1.0.
|
|
21
|
-
codebread-1.0.
|
|
22
|
-
codebread-1.0.
|
|
23
|
-
codebread-1.0.
|
|
24
|
-
codebread-1.0.
|
|
19
|
+
codebread-1.0.3.dist-info/licenses/LICENSE,sha256=okINupIvMgiuOlvMVpsM9DT5jb6QQmKBd--yfZq4bjc,1070
|
|
20
|
+
codebread-1.0.3.dist-info/METADATA,sha256=td-SyaalRl1uapOaU1tzcsGFx5oBRRU27-Utb3mYPmE,13034
|
|
21
|
+
codebread-1.0.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
22
|
+
codebread-1.0.3.dist-info/entry_points.txt,sha256=ItG61aUP1YTvA_zA-jlUshBc5BPpJDpCyVnsyaxNXKA,49
|
|
23
|
+
codebread-1.0.3.dist-info/top_level.txt,sha256=KctFB7UQb4tN9YotaJD95GuHWIQuoO5VqyLdo_gEswQ,10
|
|
24
|
+
codebread-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|