codebread 1.0.2__py3-none-any.whl → 1.0.4__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 +34 -1
- codebread/server.py +47 -21
- codebread/web/app.js +43 -18
- {codebread-1.0.2.dist-info → codebread-1.0.4.dist-info}/METADATA +62 -13
- {codebread-1.0.2.dist-info → codebread-1.0.4.dist-info}/RECORD +9 -9
- {codebread-1.0.2.dist-info → codebread-1.0.4.dist-info}/WHEEL +0 -0
- {codebread-1.0.2.dist-info → codebread-1.0.4.dist-info}/entry_points.txt +0 -0
- {codebread-1.0.2.dist-info → codebread-1.0.4.dist-info}/licenses/LICENSE +0 -0
- {codebread-1.0.2.dist-info → codebread-1.0.4.dist-info}/top_level.txt +0 -0
codebread/__init__.py
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
1
|
"""CodeBread — slice open a codebase and see its internal structure."""
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _read_version() -> str:
|
|
8
|
+
"""Single source of truth: pyproject.toml's [project] version.
|
|
9
|
+
|
|
10
|
+
Installed packages get it from the wheel's own metadata (always in
|
|
11
|
+
sync with what was actually published). Running from a source
|
|
12
|
+
checkout without installing falls back to reading pyproject.toml
|
|
13
|
+
directly — no parser dependency needed for one `version = "..."`
|
|
14
|
+
line, keeping the zero-dependency promise intact.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
18
|
+
try:
|
|
19
|
+
return version("codebread")
|
|
20
|
+
except PackageNotFoundError:
|
|
21
|
+
pass
|
|
22
|
+
except ImportError:
|
|
23
|
+
pass
|
|
24
|
+
try:
|
|
25
|
+
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
26
|
+
with open(os.path.join(root, "pyproject.toml"), encoding="utf-8") as f:
|
|
27
|
+
for line in f:
|
|
28
|
+
line = line.strip()
|
|
29
|
+
if line.startswith("version"):
|
|
30
|
+
return line.split("=", 1)[1].strip().strip("\"'")
|
|
31
|
+
except OSError:
|
|
32
|
+
pass
|
|
33
|
+
return "0.0.0+unknown"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__version__ = _read_version()
|
codebread/server.py
CHANGED
|
@@ -7,7 +7,7 @@ import socket
|
|
|
7
7
|
import threading
|
|
8
8
|
import webbrowser
|
|
9
9
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
10
|
-
from typing import Dict
|
|
10
|
+
from typing import Dict, Optional
|
|
11
11
|
|
|
12
12
|
WEB_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web")
|
|
13
13
|
WEB_ROOT = os.path.realpath(WEB_DIR)
|
|
@@ -21,7 +21,30 @@ MIME = {".html": "text/html; charset=utf-8",
|
|
|
21
21
|
".ico": "image/x-icon"}
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def safe_web_path(rel: str) -> Optional[str]:
|
|
25
|
+
"""Resolve a request path against WEB_DIR and return it only if the
|
|
26
|
+
*resolved* result is still inside WEB_DIR, else None.
|
|
27
|
+
|
|
28
|
+
String-prefix checks on `rel` alone aren't enough: on Windows,
|
|
29
|
+
os.path.join(WEB_DIR, "C:/some/file") silently discards WEB_DIR because
|
|
30
|
+
the second argument is drive-absolute, which would otherwise let a
|
|
31
|
+
request read any file on disk. Kept as a standalone function so the
|
|
32
|
+
traversal guard has a direct regression test, not just cli/browser use.
|
|
33
|
+
"""
|
|
34
|
+
full = os.path.realpath(os.path.join(WEB_DIR, rel))
|
|
35
|
+
try:
|
|
36
|
+
inside = os.path.commonpath([full, WEB_ROOT]) == WEB_ROOT
|
|
37
|
+
except ValueError:
|
|
38
|
+
inside = False # e.g. different drives on Windows
|
|
39
|
+
return full if inside else None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _free_port(preferred: int) -> Optional[int]:
|
|
43
|
+
"""Find a bindable port near `preferred`, or None if none was free.
|
|
44
|
+
`preferred == 0` means "let the OS pick" and always succeeds — must
|
|
45
|
+
return that as a distinct value from "no port found", since 0 is a
|
|
46
|
+
falsy int and `if not port:` would otherwise treat a successful
|
|
47
|
+
OS-assigned bind as failure."""
|
|
25
48
|
for port in [preferred] + list(range(preferred + 1, preferred + 30)):
|
|
26
49
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
27
50
|
try:
|
|
@@ -29,12 +52,10 @@ def _free_port(preferred: int) -> int:
|
|
|
29
52
|
return port
|
|
30
53
|
except OSError:
|
|
31
54
|
continue
|
|
32
|
-
return
|
|
55
|
+
return None
|
|
33
56
|
|
|
34
57
|
|
|
35
|
-
def
|
|
36
|
-
data_bytes = json.dumps(graph, ensure_ascii=False).encode("utf-8")
|
|
37
|
-
|
|
58
|
+
def _make_handler(data_bytes: bytes):
|
|
38
59
|
class Handler(BaseHTTPRequestHandler):
|
|
39
60
|
def do_GET(self):
|
|
40
61
|
path = self.path.split("?")[0]
|
|
@@ -46,17 +67,8 @@ def serve(graph: Dict, port: int = 8137, open_browser: bool = True) -> None:
|
|
|
46
67
|
self._file(path.lstrip("/"))
|
|
47
68
|
|
|
48
69
|
def _file(self, rel: str):
|
|
49
|
-
|
|
50
|
-
|
|
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:
|
|
70
|
+
full = safe_web_path(rel)
|
|
71
|
+
if full is None:
|
|
60
72
|
self.send_error(403)
|
|
61
73
|
return
|
|
62
74
|
if not os.path.isfile(full):
|
|
@@ -78,12 +90,26 @@ def serve(graph: Dict, port: int = 8137, open_browser: bool = True) -> None:
|
|
|
78
90
|
def log_message(self, fmt, *args): # keep the console clean
|
|
79
91
|
pass
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
return Handler
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build_server(graph: Dict, port: int = 8137) -> Optional[ThreadingHTTPServer]:
|
|
97
|
+
"""Bind a ready-to-serve ThreadingHTTPServer, or None if no port was
|
|
98
|
+
free. Split out from `serve()` so tests (and other embedders) can start
|
|
99
|
+
and cleanly `.shutdown()` a server instead of blocking forever."""
|
|
100
|
+
data_bytes = json.dumps(graph, ensure_ascii=False).encode("utf-8")
|
|
101
|
+
bound_port = _free_port(port)
|
|
102
|
+
if bound_port is None:
|
|
103
|
+
return None
|
|
104
|
+
return ThreadingHTTPServer(("127.0.0.1", bound_port), _make_handler(data_bytes))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def serve(graph: Dict, port: int = 8137, open_browser: bool = True) -> None:
|
|
108
|
+
server = build_server(graph, port)
|
|
109
|
+
if server is None:
|
|
83
110
|
print("[codebread] No free port found near 8137 — aborting serve.")
|
|
84
111
|
return
|
|
85
|
-
|
|
86
|
-
url = f"http://127.0.0.1:{port}/"
|
|
112
|
+
url = f"http://127.0.0.1:{server.server_port}/"
|
|
87
113
|
print(f"[codebread] Serving at {url} (Ctrl+C to stop)")
|
|
88
114
|
if open_browser:
|
|
89
115
|
threading.Timer(0.4, lambda: webbrowser.open(url)).start()
|
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.4
|
|
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
|
|
@@ -10,6 +10,8 @@ Description-Content-Type: text/markdown
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Provides-Extra: full
|
|
12
12
|
Requires-Dist: pathspec>=0.11; extra == "full"
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
13
15
|
Dynamic: license-file
|
|
14
16
|
|
|
15
17
|
<p align="center">
|
|
@@ -22,7 +24,8 @@ Dynamic: license-file
|
|
|
22
24
|
</p>
|
|
23
25
|
|
|
24
26
|
<p align="center">
|
|
25
|
-
<img alt="
|
|
27
|
+
<a href="https://pypi.org/project/codebread/"><img alt="PyPI" src="https://img.shields.io/pypi/v/codebread?color=2dd4bf&label=version"></a>
|
|
28
|
+
<a href="https://github.com/honow48-tech/CodeBread/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/honow48-tech/CodeBread/actions/workflows/ci.yml/badge.svg"></a>
|
|
26
29
|
<img alt="python" src="https://img.shields.io/badge/python-3.9%2B-0284c7">
|
|
27
30
|
<img alt="deps" src="https://img.shields.io/badge/required%20dependencies-none-2dd4bf">
|
|
28
31
|
<img alt="license" src="https://img.shields.io/badge/license-MIT-8b5cf6">
|
|
@@ -164,14 +167,38 @@ the map already scanned.
|
|
|
164
167
|
|
|
165
168
|
## Language support
|
|
166
169
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
|
171
|
-
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
170
|
+
Parser used per language, then exactly what gets extracted — checked feature
|
|
171
|
+
by feature against the actual parser code, not a marketing table:
|
|
172
|
+
|
|
173
|
+
| Language | Parser | Functions / methods | Classes / structs | Backend routes | Outgoing API calls | ORM / DB models | Raw SQL | Call graph | Page-nav links |
|
|
174
|
+
|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
175
|
+
| Python | stdlib `ast` (precise) | ✅ | ✅ | ✅ Flask / FastAPI / Django | ✅ `requests` / `httpx` / `aiohttp` / `urllib` | ✅ SQLAlchemy / Django / peewee / tortoise | ✅ | ✅ | – |
|
|
176
|
+
| JavaScript / TypeScript / JSX / TSX | regex + brace matching | ✅ | ✅ | ✅ Express / Fastify / Koa / NestJS | ✅ `fetch` / `axios` | ✅ Mongoose / Prisma / Sequelize / Knex / TypeORM | ✅ | ✅ | – |
|
|
177
|
+
| Vue / Svelte | regex, `<script>` block only¹ | ✅ | ✅ | ✅ (same as JS) | ✅ (same as JS) | ✅ (same as JS) | ✅ | ✅ | – |
|
|
178
|
+
| Java | regex | ✅ | ✅ | ✅ Spring `@*Mapping` | – | – | ✅ | ✅ | – |
|
|
179
|
+
| C# | regex | ✅ | ✅ | ✅ ASP.NET `[Http*]` | – | – | ✅ | ✅ | – |
|
|
180
|
+
| Go | regex | ✅ | ✅ (structs) | ✅ Gin / net-http style | – | – | ✅ | ✅ | – |
|
|
181
|
+
| PHP | regex | ✅ | ✅ | ✅ Laravel `Route::` | – | – | ✅ | ✅ | ✅ |
|
|
182
|
+
| Ruby | regex | ✅ | ✅ | ✅ Rails / Sinatra | – | – | ✅ | ✅ | ✅ |
|
|
183
|
+
| SQL (`.sql` files) | regex | – | – | – | – | ✅ `CREATE TABLE` schemas + columns | ✅ | – | – |
|
|
184
|
+
| Config (`.env`, json/yaml/toml/ini/xml…)² | key scan | – | – | – | – | – | – | – | – |
|
|
185
|
+
| Anything else (Rust, Kotlin, Swift, C/C++, Scala, Elixir, Erlang, Lua, R, Perl, Dart, Zig…) | – | – | – | – | – | – | – | – | – |
|
|
186
|
+
|
|
187
|
+
Unsupported languages still show up in the Explorer tree with an
|
|
188
|
+
"⚠ Unsupported — parsing skipped" badge — never silently dropped — they just
|
|
189
|
+
have no functions/edges to draw.
|
|
190
|
+
|
|
191
|
+
¹ Vue/Svelte only parse the `<script>` block — template-only bindings
|
|
192
|
+
(`@click`, `v-on`, Svelte reactive markup) aren't extracted.
|
|
193
|
+
² Config-format files get a DB-connection-settings scan instead of code
|
|
194
|
+
extraction — **credentials always masked**, see [export.py](codebread/export.py)-embedded
|
|
195
|
+
source too. Files like `settings.py` or `config.js` are parsed as their real
|
|
196
|
+
language (full extraction above), not this bucket — only non-code formats
|
|
197
|
+
(json/yaml/toml/ini/`.env`/xml/properties) land here.
|
|
198
|
+
|
|
199
|
+
Only Python's `ast`-based parser captures real parameter type annotations,
|
|
200
|
+
return types, and docstrings — every regex-based parser above extracts
|
|
201
|
+
parameter *names* only.
|
|
175
202
|
|
|
176
203
|
## How it classifies layers
|
|
177
204
|
|
|
@@ -214,12 +241,30 @@ codebread/
|
|
|
214
241
|
server.py ← stdlib local web server
|
|
215
242
|
export.py ← JSON + single-file HTML export
|
|
216
243
|
web/ ← the UI (vanilla JS + SVG, zero dependencies)
|
|
244
|
+
tests/ ← pytest suite + per-language fixture corpus
|
|
217
245
|
assets/ ← logo (.svg source + .png for the README —
|
|
218
246
|
GitHub blocks same-repo SVG <img> embeds)
|
|
219
247
|
```
|
|
220
248
|
|
|
221
249
|
---
|
|
222
250
|
|
|
251
|
+
## Testing
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
pip install -e ".[full,test]"
|
|
255
|
+
pytest -q
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The suite covers every parser (a small fixture file per language under
|
|
259
|
+
`tests/fixtures/`), the connection/graph builder (call resolution,
|
|
260
|
+
API↔route matching, DB edges, orphan + cycle detection), the scanner
|
|
261
|
+
(`.gitignore` handling, language detection), the local server (including a
|
|
262
|
+
regression test for the path-traversal fix), and an end-to-end analyze
|
|
263
|
+
pass — including a regression test that secrets in scanned `.env` files
|
|
264
|
+
never leak into an exported graph. CI (`.github/workflows/ci.yml`) runs it
|
|
265
|
+
on every push/PR across the minimum and latest supported Python versions,
|
|
266
|
+
and the PyPI release workflow won't publish if it fails.
|
|
267
|
+
|
|
223
268
|
## Roadmap
|
|
224
269
|
|
|
225
270
|
Already done as of v1.0:
|
|
@@ -227,13 +272,13 @@ Already done as of v1.0:
|
|
|
227
272
|
- [x] Orphaned-function and circular-dependency detection
|
|
228
273
|
- [x] Diff mode between two scans
|
|
229
274
|
- [x] Focus mode, orbit layout, right-click actions, minimap, breadcrumb
|
|
275
|
+
- [x] Automated test suite + CI
|
|
230
276
|
|
|
231
277
|
Not yet, but planned:
|
|
232
278
|
|
|
233
279
|
- [ ] "Explain this function" — a plain-language AI summary button
|
|
234
280
|
- [ ] More precise multi-language parsing (currently structural regex for
|
|
235
281
|
everything but Python)
|
|
236
|
-
- [ ] An automated test suite
|
|
237
282
|
|
|
238
283
|
Have an idea? Open an issue.
|
|
239
284
|
|
|
@@ -247,8 +292,12 @@ but contributions are genuinely welcome:
|
|
|
247
292
|
or UI polish. Keep the zero-dependency philosophy: the core tool should
|
|
248
293
|
keep running on nothing but the Python standard library, and the UI
|
|
249
294
|
should keep running on vanilla JS with no build step.
|
|
250
|
-
-
|
|
251
|
-
|
|
295
|
+
- Run `pytest -q` before opening a PR (see [Testing](#testing) above); add
|
|
296
|
+
a fixture + test case under `tests/` for new parser behavior where you
|
|
297
|
+
can.
|
|
298
|
+
|
|
299
|
+
Security issue instead? See [SECURITY.md](SECURITY.md) rather than a
|
|
300
|
+
public issue.
|
|
252
301
|
|
|
253
302
|
## License
|
|
254
303
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
codebread/__init__.py,sha256=
|
|
1
|
+
codebread/__init__.py,sha256=mwaD5s8k8q32ofSXrl6_tMjBJk0U1U2lrxwP3PPNtCs,1215
|
|
2
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
|
|
@@ -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=Uh7L2n0pumpt1UNSlC1mvcjg6yBl1LAxTmbz9cmsilA,4597
|
|
12
12
|
codebread/parsers/__init__.py,sha256=ovsvmXGClshiUlDdx6mkBmRu8__OZIqYos6-1eLb9eM,2285
|
|
13
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.4.dist-info/licenses/LICENSE,sha256=okINupIvMgiuOlvMVpsM9DT5jb6QQmKBd--yfZq4bjc,1070
|
|
20
|
+
codebread-1.0.4.dist-info/METADATA,sha256=KY847kSXgZNds5HE-a9UJdGXazfY1nhrWW661_1lIjg,14262
|
|
21
|
+
codebread-1.0.4.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
22
|
+
codebread-1.0.4.dist-info/entry_points.txt,sha256=ItG61aUP1YTvA_zA-jlUshBc5BPpJDpCyVnsyaxNXKA,49
|
|
23
|
+
codebread-1.0.4.dist-info/top_level.txt,sha256=KctFB7UQb4tN9YotaJD95GuHWIQuoO5VqyLdo_gEswQ,10
|
|
24
|
+
codebread-1.0.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|