pymap-cli 0.3.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.
@@ -0,0 +1,42 @@
1
+ <!doctype html>
2
+ <meta charset="utf-8">
3
+ <title>Map of __PYMAP_NAME__</title>
4
+ <style>
5
+ :root {
6
+ --ink:#12171c; --paper:#f7f6f3; --rule:#c9c4ba;
7
+ --accent:#1a5e63; --alert:#9c3b28; --muted:#6a6459;
8
+ }
9
+ * { box-sizing:border-box }
10
+ body {
11
+ margin:0; background:var(--paper); color:var(--ink);
12
+ font:15px/1.6 "SF Mono",ui-monospace,"JetBrains Mono",Menlo,monospace;
13
+ }
14
+ header { padding:44px 32px 28px; border-bottom:2px solid var(--ink) }
15
+ h1 { margin:0; font-size:30px; font-weight:600; letter-spacing:-.02em }
16
+ .sub { color:var(--muted); margin-top:6px }
17
+ main { padding:0 32px 64px }
18
+ section { border-bottom:1px solid var(--rule); padding:28px 0 }
19
+ h2 { font-size:17px; margin:0 0 4px; font-weight:600 }
20
+ .what { color:var(--muted); margin:0 0 14px; max-width:62ch }
21
+ .stats { display:flex; gap:26px; margin:14px 0 18px; flex-wrap:wrap }
22
+ .stat b { display:block; font-size:26px; font-weight:600; color:var(--accent) }
23
+ .stat span { color:var(--muted); font-size:13px }
24
+ a.view {
25
+ display:inline-block; padding:9px 15px; border:1.5px solid var(--ink);
26
+ color:var(--ink); text-decoration:none; margin:0 8px 8px 0;
27
+ }
28
+ a.view:hover { background:var(--ink); color:var(--paper) }
29
+ a.view:focus-visible { outline:3px solid var(--accent); outline-offset:2px }
30
+ .absent { color:var(--muted); font-style:italic }
31
+ .cycles { border-left:4px solid var(--alert); padding:12px 16px; background:#fff }
32
+ .cycles b { color:var(--alert) }
33
+ code { background:#ebe8e2; padding:1px 5px }
34
+ ul { margin:8px 0; padding-left:20px }
35
+ </style>
36
+ <header>
37
+ <h1>__PYMAP_NAME__</h1>
38
+ <div class="sub">__PYMAP_FILES__ Python files · __PYMAP_LINES__ lines · analysed locally, nothing is sent anywhere</div>
39
+ </header>
40
+ <main>
41
+ __PYMAP_BODY__
42
+ </main>
@@ -0,0 +1,7 @@
1
+ <section>
2
+ <h2>__PYMAP_TITLE__</h2>
3
+ <p class="what">__PYMAP_WHAT__</p>
4
+ __PYMAP_STATS__
5
+ __PYMAP_DETAIL__
6
+ __PYMAP_LINKS__
7
+ </section>
@@ -0,0 +1,22 @@
1
+ """Wrappers around external tools: one module per tool.
2
+
3
+ Each module exposes ``execute(settings)`` and returns raw facts, never HTML --
4
+ layout belongs to :mod:`pymap.render`. A missing tool returns an empty result
5
+ carrying an explanatory ``log``; it never aborts the run.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from pathlib import Path
12
+
13
+
14
+ def count_svg(path: str | Path) -> tuple[int, int]:
15
+ """(nodes, edges) of an SVG produced by Graphviz."""
16
+ try:
17
+ text = Path(path).read_text(encoding="utf-8", errors="ignore")
18
+ except OSError:
19
+ return 0, 0
20
+ titles = re.findall(r"<title>([^<]+)</title>", text)
21
+ edges = [t for t in titles if "&#45;&gt;" in t or "->" in t]
22
+ return len(titles) - len(edges), len(edges)
@@ -0,0 +1,56 @@
1
+ """code2flow -- call graph between functions.
2
+
3
+ Only the JSON is produced: pymap's own explorer supersedes code2flow's SVG, but
4
+ it feeds on this graph.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from typing import Any
11
+
12
+ from pymap import runner
13
+ from pymap.settings import Settings
14
+
15
+ ROLE = "call graph"
16
+
17
+ #: Past this point the file list would exceed the maximum command-line length,
18
+ #: so we fall back to the directory (and therefore to code2flow's own walk).
19
+ MAX_ARGUMENTS = 3000
20
+
21
+ #: code2flow is slow on large codebases; it needs more than the shared timeout.
22
+ MIN_TIMEOUT = 420
23
+
24
+
25
+ def execute(settings: Settings) -> dict[str, Any]:
26
+ """Write ``calls.json``. Returns ``{json, functions, calls, log}``."""
27
+ empty: dict[str, Any] = {"json": None, "functions": 0, "calls": 0}
28
+ base = runner.resolve("code2flow")
29
+ if not base:
30
+ return dict(empty, log="code2flow is not installed")
31
+
32
+ files = settings.files()
33
+ if not files:
34
+ return dict(empty, log="no Python file to analyse")
35
+ # Pass the filtered list rather than the directory: code2flow does not know
36
+ # about our exclusions and would walk into .venv/ if pointed at the root.
37
+ sources = (
38
+ [str(path) for path in files] if len(files) <= MAX_ARGUMENTS else [str(settings.target)]
39
+ )
40
+
41
+ output = settings.output / "calls.json"
42
+ ok, log = runner.run(
43
+ base + sources + ["-o", str(output)], timeout=max(settings.timeout, MIN_TIMEOUT)
44
+ )
45
+ if not (ok and output.exists()):
46
+ return dict(empty, log=log or "code2flow failed")
47
+ try:
48
+ graph = json.loads(output.read_text(encoding="utf-8"))["graph"]
49
+ except (OSError, ValueError, KeyError):
50
+ return dict(empty, json=output, log="unreadable code2flow JSON")
51
+ return {
52
+ "json": output,
53
+ "functions": len(graph["nodes"]),
54
+ "calls": len(graph["edges"]),
55
+ "log": "",
56
+ }
pymap/tools/pydeps.py ADDED
@@ -0,0 +1,42 @@
1
+ """pydeps -- who imports whom, at module level."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pymap import runner
8
+ from pymap.settings import Settings
9
+ from pymap.tools import count_svg
10
+
11
+ ROLE = "import graph"
12
+
13
+
14
+ def execute(settings: Settings) -> dict[str, Any]:
15
+ """Write ``imports.svg``. Returns ``{svg, modules, imports, log}``."""
16
+ empty: dict[str, Any] = {"svg": None, "modules": 0, "imports": 0}
17
+ base = runner.resolve("pydeps")
18
+ if not base:
19
+ return dict(empty, log="pydeps is not installed")
20
+ if not runner.resolve("dot"):
21
+ return dict(empty, log="graphviz (dot) missing: pydeps cannot render the SVG")
22
+
23
+ svg = settings.output / "imports.svg"
24
+ ok, log = runner.run(
25
+ [
26
+ *base,
27
+ str(settings.target),
28
+ "--noshow",
29
+ "--cluster",
30
+ "--max-bacon",
31
+ "2",
32
+ "-T",
33
+ "svg",
34
+ "-o",
35
+ str(svg),
36
+ ],
37
+ timeout=settings.timeout,
38
+ )
39
+ if not (ok and svg.exists()):
40
+ return dict(empty, log=log or "pydeps failed")
41
+ nodes, edges = count_svg(svg)
42
+ return {"svg": svg, "modules": nodes, "imports": edges, "log": ""}
@@ -0,0 +1,41 @@
1
+ """pyreverse (from pylint) -- class and package diagrams."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ from pymap import runner
9
+ from pymap.settings import Settings
10
+
11
+ ROLE = "class diagrams"
12
+
13
+
14
+ def execute(settings: Settings) -> dict[str, Any]:
15
+ """Write ``classes_<name>.svg`` and ``packages_<name>.svg``.
16
+
17
+ Returns ``{classes, packages, log}``.
18
+ """
19
+ empty: dict[str, Any] = {"classes": None, "packages": None}
20
+ base = runner.resolve("pyreverse")
21
+ if not base:
22
+ return dict(empty, log="pyreverse is not installed (ships with pylint)")
23
+ if not runner.resolve("dot"):
24
+ return dict(empty, log="graphviz (dot) missing: pyreverse cannot render the SVG")
25
+
26
+ project = re.sub(r"[^A-Za-z0-9_.-]", "_", settings.name) or "project"
27
+ ok, log = runner.run(
28
+ [*base, "-o", "svg", "-p", project, "-d", str(settings.output), str(settings.target)],
29
+ cwd=str(settings.target.parent),
30
+ timeout=settings.timeout,
31
+ )
32
+
33
+ classes = settings.output / f"classes_{project}.svg"
34
+ packages = settings.output / f"packages_{project}.svg"
35
+ if not ok and not classes.exists():
36
+ return dict(empty, log=log or "pyreverse failed")
37
+ return {
38
+ "classes": classes if classes.exists() else None,
39
+ "packages": packages if packages.exists() else None,
40
+ "log": "",
41
+ }
pymap/tools/tach.py ADDED
@@ -0,0 +1,104 @@
1
+ """tach -- module boundaries and file-by-file dependencies.
2
+
3
+ tach requires a ``tach.toml`` at the root of wherever it runs. Rather than drop
4
+ that file into the analysed project -- and risk clobbering the user's own --
5
+ pymap copies the ``.py`` sources into a temporary directory and works there.
6
+ The target project is never modified.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import shutil
13
+ import tempfile
14
+ from collections.abc import Iterable, Sequence
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from pymap import runner
19
+ from pymap.settings import Settings, is_excluded
20
+
21
+ ROLE = "module boundaries"
22
+
23
+ #: Past this point ``tach sync`` gets very slow, for a graph already unreadable.
24
+ MAX_MODULES = 60
25
+
26
+
27
+ def _copy_sources(target: Path, into: Path, excluded: Sequence[str]) -> None:
28
+ """Copy the tree keeping only ``.py`` files, excluded branches pruned."""
29
+
30
+ def ignore(directory: str, names: list[str]) -> set[str]:
31
+ rejected = set()
32
+ for name in names:
33
+ if (Path(directory) / name).is_dir():
34
+ if is_excluded(name, excluded):
35
+ rejected.add(name)
36
+ elif not name.endswith(".py"):
37
+ rejected.add(name)
38
+ return rejected
39
+
40
+ shutil.copytree(target, into, ignore=ignore, symlinks=True)
41
+
42
+
43
+ def _module_paths(target: Path, files: Iterable[Path]) -> list[str]:
44
+ """Dotted module paths, relative to the source root."""
45
+ names = set()
46
+ for path in files:
47
+ if path.name == "__init__.py":
48
+ continue
49
+ relative = path.relative_to(target).with_suffix("").as_posix().replace("/", ".")
50
+ names.add(f"{target.name}.{relative}")
51
+ return sorted(names)
52
+
53
+
54
+ def execute(settings: Settings) -> dict[str, Any]:
55
+ """Write ``tach.mmd``. Returns ``{mermaid, deps, modules, truncated, log}``."""
56
+ empty: dict[str, Any] = {"mermaid": None, "deps": {}, "modules": 0, "truncated": 0}
57
+ base = runner.resolve("tach")
58
+ if not base:
59
+ return dict(empty, log="tach is not installed")
60
+
61
+ every = _module_paths(settings.target, settings.files())
62
+ if not every:
63
+ return dict(empty, log="no module to declare")
64
+ kept = every[:MAX_MODULES]
65
+
66
+ with tempfile.TemporaryDirectory(prefix="pymap-tach-") as tmp:
67
+ workspace = Path(tmp)
68
+ try:
69
+ _copy_sources(settings.target, workspace / settings.target.name, settings.excluded)
70
+ except OSError as exc:
71
+ return dict(empty, log=f"could not copy sources: {exc}")
72
+
73
+ lines = ['source_roots = ["."]\n']
74
+ lines += [f'[[modules]]\npath = "{m}"\ndepends_on = []\n\n' for m in kept]
75
+ (workspace / "tach.toml").write_text("".join(lines), encoding="utf-8")
76
+
77
+ ok, log = runner.run([*base, "sync"], cwd=str(workspace), timeout=settings.timeout)
78
+ if not ok:
79
+ return dict(empty, log=log or "tach sync failed")
80
+
81
+ mermaid = settings.output / "tach.mmd"
82
+ drawn, _ = runner.run(
83
+ [*base, "show", "--mermaid", "-o", str(mermaid)],
84
+ cwd=str(workspace),
85
+ timeout=settings.timeout,
86
+ )
87
+
88
+ deps: dict[str, list[str]] = {}
89
+ mapped, raw = runner.run(
90
+ [*base, "map", "-o", "-"], cwd=str(workspace), timeout=settings.timeout, full=True
91
+ )
92
+ if mapped:
93
+ try:
94
+ deps = json.loads(raw[raw.index("{") : raw.rindex("}") + 1])
95
+ except (ValueError, json.JSONDecodeError):
96
+ deps = {}
97
+
98
+ return {
99
+ "mermaid": mermaid if (drawn and mermaid.exists()) else None,
100
+ "deps": deps,
101
+ "modules": len(kept),
102
+ "truncated": len(every) - len(kept),
103
+ "log": "",
104
+ }
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.5
2
+ Name: pymap-cli
3
+ Version: 0.3.0
4
+ Summary: Map a Python codebase in one command.
5
+ Project-URL: Homepage, https://github.com/hermann225-zrouama/pymap-cli
6
+ Project-URL: Issues, https://github.com/hermann225-zrouama/pymap-cli/issues
7
+ Project-URL: Changelog, https://github.com/hermann225-zrouama/pymap-cli/blob/main/CHANGELOG.md
8
+ Author: Franck Zrouama
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: architecture,ast,call graph,codebase map,documentation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Documentation
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Requires-Python: >=3.9
24
+ Provides-Extra: dev
25
+ Requires-Dist: code2flow>=2.5; extra == 'dev'
26
+ Requires-Dist: pydeps>=1.11; extra == 'dev'
27
+ Requires-Dist: pylint>=2.15; extra == 'dev'
28
+ Requires-Dist: pytest>=7; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Requires-Dist: tach>=0.9; extra == 'dev'
31
+ Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'dev'
32
+ Provides-Extra: graphs
33
+ Requires-Dist: code2flow>=2.5; extra == 'graphs'
34
+ Requires-Dist: pydeps>=1.11; extra == 'graphs'
35
+ Requires-Dist: pylint>=2.15; extra == 'graphs'
36
+ Requires-Dist: tach>=0.9; extra == 'graphs'
37
+ Provides-Extra: toml
38
+ Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'toml'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # pymap
42
+
43
+ Map a Python codebase in one command. No service, no upload: everything is
44
+ analysed and rendered locally.
45
+
46
+ ```bash
47
+ cd my-project
48
+ pymap --open
49
+ ```
50
+
51
+ pymap guesses which package to analyse, runs whichever graph tools you have
52
+ installed, and assembles the result into a single page — plus a **flow
53
+ explorer** that pymap builds itself: for every function, the order of the calls,
54
+ the conditions, the loops, the error paths, and the data flowing through.
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pipx install pymap-cli # isolated, available everywhere
60
+ # or, inside the project you want to map:
61
+ pip install pymap-cli
62
+ ```
63
+
64
+ The core depends only on the standard library, so installing pymap into a
65
+ project adds **no version constraint** to it. The graph tools are optional and
66
+ live behind an extra:
67
+
68
+ ```bash
69
+ pip install "pymap-cli[graphs]" # pydeps, code2flow, pylint, tach
70
+ brew install graphviz # or: apt install graphviz
71
+ ```
72
+
73
+ Any missing tool is reported at start-up and then skipped; the explorer always
74
+ works.
75
+
76
+ ## Usage
77
+
78
+ ```bash
79
+ pymap # guess the current project's package
80
+ pymap src/mypkg # explicit target
81
+ pymap src/mypkg -o map/ --open # output directory, open when done
82
+ pymap --exclude "generated_*" # skip more paths
83
+ pymap --editor pycharm # code links for another editor
84
+ ```
85
+
86
+ How the target is guessed, in order: `[tool.pymap] target` in `pyproject.toml`,
87
+ a `src/<package>/` layout, the package named after the project, then the single
88
+ top-level package. If nothing stands out, pymap says so and waits for a path.
89
+
90
+ ### Configuration
91
+
92
+ Optional, in the project's `pyproject.toml` (read on Python ≥ 3.11, or with
93
+ `tomli` installed):
94
+
95
+ ```toml
96
+ [tool.pymap]
97
+ target = "src/mypkg"
98
+ output = "pymap-out"
99
+ editor = "vscode" # vscodium, cursor, windsurf, zed, pycharm,
100
+ # idea, sublime, none, or "myeditor://{f}:{l}"
101
+ exclude = ["vendor", "generated_*"]
102
+ timeout = 300 # seconds per external tool
103
+ ```
104
+
105
+ ### As a library
106
+
107
+ ```python
108
+ from pymap import Settings, map_codebase
109
+
110
+ report = map_codebase(Settings(target="src/mypkg", output="map/"))
111
+ print(report.coverage, "% documented,", len(report.cycles), "cycles")
112
+ ```
113
+
114
+ ## What the map contains
115
+
116
+ | View | Source | What you read there |
117
+ |---|---|---|
118
+ | **Walkthrough tree** | pymap (AST) | The execution flow function by function, keyboard-navigable |
119
+ | Imports between modules | pydeps | The layers, and the modules everyone pulls in |
120
+ | Classes and inheritance | pyreverse | Attributes and hierarchies, when the code is object-oriented |
121
+ | Module boundaries | tach | File-by-file dependencies, cycles, Mermaid source |
122
+
123
+ The output directory (`pymap-out/` by default) holds `index.html` — the page to
124
+ open — and the views it links to.
125
+
126
+ ### In the explorer
127
+
128
+ <kbd>↓</kbd> <kbd>↑</kbd> walk the steps · <kbd>↵</kbd> step into the
129
+ highlighted call · <kbd>←</kbd> go back up · <kbd>m</kbd> mark seen ·
130
+ <kbd>o</kbd> open in the editor · <kbd>/</kbd> search
131
+
132
+ ## How the code is organised
133
+
134
+ ```
135
+ src/pymap/
136
+ ├── cli.py command line: arguments, pyproject, messages
137
+ ├── settings.py what to analyse, where to write, what to skip
138
+ ├── mapper.py orchestration + library API
139
+ ├── runner.py launching external tools, detecting missing ones
140
+ ├── render.py templates → HTML pages
141
+ ├── analysis/ what pymap works out on its own, from the AST
142
+ │ ├── symbols.py modules, classes, functions, signatures
143
+ │ ├── flow.py execution tree of a function
144
+ │ ├── calls.py code2flow's call graph
145
+ │ └── cycles.py circular dependencies
146
+ ├── tools/ one module per external tool
147
+ │ ├── pydeps.py pyreverse.py code2flow.py tach.py
148
+ └── templates/ index.html, section.html, explorer.html
149
+ ```
150
+
151
+ Adding a tool: drop a module in `tools/` exposing `execute(settings)` that
152
+ returns raw facts (never HTML), then add one line to `STEPS` and one section to
153
+ `_sections()`, both in `mapper.py`. See [CONTRIBUTING.md](CONTRIBUTING.md).
154
+
155
+ ## Guarantees
156
+
157
+ - **Nothing is executed** from the analysed code: everything goes through the
158
+ standard library's AST.
159
+ - **Nothing is written** into the target project. `tach` requires a `tach.toml`
160
+ at the root of wherever it runs, so pymap copies the sources into a temporary
161
+ directory instead of dropping that file in your tree.
162
+ - Environments and caches (`.venv/`, `node_modules/`, `build/`,
163
+ `__pycache__/`, …) are pruned during the walk, never traversed.
164
+
165
+ ## Known limitations
166
+
167
+ - **Files sharing a basename share a namespace.** Symbol keys are
168
+ `<file stem>::<qualified name>`, because that is how code2flow names its
169
+ graph nodes and it is what lets the two data sets be joined. In a project
170
+ with `app/models.py` and `blog/models.py`, their symbols merge in the
171
+ explorer. pymap prints a note when it detects the case.
172
+ - **tach declares at most 60 modules.** Beyond that `tach sync` gets very slow
173
+ for a graph that is already unreadable. The count left out is reported.
174
+ - The call graph is only as good as code2flow's static resolution: calls
175
+ through dynamic dispatch or `getattr` do not appear.
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ git clone https://github.com/hermann225-zrouama/pymap-cli
181
+ cd pymap-cli
182
+ pip install -e ".[dev]"
183
+ pytest
184
+ ruff check src tests && ruff format --check src tests
185
+ pymap # pymap maps itself
186
+ ```
187
+
188
+ `tests/test_contract.py` pins the key names shared between the Python payload
189
+ and `templates/explorer.html`. A rename on one side without the other produces
190
+ a blank page rather than an error, so that test is what keeps them honest.
191
+
192
+ ## Licence
193
+
194
+ MIT — see [LICENSE](LICENSE).
195
+
196
+ The distribution is named `pymap-cli` because `pymap` is already taken on PyPI
197
+ by an IMAP library. The command and the import name are both `pymap`.
@@ -0,0 +1,27 @@
1
+ pymap/__init__.py,sha256=C1jcrwBVVCpCK6fNFLdbZohI0L99BMVgA5JWDN_w6YQ,1223
2
+ pymap/__main__.py,sha256=LnOY_l14VeZ9NVefVstic4CxBNVaq9H4dUdgz4S3bHk,169
3
+ pymap/cli.py,sha256=O8rGgY8MC_8jYFeuSl4n6qAP7uJjAcnsR-GG2y7k7l0,4718
4
+ pymap/mapper.py,sha256=bFcdsImrGbxSNPzb5vjVVBnngbjgcATMGLsvpcIuDqc,7229
5
+ pymap/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ pymap/render.py,sha256=ctnR5yEtiHkc-Aj0L1MNlf_rM4HIEWZg7q9k5sez9gY,5249
7
+ pymap/runner.py,sha256=TB-8WDQuw6Q2IFqxUhxG0etkswWCOtmxnO-X_Kb3HbA,3302
8
+ pymap/settings.py,sha256=P_cML1kHCBgaGRMaaB89CHUfaC5tdzlo9aya7tUV8uQ,6025
9
+ pymap/analysis/__init__.py,sha256=sQkLkcimHcjMabQoBq74N2p7jl4suuVRUbmMMk-4Egk,80
10
+ pymap/analysis/calls.py,sha256=K_vC7v4e0Cgb_1Xo1kDmlgxpk9omBsJFbk9wfeKHinU,1319
11
+ pymap/analysis/cycles.py,sha256=YuKIYs2LHRnkEt46AnzlzDq3NApf8FONT76LcVyggtE,1342
12
+ pymap/analysis/flow.py,sha256=o__gpjM7qmipfeMa7q5a0eYWiwYKR-dOiEIgA1pqRR0,6453
13
+ pymap/analysis/symbols.py,sha256=wOqHGDHmGuXWy77KQWqDG74HJwgnt76uqUZY1rd3fM4,6884
14
+ pymap/templates/__init__.py,sha256=u1GeOJbGJC3VdLp1WiZN-Xwu52tNK2RElXJ8e0j230A,437
15
+ pymap/templates/explorer.html,sha256=XJLJh7bh-0ucu2ZlY55yDLaHkby7g0ReJJCkSdbRYjQ,37734
16
+ pymap/templates/index.html,sha256=tdtChG7Oipp7S5KWjaebMcpftkNgloTp9rhIcYkgxZo,1760
17
+ pymap/templates/section.html,sha256=bFB1E2mvl89XuzRPZO3K0oNEi8M_V4VPsre9XxEubkU,140
18
+ pymap/tools/__init__.py,sha256=a7_ToNuCq6Rc08Vi1-v919XQPghdfbt1a8OHcJ-jjYc,749
19
+ pymap/tools/code2flow.py,sha256=XxjZRCxaAK38FVKGM0DOTU1Dds4JTiw8G67nmtmWiGo,1889
20
+ pymap/tools/pydeps.py,sha256=NLq1ogy0jkAyhY7hNDUwifkH5ck5i3Q31LfwMkgeQog,1207
21
+ pymap/tools/pyreverse.py,sha256=aOoNnL7E3JC31UA6qbT1mnR4OaFObExHHljKbGjp3_0,1361
22
+ pymap/tools/tach.py,sha256=JknllG_rWIGr-FjPNGDsG7gwmWGyvrdZgX2tVmsssxc,3669
23
+ pymap_cli-0.3.0.dist-info/METADATA,sha256=sBo9Zim92TSOtpDzV3Vz7sGSG5Vs8vip2KAI6TxYF_E,7674
24
+ pymap_cli-0.3.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
25
+ pymap_cli-0.3.0.dist-info/entry_points.txt,sha256=fIJReyb8moS3dUNAxWnMTQan-joX10DqqmZs2Esym3k,41
26
+ pymap_cli-0.3.0.dist-info/licenses/LICENSE,sha256=7BJhC6lQ3aGXwDNyJcE9U2MhD9I0krkxJKrSZAX8RlI,1071
27
+ pymap_cli-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pymap = pymap.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Franck Zrouama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.