pymap-cli 0.3.0__tar.gz

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.
Files changed (39) hide show
  1. pymap_cli-0.3.0/.gitignore +26 -0
  2. pymap_cli-0.3.0/CHANGELOG.md +72 -0
  3. pymap_cli-0.3.0/LICENSE +21 -0
  4. pymap_cli-0.3.0/PKG-INFO +197 -0
  5. pymap_cli-0.3.0/README.md +157 -0
  6. pymap_cli-0.3.0/pyproject.toml +97 -0
  7. pymap_cli-0.3.0/src/pymap/__init__.py +37 -0
  8. pymap_cli-0.3.0/src/pymap/__main__.py +8 -0
  9. pymap_cli-0.3.0/src/pymap/analysis/__init__.py +1 -0
  10. pymap_cli-0.3.0/src/pymap/analysis/calls.py +36 -0
  11. pymap_cli-0.3.0/src/pymap/analysis/cycles.py +43 -0
  12. pymap_cli-0.3.0/src/pymap/analysis/flow.py +175 -0
  13. pymap_cli-0.3.0/src/pymap/analysis/symbols.py +175 -0
  14. pymap_cli-0.3.0/src/pymap/cli.py +143 -0
  15. pymap_cli-0.3.0/src/pymap/mapper.py +198 -0
  16. pymap_cli-0.3.0/src/pymap/py.typed +0 -0
  17. pymap_cli-0.3.0/src/pymap/render.py +170 -0
  18. pymap_cli-0.3.0/src/pymap/runner.py +93 -0
  19. pymap_cli-0.3.0/src/pymap/settings.py +176 -0
  20. pymap_cli-0.3.0/src/pymap/templates/__init__.py +8 -0
  21. pymap_cli-0.3.0/src/pymap/templates/explorer.html +822 -0
  22. pymap_cli-0.3.0/src/pymap/templates/index.html +42 -0
  23. pymap_cli-0.3.0/src/pymap/templates/section.html +7 -0
  24. pymap_cli-0.3.0/src/pymap/tools/__init__.py +22 -0
  25. pymap_cli-0.3.0/src/pymap/tools/code2flow.py +56 -0
  26. pymap_cli-0.3.0/src/pymap/tools/pydeps.py +42 -0
  27. pymap_cli-0.3.0/src/pymap/tools/pyreverse.py +41 -0
  28. pymap_cli-0.3.0/src/pymap/tools/tach.py +104 -0
  29. pymap_cli-0.3.0/tests/conftest.py +23 -0
  30. pymap_cli-0.3.0/tests/sample/__init__.py +1 -0
  31. pymap_cli-0.3.0/tests/sample/engine.py +34 -0
  32. pymap_cli-0.3.0/tests/sample/entry.py +9 -0
  33. pymap_cli-0.3.0/tests/test_analysis.py +194 -0
  34. pymap_cli-0.3.0/tests/test_cli.py +82 -0
  35. pymap_cli-0.3.0/tests/test_contract.py +109 -0
  36. pymap_cli-0.3.0/tests/test_mapper.py +99 -0
  37. pymap_cli-0.3.0/tests/test_render.py +142 -0
  38. pymap_cli-0.3.0/tests/test_settings.py +88 -0
  39. pymap_cli-0.3.0/tests/test_tools.py +91 -0
@@ -0,0 +1,26 @@
1
+ # pymap output
2
+ pymap-out/
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+
12
+ # environments and caches
13
+ .venv/
14
+ venv/
15
+ .tox/
16
+ .nox/
17
+ .mypy_cache/
18
+ .pytest_cache/
19
+ .ruff_cache/
20
+
21
+ # editor local state
22
+ .idea/
23
+ .vscode/
24
+
25
+ # OS
26
+ .DS_Store
@@ -0,0 +1,72 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.3.0] — 2026-09-04
8
+
9
+ First release prepared for publication. The codebase, its comments and the
10
+ generated interface are now entirely in English.
11
+
12
+ ### Added
13
+ - `tests/test_contract.py`, pinning the key names shared between the Python
14
+ payload and `templates/explorer.html`, and running `node --check` on the
15
+ generated script when Node is available.
16
+ - A notice when several files share a basename, since their symbols merge into
17
+ one namespace in the explorer.
18
+ - `ruff` configuration (lint + format), type annotations across the public API,
19
+ and a `py.typed` marker.
20
+ - `LICENSE` (MIT), `CONTRIBUTING.md`, this changelog.
21
+
22
+ ### Fixed
23
+ - A docstring containing `</script>` closed the `<script>` block early and
24
+ blanked the whole explorer page. Payloads now go through `script_json()`,
25
+ which escapes `<`, `>`, `&` and the U+2028/U+2029 line separators.
26
+ - Keyboard navigation of the flow (<kbd>↓</kbd> <kbd>↑</kbd> <kbd>↵</kbd>) was
27
+ advertised in the help panel but did nothing: the step list was never filled.
28
+ - Marking a symbol as seen (<kbd>m</kbd>) updated the progress bar but left the
29
+ tree unchanged; seen symbols are now dimmed and ticked.
30
+
31
+ ### Changed
32
+ - Renamed for English: `Reglages` → `Settings`, `cartographier` →
33
+ `map_codebase`, `Rapport` → `Report`, and the modules `reglages`/`processus`/
34
+ `carte`/`rendu`/`analyse`/`outils`/`gabarits` → `settings`/`runner`/`mapper`/
35
+ `render`/`analysis`/`tools`/`templates`.
36
+ - The call-graph JSON is now written as `calls.json` (was `appels.json`), and
37
+ the explorer page as `explorer.html` (was `explorateur.html`).
38
+
39
+ ## [0.2.0] — 2026-09-04
40
+
41
+ Restructured from a single 1,339-line script into an installable package.
42
+
43
+ ### Added
44
+ - `pyproject.toml`, a `src/` layout and a `pymap` console script, so pymap can
45
+ be installed into any Python project.
46
+ - Zero-argument invocation: the target package is detected from
47
+ `[tool.pymap] target`, a `src/` layout, or the project name.
48
+ - Configuration through `[tool.pymap]` in the target's `pyproject.toml`.
49
+ - `--exclude`, `--editor` and `--timeout` options; code links support eight
50
+ editors or a custom URI template.
51
+ - Exclusion of environments and caches (`.venv/`, `node_modules/`, `build/`, …),
52
+ pruned during the walk rather than after it.
53
+ - A test suite covering the degraded path with no external tool installed.
54
+
55
+ ### Fixed
56
+ - `pyreverse` was declared among the required tools and advertised in the
57
+ documentation, but no step ever ran it. It now produces the class and package
58
+ diagrams.
59
+ - `tach` wrote a `tach.toml` two directories above the target, saving and
60
+ restoring any existing one — a crash in between destroyed the user's file.
61
+ The sources are now copied into a temporary directory.
62
+ - External tools were not found when pymap ran from a virtualenv whose `bin/`
63
+ was not on `PATH`, and the `python -m` fallback failed on packages without a
64
+ `__main__` module.
65
+
66
+ ### Changed
67
+ - The HTML templates moved out of the Python source into real `.html` files,
68
+ substituted through `__PYMAP_X__` tokens instead of `str.format`.
69
+
70
+ ## [0.1.0]
71
+
72
+ Initial single-file version.
@@ -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.
@@ -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,157 @@
1
+ # pymap
2
+
3
+ Map a Python codebase in one command. No service, no upload: everything is
4
+ analysed and rendered locally.
5
+
6
+ ```bash
7
+ cd my-project
8
+ pymap --open
9
+ ```
10
+
11
+ pymap guesses which package to analyse, runs whichever graph tools you have
12
+ installed, and assembles the result into a single page — plus a **flow
13
+ explorer** that pymap builds itself: for every function, the order of the calls,
14
+ the conditions, the loops, the error paths, and the data flowing through.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pipx install pymap-cli # isolated, available everywhere
20
+ # or, inside the project you want to map:
21
+ pip install pymap-cli
22
+ ```
23
+
24
+ The core depends only on the standard library, so installing pymap into a
25
+ project adds **no version constraint** to it. The graph tools are optional and
26
+ live behind an extra:
27
+
28
+ ```bash
29
+ pip install "pymap-cli[graphs]" # pydeps, code2flow, pylint, tach
30
+ brew install graphviz # or: apt install graphviz
31
+ ```
32
+
33
+ Any missing tool is reported at start-up and then skipped; the explorer always
34
+ works.
35
+
36
+ ## Usage
37
+
38
+ ```bash
39
+ pymap # guess the current project's package
40
+ pymap src/mypkg # explicit target
41
+ pymap src/mypkg -o map/ --open # output directory, open when done
42
+ pymap --exclude "generated_*" # skip more paths
43
+ pymap --editor pycharm # code links for another editor
44
+ ```
45
+
46
+ How the target is guessed, in order: `[tool.pymap] target` in `pyproject.toml`,
47
+ a `src/<package>/` layout, the package named after the project, then the single
48
+ top-level package. If nothing stands out, pymap says so and waits for a path.
49
+
50
+ ### Configuration
51
+
52
+ Optional, in the project's `pyproject.toml` (read on Python ≥ 3.11, or with
53
+ `tomli` installed):
54
+
55
+ ```toml
56
+ [tool.pymap]
57
+ target = "src/mypkg"
58
+ output = "pymap-out"
59
+ editor = "vscode" # vscodium, cursor, windsurf, zed, pycharm,
60
+ # idea, sublime, none, or "myeditor://{f}:{l}"
61
+ exclude = ["vendor", "generated_*"]
62
+ timeout = 300 # seconds per external tool
63
+ ```
64
+
65
+ ### As a library
66
+
67
+ ```python
68
+ from pymap import Settings, map_codebase
69
+
70
+ report = map_codebase(Settings(target="src/mypkg", output="map/"))
71
+ print(report.coverage, "% documented,", len(report.cycles), "cycles")
72
+ ```
73
+
74
+ ## What the map contains
75
+
76
+ | View | Source | What you read there |
77
+ |---|---|---|
78
+ | **Walkthrough tree** | pymap (AST) | The execution flow function by function, keyboard-navigable |
79
+ | Imports between modules | pydeps | The layers, and the modules everyone pulls in |
80
+ | Classes and inheritance | pyreverse | Attributes and hierarchies, when the code is object-oriented |
81
+ | Module boundaries | tach | File-by-file dependencies, cycles, Mermaid source |
82
+
83
+ The output directory (`pymap-out/` by default) holds `index.html` — the page to
84
+ open — and the views it links to.
85
+
86
+ ### In the explorer
87
+
88
+ <kbd>↓</kbd> <kbd>↑</kbd> walk the steps · <kbd>↵</kbd> step into the
89
+ highlighted call · <kbd>←</kbd> go back up · <kbd>m</kbd> mark seen ·
90
+ <kbd>o</kbd> open in the editor · <kbd>/</kbd> search
91
+
92
+ ## How the code is organised
93
+
94
+ ```
95
+ src/pymap/
96
+ ├── cli.py command line: arguments, pyproject, messages
97
+ ├── settings.py what to analyse, where to write, what to skip
98
+ ├── mapper.py orchestration + library API
99
+ ├── runner.py launching external tools, detecting missing ones
100
+ ├── render.py templates → HTML pages
101
+ ├── analysis/ what pymap works out on its own, from the AST
102
+ │ ├── symbols.py modules, classes, functions, signatures
103
+ │ ├── flow.py execution tree of a function
104
+ │ ├── calls.py code2flow's call graph
105
+ │ └── cycles.py circular dependencies
106
+ ├── tools/ one module per external tool
107
+ │ ├── pydeps.py pyreverse.py code2flow.py tach.py
108
+ └── templates/ index.html, section.html, explorer.html
109
+ ```
110
+
111
+ Adding a tool: drop a module in `tools/` exposing `execute(settings)` that
112
+ returns raw facts (never HTML), then add one line to `STEPS` and one section to
113
+ `_sections()`, both in `mapper.py`. See [CONTRIBUTING.md](CONTRIBUTING.md).
114
+
115
+ ## Guarantees
116
+
117
+ - **Nothing is executed** from the analysed code: everything goes through the
118
+ standard library's AST.
119
+ - **Nothing is written** into the target project. `tach` requires a `tach.toml`
120
+ at the root of wherever it runs, so pymap copies the sources into a temporary
121
+ directory instead of dropping that file in your tree.
122
+ - Environments and caches (`.venv/`, `node_modules/`, `build/`,
123
+ `__pycache__/`, …) are pruned during the walk, never traversed.
124
+
125
+ ## Known limitations
126
+
127
+ - **Files sharing a basename share a namespace.** Symbol keys are
128
+ `<file stem>::<qualified name>`, because that is how code2flow names its
129
+ graph nodes and it is what lets the two data sets be joined. In a project
130
+ with `app/models.py` and `blog/models.py`, their symbols merge in the
131
+ explorer. pymap prints a note when it detects the case.
132
+ - **tach declares at most 60 modules.** Beyond that `tach sync` gets very slow
133
+ for a graph that is already unreadable. The count left out is reported.
134
+ - The call graph is only as good as code2flow's static resolution: calls
135
+ through dynamic dispatch or `getattr` do not appear.
136
+
137
+ ## Development
138
+
139
+ ```bash
140
+ git clone https://github.com/hermann225-zrouama/pymap-cli
141
+ cd pymap-cli
142
+ pip install -e ".[dev]"
143
+ pytest
144
+ ruff check src tests && ruff format --check src tests
145
+ pymap # pymap maps itself
146
+ ```
147
+
148
+ `tests/test_contract.py` pins the key names shared between the Python payload
149
+ and `templates/explorer.html`. A rename on one side without the other produces
150
+ a blank page rather than an error, so that test is what keeps them honest.
151
+
152
+ ## Licence
153
+
154
+ MIT — see [LICENSE](LICENSE).
155
+
156
+ The distribution is named `pymap-cli` because `pymap` is already taken on PyPI
157
+ by an IMAP library. The command and the import name are both `pymap`.
@@ -0,0 +1,97 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ # The import name and the command stay "pymap"; only the distribution name is
7
+ # suffixed, because "pymap" is already taken on PyPI by an IMAP library.
8
+ name = "pymap-cli"
9
+ # Single source of truth: src/pymap/__init__.py. Bumping two files by hand is
10
+ # how a release ends up reporting a version it was not built from.
11
+ dynamic = ["version"]
12
+ description = "Map a Python codebase in one command."
13
+ readme = "README.md"
14
+ requires-python = ">=3.9"
15
+ license = "MIT"
16
+ license-files = ["LICENSE"]
17
+ authors = [{ name = "Franck Zrouama" }]
18
+ keywords = ["codebase map", "documentation", "call graph", "architecture", "ast"]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Environment :: Console",
22
+ "Intended Audience :: Developers",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Topic :: Software Development :: Documentation",
30
+ "Topic :: Software Development :: Quality Assurance",
31
+ ]
32
+ # The core runs on the standard library: installing pymap into a project adds
33
+ # no version constraint to it.
34
+ dependencies = []
35
+
36
+ [project.optional-dependencies]
37
+ # The four external tools pymap knows how to drive. Without them it still
38
+ # produces the flow explorer.
39
+ graphs = ["pydeps>=1.11", "code2flow>=2.5", "pylint>=2.15", "tach>=0.9"]
40
+ toml = ["tomli>=2.0; python_version < '3.11'"]
41
+ dev = ["pytest>=7", "ruff>=0.6", "pymap-cli[graphs,toml]"]
42
+
43
+ [project.scripts]
44
+ pymap = "pymap.cli:main"
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/hermann225-zrouama/pymap-cli"
48
+ Issues = "https://github.com/hermann225-zrouama/pymap-cli/issues"
49
+ Changelog = "https://github.com/hermann225-zrouama/pymap-cli/blob/main/CHANGELOG.md"
50
+
51
+ [tool.hatch.version]
52
+ path = "src/pymap/__init__.py"
53
+
54
+ [tool.hatch.build.targets.wheel]
55
+ packages = ["src/pymap"]
56
+
57
+ [tool.hatch.build.targets.sdist]
58
+ include = ["src/pymap", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
59
+
60
+ # pymap maps itself: run `pymap` with no argument at the repository root.
61
+ [tool.pymap]
62
+ target = "src/pymap"
63
+ output = "pymap-out"
64
+
65
+ [tool.pytest.ini_options]
66
+ testpaths = ["tests"]
67
+ addopts = "-q"
68
+
69
+ [tool.ruff]
70
+ line-length = 96
71
+ target-version = "py39"
72
+ src = ["src", "tests"]
73
+
74
+ [tool.ruff.lint]
75
+ select = [
76
+ "E", "W", # pycodestyle
77
+ "F", # pyflakes
78
+ "I", # import sorting
79
+ "UP", # pyupgrade
80
+ "B", # bugbear
81
+ "C4", # comprehensions
82
+ "SIM", # simplify
83
+ "RUF", # ruff-specific
84
+ ]
85
+ ignore = [
86
+ "E501", # line length is handled by the formatter
87
+ # Long lists of short words (builtin names, ignored directories) are kept as
88
+ # a single split() string on purpose: the literal form expands to one item
89
+ # per line under the formatter, and the suggested fix is a 400-column line
90
+ # that the formatter immediately explodes again.
91
+ "SIM905",
92
+ ]
93
+
94
+ [tool.ruff.lint.per-file-ignores]
95
+ # Tests deliberately reach into private helpers and build odd inputs.
96
+ "tests/*" = ["B011", "SIM117"]
97
+ "tests/sample/*" = ["D", "B", "SIM"]
@@ -0,0 +1,37 @@
1
+ """pymap -- map a Python codebase in one command.
2
+
3
+ It drives the tools that actually work on Python and gathers their output into
4
+ a single page, plus a flow explorer that pymap builds itself:
5
+
6
+ pydeps import graph between modules
7
+ pyreverse class and package diagrams (ships with pylint)
8
+ code2flow call graph at function level
9
+ tach module graph + Mermaid, cycle detection
10
+
11
+ The core depends only on the standard library: missing tools are reported and
12
+ then skipped.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ if TYPE_CHECKING: # imported lazily at runtime by __getattr__ below
20
+ from pymap.mapper import Report, map_codebase
21
+ from pymap.settings import Settings
22
+
23
+ __version__ = "0.3.0"
24
+ __all__ = ["Report", "Settings", "__version__", "map_codebase"]
25
+
26
+
27
+ def __getattr__(name: str) -> Any:
28
+ # Lazy import, so `import pymap` stays instant when all you want is __version__.
29
+ if name in ("Report", "map_codebase"):
30
+ from pymap import mapper
31
+
32
+ return getattr(mapper, name)
33
+ if name == "Settings":
34
+ from pymap.settings import Settings
35
+
36
+ return Settings
37
+ raise AttributeError(f"module 'pymap' has no attribute {name!r}")
@@ -0,0 +1,8 @@
1
+ """Enables `python -m pymap`, useful when the console script is not on PATH."""
2
+
3
+ import sys
4
+
5
+ from pymap.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """Static analysis: everything pymap works out on its own, without any tool."""
@@ -0,0 +1,36 @@
1
+ """Reading the call graph produced by code2flow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ Relations = dict[str, dict[str, list[str]]]
9
+
10
+
11
+ def load_call_graph(path: str | Path) -> Relations:
12
+ """Read code2flow's JSON and return outgoing and incoming calls per symbol.
13
+
14
+ Result shape: ``{symbol: {"calls": [...], "called_by": [...]}}``. A missing
15
+ or unreadable file yields an empty graph: the explorer works without it, it
16
+ just loses navigation between functions.
17
+ """
18
+ try:
19
+ graph = json.loads(Path(path).read_text(encoding="utf-8"))["graph"]
20
+ except (OSError, ValueError, KeyError):
21
+ return {}
22
+
23
+ names = {node_id: node["name"] for node_id, node in graph["nodes"].items()}
24
+ relations: dict[str, dict[str, set[str]]] = {}
25
+ for edge in graph["edges"]:
26
+ source, target = names.get(edge["source"]), names.get(edge["target"])
27
+ if not source or not target:
28
+ continue
29
+ relations.setdefault(source, {"calls": set(), "called_by": set()})["calls"].add(target)
30
+ relations.setdefault(target, {"calls": set(), "called_by": set()})["called_by"].add(
31
+ source
32
+ )
33
+ return {
34
+ name: {side: sorted(values) for side, values in sides.items()}
35
+ for name, sides in relations.items()
36
+ }