modwire-cli 1.0.0__tar.gz → 1.0.1__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.
- {modwire_cli-1.0.0 → modwire_cli-1.0.1}/.gitignore +2 -7
- modwire_cli-1.0.1/PKG-INFO +72 -0
- modwire_cli-1.0.1/README.md +37 -0
- modwire_cli-1.0.1/pyproject.toml +84 -0
- modwire_cli-1.0.1/src/modwire_cli/__init__.py +3 -0
- modwire_cli-1.0.1/src/modwire_cli/__main__.py +38 -0
- {modwire_cli-1.0.0 → modwire_cli-1.0.1}/src/modwire_cli/_version.py +3 -3
- modwire_cli-1.0.1/src/modwire_cli/config.py +15 -0
- modwire_cli-1.0.1/src/modwire_cli/extraction.py +14 -0
- modwire_cli-1.0.1/src/modwire_cli/facade.py +30 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/__init__.py +0 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/context.py +26 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/runner.py +32 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/step.py +13 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/__init__.py +0 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/callables_insight.py +24 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/clusters_insight.py +28 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/coherence_insight.py +37 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/flow_violations.py +24 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/hotspots_insight.py +26 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/map_tree.py +34 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/shape_violations.py +27 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/success.py +16 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/unknown_files_warning.py +20 -0
- modwire_cli-1.0.1/src/modwire_cli/pipeline/steps/unused_exports_insight.py +26 -0
- modwire_cli-1.0.1/tests/test_cli.py +60 -0
- modwire_cli-1.0.0/.github/ISSUE_TEMPLATE/bug_report.yml +0 -46
- modwire_cli-1.0.0/.github/ISSUE_TEMPLATE/config.yml +0 -1
- modwire_cli-1.0.0/.github/ISSUE_TEMPLATE/feature_request.yml +0 -47
- modwire_cli-1.0.0/.github/ISSUE_TEMPLATE/task.yml +0 -48
- modwire_cli-1.0.0/.github/workflows/ci.yml +0 -28
- modwire_cli-1.0.0/.github/workflows/human-approval.yml +0 -133
- modwire_cli-1.0.0/.github/workflows/issue-metadata.yml +0 -21
- modwire_cli-1.0.0/.github/workflows/release.yml +0 -68
- modwire_cli-1.0.0/CHANGELOG.md +0 -11
- modwire_cli-1.0.0/MANIFEST.in +0 -5
- modwire_cli-1.0.0/Makefile +0 -21
- modwire_cli-1.0.0/PKG-INFO +0 -78
- modwire_cli-1.0.0/README.md +0 -42
- modwire_cli-1.0.0/pyproject.toml +0 -75
- modwire_cli-1.0.0/scripts/verify_release.py +0 -57
- modwire_cli-1.0.0/setup.cfg +0 -4
- modwire_cli-1.0.0/src/modwire_cli/__init__.py +0 -6
- modwire_cli-1.0.0/src/modwire_cli/main.py +0 -65
- modwire_cli-1.0.0/src/modwire_cli.egg-info/PKG-INFO +0 -78
- modwire_cli-1.0.0/src/modwire_cli.egg-info/SOURCES.txt +0 -31
- modwire_cli-1.0.0/src/modwire_cli.egg-info/dependency_links.txt +0 -1
- modwire_cli-1.0.0/src/modwire_cli.egg-info/entry_points.txt +0 -2
- modwire_cli-1.0.0/src/modwire_cli.egg-info/requires.txt +0 -9
- modwire_cli-1.0.0/src/modwire_cli.egg-info/scm_file_list.json +0 -26
- modwire_cli-1.0.0/src/modwire_cli.egg-info/scm_version.json +0 -8
- modwire_cli-1.0.0/src/modwire_cli.egg-info/top_level.txt +0 -1
- modwire_cli-1.0.0/tests/test_cli.py +0 -65
- modwire_cli-1.0.0/tests/test_release.py +0 -9
- modwire_cli-1.0.0/uv.lock +0 -867
- {modwire_cli-1.0.0 → modwire_cli-1.0.1}/LICENSE +0 -0
- {modwire_cli-1.0.0 → modwire_cli-1.0.1}/src/modwire_cli/py.typed +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modwire-cli
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Command-line interface for the Modwire ecosystem.
|
|
5
|
+
Project-URL: Homepage, https://github.com/modwire/modwire-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/modwire/modwire-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/modwire/modwire-cli/issues
|
|
8
|
+
Author: Tomasz Szpak
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,modwire
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: modwire-architecture<7,>=6
|
|
25
|
+
Requires-Dist: modwire-extraction<3,>=2
|
|
26
|
+
Requires-Dist: pydantic-yaml>=1.7
|
|
27
|
+
Requires-Dist: rich>=13
|
|
28
|
+
Requires-Dist: wireup>=2.12.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.3; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=9; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.14; extra == 'dev'
|
|
33
|
+
Requires-Dist: twine>=6; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# modwire-cli
|
|
37
|
+
|
|
38
|
+
The command-line interface for the Modwire ecosystem.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
pip install modwire-cli
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
modwire --language python
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
By default, Modwire reads its configuration from `.modwire/architecture.yaml`
|
|
53
|
+
and analyses the current directory. Override either path when needed:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
modwire --language typescript --dot-dir path/to/.modwire --architecture-root path/to/source
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To check this repository's architecture:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
uv run modwire --language python
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
uv sync --all-groups
|
|
69
|
+
uv run pytest
|
|
70
|
+
uv run ruff check .
|
|
71
|
+
uv build
|
|
72
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# modwire-cli
|
|
2
|
+
|
|
3
|
+
The command-line interface for the Modwire ecosystem.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pip install modwire-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
modwire --language python
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
By default, Modwire reads its configuration from `.modwire/architecture.yaml`
|
|
18
|
+
and analyses the current directory. Override either path when needed:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
modwire --language typescript --dot-dir path/to/.modwire --architecture-root path/to/source
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
To check this repository's architecture:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
uv run modwire --language python
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Development
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
uv sync --all-groups
|
|
34
|
+
uv run pytest
|
|
35
|
+
uv run ruff check .
|
|
36
|
+
uv build
|
|
37
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.28", "hatch-vcs>=0.5"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "modwire-cli"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Command-line interface for the Modwire ecosystem."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Tomasz Szpak" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Topic :: Software Development",
|
|
25
|
+
]
|
|
26
|
+
keywords = ["cli", "modwire"]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"modwire-architecture>=6,<7",
|
|
29
|
+
"modwire-extraction>=2,<3",
|
|
30
|
+
"pydantic-yaml>=1.7",
|
|
31
|
+
"rich>=13",
|
|
32
|
+
"wireup>=2.12.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
modwire = "modwire_cli.__main__:main"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"build>=1.3",
|
|
41
|
+
"pytest>=9",
|
|
42
|
+
"ruff>=0.14",
|
|
43
|
+
"twine>=6",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/modwire/modwire-cli"
|
|
48
|
+
Repository = "https://github.com/modwire/modwire-cli"
|
|
49
|
+
Issues = "https://github.com/modwire/modwire-cli/issues"
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"build>=1.3",
|
|
54
|
+
"pytest>=9",
|
|
55
|
+
"ruff>=0.14",
|
|
56
|
+
"twine>=6",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.hatch.version]
|
|
60
|
+
source = "vcs"
|
|
61
|
+
tag-pattern = '^v(?P<version>\d+\.\d+\.\d+)$'
|
|
62
|
+
fallback-version = "0.0.0"
|
|
63
|
+
|
|
64
|
+
[tool.hatch.build.hooks.vcs]
|
|
65
|
+
version-file = "src/modwire_cli/_version.py"
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.wheel]
|
|
68
|
+
packages = ["src/modwire_cli"]
|
|
69
|
+
|
|
70
|
+
[tool.hatch.build.targets.sdist]
|
|
71
|
+
include = ["src", "tests", "README.md", "LICENSE", "pyproject.toml"]
|
|
72
|
+
|
|
73
|
+
[tool.pytest.ini_options]
|
|
74
|
+
testpaths = ["tests"]
|
|
75
|
+
addopts = ["-ra", "--strict-config", "--strict-markers"]
|
|
76
|
+
filterwarnings = ["error"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
line-length = 120
|
|
80
|
+
extend-exclude = ["**/_version.py"]
|
|
81
|
+
target-version = "py312"
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint]
|
|
84
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from wireup import create_sync_container, instance
|
|
10
|
+
|
|
11
|
+
import modwire_cli
|
|
12
|
+
|
|
13
|
+
from .facade import FitnessFacade
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> int:
|
|
17
|
+
return run(sys.argv[1:])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run(argv: Sequence[str]) -> int:
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
parser.add_argument("--dot-dir", type=Path, default=Path(".modwire"))
|
|
23
|
+
parser.add_argument("--architecture-root", type=Path, default=Path("."))
|
|
24
|
+
parser.add_argument("--language", required=True)
|
|
25
|
+
arguments = parser.parse_args(argv)
|
|
26
|
+
|
|
27
|
+
container = create_sync_container(
|
|
28
|
+
injectables=[modwire_cli, instance(Console(), as_type=Console)]
|
|
29
|
+
)
|
|
30
|
+
try:
|
|
31
|
+
facade = container.get(FitnessFacade)
|
|
32
|
+
return facade.reports(
|
|
33
|
+
dot_dir=arguments.dot_dir,
|
|
34
|
+
architecture_root=arguments.architecture_root,
|
|
35
|
+
language=arguments.language,
|
|
36
|
+
)
|
|
37
|
+
finally:
|
|
38
|
+
container.close()
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id =
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from modwire_architecture import ArchitectureConfig
|
|
7
|
+
from pydantic_yaml import parse_yaml_file_as
|
|
8
|
+
from wireup import injectable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@injectable
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ConfigService:
|
|
14
|
+
def load(self, dot_dir: Path) -> ArchitectureConfig:
|
|
15
|
+
return parse_yaml_file_as(ArchitectureConfig, dot_dir / "architecture.yaml")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from modwire_extraction import ModwireExtraction, QueryableCodeMap
|
|
7
|
+
from wireup import injectable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class ExtractionService:
|
|
13
|
+
def load(self, architecture_root: Path, language: str) -> QueryableCodeMap:
|
|
14
|
+
return ModwireExtraction(architecture_root).generate_queryable_map(language)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from modwire_architecture import Modwire
|
|
7
|
+
from wireup import injectable
|
|
8
|
+
|
|
9
|
+
from .config import ConfigService
|
|
10
|
+
from .extraction import ExtractionService
|
|
11
|
+
from .pipeline.runner import ReportPipelineRunner
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@injectable
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class FitnessFacade:
|
|
17
|
+
config_service: ConfigService
|
|
18
|
+
extraction_service: ExtractionService
|
|
19
|
+
pipeline_runner: ReportPipelineRunner
|
|
20
|
+
|
|
21
|
+
def reports(
|
|
22
|
+
self,
|
|
23
|
+
dot_dir: Path,
|
|
24
|
+
architecture_root: Path,
|
|
25
|
+
language: str,
|
|
26
|
+
) -> int:
|
|
27
|
+
config = self.config_service.load(dot_dir)
|
|
28
|
+
code_map = self.extraction_service.load(architecture_root, language)
|
|
29
|
+
reports = Modwire().architecture(config).report(code_map)
|
|
30
|
+
return self.pipeline_runner.run(reports)
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
from modwire_architecture.shared.report.base import ReportNode
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ReportPipelineContext:
|
|
12
|
+
reports: tuple[ReportNode, ...]
|
|
13
|
+
console: Console
|
|
14
|
+
failed: bool = False
|
|
15
|
+
|
|
16
|
+
def has_report(self, report_type: type[ReportNode]) -> bool:
|
|
17
|
+
return any(type(report) is report_type for report in self.reports)
|
|
18
|
+
|
|
19
|
+
def report[ReportType: ReportNode](self, report_type: type[ReportType]) -> ReportType:
|
|
20
|
+
for report in self.reports:
|
|
21
|
+
if type(report) is report_type:
|
|
22
|
+
return cast(ReportType, report)
|
|
23
|
+
raise LookupError(f"Missing report: {report_type.__name__}")
|
|
24
|
+
|
|
25
|
+
def fail(self) -> ReportPipelineContext:
|
|
26
|
+
return replace(self, failed=True)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Hashable, Mapping
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from modwire_architecture.shared.report.base import ReportNode
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from wireup import injectable
|
|
9
|
+
|
|
10
|
+
from .context import ReportPipelineContext
|
|
11
|
+
from .step import ReportPipelineStep
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@injectable
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class ReportPipelineRunner:
|
|
17
|
+
steps: Mapping[Hashable, ReportPipelineStep]
|
|
18
|
+
console: Console
|
|
19
|
+
|
|
20
|
+
def run(self, reports: tuple[ReportNode, ...]) -> int:
|
|
21
|
+
context = ReportPipelineContext(reports=reports, console=self.console)
|
|
22
|
+
result = self.process(context)
|
|
23
|
+
return int(result.failed)
|
|
24
|
+
|
|
25
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
26
|
+
for _, step in sorted(
|
|
27
|
+
self.steps.items(),
|
|
28
|
+
key=lambda item: int(str(item[0]).partition("-")[0]),
|
|
29
|
+
):
|
|
30
|
+
if step.should_process(context):
|
|
31
|
+
context = step.process(context)
|
|
32
|
+
return context
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from .context import ReportPipelineContext
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReportPipelineStep(ABC):
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def should_process(self, context: ReportPipelineContext) -> bool: ...
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext: ...
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.insights.reporters.callables import CallablesReport
|
|
4
|
+
from wireup import injectable
|
|
5
|
+
|
|
6
|
+
from ..context import ReportPipelineContext
|
|
7
|
+
from ..step import ReportPipelineStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable(as_type=ReportPipelineStep, qualifier="80-callables")
|
|
11
|
+
class CallablesInsight(ReportPipelineStep):
|
|
12
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
13
|
+
return context.has_report(CallablesReport) and bool(context.report(CallablesReport).entries)
|
|
14
|
+
|
|
15
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
16
|
+
report = context.report(CallablesReport)
|
|
17
|
+
context.console.print("[blue]Callable graph:[/blue]")
|
|
18
|
+
for entry in report.entries:
|
|
19
|
+
context.console.print(entry.source_callable, markup=False)
|
|
20
|
+
for call in entry.calls:
|
|
21
|
+
context.console.print(f" calls: {call}", markup=False)
|
|
22
|
+
for caller in entry.callers:
|
|
23
|
+
context.console.print(f" called by: {caller}", markup=False)
|
|
24
|
+
return context
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.insights.reporters.clusters import ClustersReport
|
|
4
|
+
from wireup import injectable
|
|
5
|
+
|
|
6
|
+
from ..context import ReportPipelineContext
|
|
7
|
+
from ..step import ReportPipelineStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable(as_type=ReportPipelineStep, qualifier="50-clusters")
|
|
11
|
+
class ClustersInsight(ReportPipelineStep):
|
|
12
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
13
|
+
return context.has_report(ClustersReport) and bool(context.report(ClustersReport).clusters)
|
|
14
|
+
|
|
15
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
16
|
+
report = context.report(ClustersReport)
|
|
17
|
+
context.console.print("[blue]Dependency clusters:[/blue]")
|
|
18
|
+
for cluster in report.clusters:
|
|
19
|
+
context.console.print(
|
|
20
|
+
(
|
|
21
|
+
f"{cluster.name} (pressure: {cluster.pressure_score}, "
|
|
22
|
+
f"incoming: {cluster.incoming_count}, outgoing: {cluster.outgoing_count})"
|
|
23
|
+
),
|
|
24
|
+
markup=False,
|
|
25
|
+
)
|
|
26
|
+
for source_id in cluster.top_files:
|
|
27
|
+
context.console.print(f" - {source_id}", markup=False)
|
|
28
|
+
return context
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.insights.reporters.coherence import CoherenceReport
|
|
4
|
+
from wireup import injectable
|
|
5
|
+
|
|
6
|
+
from ..context import ReportPipelineContext
|
|
7
|
+
from ..step import ReportPipelineStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable(as_type=ReportPipelineStep, qualifier="70-coherence")
|
|
11
|
+
class CoherenceInsight(ReportPipelineStep):
|
|
12
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
13
|
+
if not context.has_report(CoherenceReport):
|
|
14
|
+
return False
|
|
15
|
+
report = context.report(CoherenceReport)
|
|
16
|
+
return bool(report.roots or report.leaves or report.isolated or report.external_dependencies)
|
|
17
|
+
|
|
18
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
19
|
+
report = context.report(CoherenceReport)
|
|
20
|
+
context.console.print("[blue]Dependency coherence:[/blue]")
|
|
21
|
+
self._print_section(context, "Roots", report.roots)
|
|
22
|
+
self._print_section(context, "Leaves", report.leaves)
|
|
23
|
+
self._print_section(context, "Isolated", report.isolated)
|
|
24
|
+
self._print_section(context, "External dependencies", report.external_dependencies)
|
|
25
|
+
return context
|
|
26
|
+
|
|
27
|
+
def _print_section(
|
|
28
|
+
self,
|
|
29
|
+
context: ReportPipelineContext,
|
|
30
|
+
title: str,
|
|
31
|
+
entries: tuple[str, ...],
|
|
32
|
+
) -> None:
|
|
33
|
+
if not entries:
|
|
34
|
+
return
|
|
35
|
+
context.console.print(title)
|
|
36
|
+
for entry in entries:
|
|
37
|
+
context.console.print(f" - {entry}", markup=False)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.boundaries.collector import FlowReport
|
|
4
|
+
from wireup import injectable
|
|
5
|
+
|
|
6
|
+
from ..context import ReportPipelineContext
|
|
7
|
+
from ..step import ReportPipelineStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable(as_type=ReportPipelineStep, qualifier="30-flow-violations")
|
|
11
|
+
class FlowViolations(ReportPipelineStep):
|
|
12
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
13
|
+
return bool(context.report(FlowReport).violations)
|
|
14
|
+
|
|
15
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
16
|
+
report = context.report(FlowReport)
|
|
17
|
+
context.console.print("[red]Flow violations:[/red]")
|
|
18
|
+
for violation in report.violations:
|
|
19
|
+
context.console.print(
|
|
20
|
+
f" - [{violation.rule_name}] {violation.message}",
|
|
21
|
+
markup=False,
|
|
22
|
+
)
|
|
23
|
+
context.console.print(f" {' -> '.join(violation.path)}", markup=False)
|
|
24
|
+
return context.fail()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.insights.reporters.hotspots import HotspotsReport
|
|
4
|
+
from wireup import injectable
|
|
5
|
+
|
|
6
|
+
from ..context import ReportPipelineContext
|
|
7
|
+
from ..step import ReportPipelineStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable(as_type=ReportPipelineStep, qualifier="60-hotspots")
|
|
11
|
+
class HotspotsInsight(ReportPipelineStep):
|
|
12
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
13
|
+
return context.has_report(HotspotsReport) and bool(context.report(HotspotsReport).hotspots)
|
|
14
|
+
|
|
15
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
16
|
+
report = context.report(HotspotsReport)
|
|
17
|
+
context.console.print("[blue]Dependency hotspots:[/blue]")
|
|
18
|
+
for hotspot in report.hotspots:
|
|
19
|
+
context.console.print(
|
|
20
|
+
(
|
|
21
|
+
f" - {hotspot.source_id} (pressure: {hotspot.pressure_score}, "
|
|
22
|
+
f"incoming: {hotspot.incoming_count}, outgoing: {hotspot.outgoing_count})"
|
|
23
|
+
),
|
|
24
|
+
markup=False,
|
|
25
|
+
)
|
|
26
|
+
return context
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.map.collector import ArchitectureGroup, MapReport
|
|
4
|
+
from rich.tree import Tree
|
|
5
|
+
from wireup import injectable
|
|
6
|
+
|
|
7
|
+
from ..context import ReportPipelineContext
|
|
8
|
+
from ..step import ReportPipelineStep
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@injectable(as_type=ReportPipelineStep, qualifier="10-map")
|
|
12
|
+
class MapTree(ReportPipelineStep):
|
|
13
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
14
|
+
return True
|
|
15
|
+
|
|
16
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
17
|
+
report = context.report(MapReport)
|
|
18
|
+
tree = Tree("Architecture Map")
|
|
19
|
+
self._add_groups(tree, "Modules", report.modules)
|
|
20
|
+
self._add_groups(tree, "Layers", report.layers)
|
|
21
|
+
context.console.print(tree)
|
|
22
|
+
return context
|
|
23
|
+
|
|
24
|
+
def _add_groups(
|
|
25
|
+
self,
|
|
26
|
+
tree: Tree,
|
|
27
|
+
title: str,
|
|
28
|
+
groups: tuple[ArchitectureGroup, ...],
|
|
29
|
+
) -> None:
|
|
30
|
+
branch = tree.add(title)
|
|
31
|
+
for group in groups:
|
|
32
|
+
group_branch = branch.add(group.name)
|
|
33
|
+
for source_id in group.source_ids:
|
|
34
|
+
group_branch.add(source_id)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.shape.collector import ShapeReport
|
|
4
|
+
from wireup import injectable
|
|
5
|
+
|
|
6
|
+
from ..context import ReportPipelineContext
|
|
7
|
+
from ..step import ReportPipelineStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable(as_type=ReportPipelineStep, qualifier="40-shape-violations")
|
|
11
|
+
class ShapeViolations(ReportPipelineStep):
|
|
12
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
13
|
+
return bool(context.report(ShapeReport).violations)
|
|
14
|
+
|
|
15
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
16
|
+
report = context.report(ShapeReport)
|
|
17
|
+
context.console.print("[red]Shape violations:[/red]")
|
|
18
|
+
for violation in report.violations:
|
|
19
|
+
symbol = f" {violation.symbol_kind} {violation.symbol_name}" if violation.symbol_kind else ""
|
|
20
|
+
context.console.print(
|
|
21
|
+
(
|
|
22
|
+
f" - [{violation.rule_name}] {violation.source_id}{symbol}: "
|
|
23
|
+
f"{violation.actual} exceeds {violation.limit}"
|
|
24
|
+
),
|
|
25
|
+
markup=False,
|
|
26
|
+
)
|
|
27
|
+
return context.fail()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wireup import injectable
|
|
4
|
+
|
|
5
|
+
from ..context import ReportPipelineContext
|
|
6
|
+
from ..step import ReportPipelineStep
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@injectable(as_type=ReportPipelineStep, qualifier="100-success")
|
|
10
|
+
class Success(ReportPipelineStep):
|
|
11
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
12
|
+
return not context.failed
|
|
13
|
+
|
|
14
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
15
|
+
context.console.print("[green]Architecture checks passed.[/green]")
|
|
16
|
+
return context
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from modwire_architecture.architecture.map.collector import MapReport
|
|
4
|
+
from wireup import injectable
|
|
5
|
+
|
|
6
|
+
from ..context import ReportPipelineContext
|
|
7
|
+
from ..step import ReportPipelineStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@injectable(as_type=ReportPipelineStep, qualifier="20-unknown-files")
|
|
11
|
+
class UnknownFilesWarning(ReportPipelineStep):
|
|
12
|
+
def should_process(self, context: ReportPipelineContext) -> bool:
|
|
13
|
+
return bool(context.report(MapReport).unknown_files)
|
|
14
|
+
|
|
15
|
+
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
16
|
+
report = context.report(MapReport)
|
|
17
|
+
context.console.print("[yellow]Warning:[/yellow] Files not included in the architecture map:")
|
|
18
|
+
for source_id in report.unknown_files:
|
|
19
|
+
context.console.print(f" - {source_id}", markup=False)
|
|
20
|
+
return context
|