modwire-cli 1.0.1__tar.gz → 1.1.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.
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/PKG-INFO +7 -1
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/README.md +6 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/__main__.py +6 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/_version.py +2 -2
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/facade.py +2 -1
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/context.py +1 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/runner.py +2 -2
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/map_tree.py +18 -2
- modwire_cli-1.1.0/tests/test_cli.py +122 -0
- modwire_cli-1.0.1/tests/test_cli.py +0 -60
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/.gitignore +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/LICENSE +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/pyproject.toml +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/__init__.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/config.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/extraction.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/__init__.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/step.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/__init__.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/callables_insight.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/clusters_insight.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/coherence_insight.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/flow_violations.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/hotspots_insight.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/shape_violations.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/success.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/unknown_files_warning.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/unused_exports_insight.py +0 -0
- {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modwire-cli
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Command-line interface for the Modwire ecosystem.
|
|
5
5
|
Project-URL: Homepage, https://github.com/modwire/modwire-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/modwire/modwire-cli
|
|
@@ -56,6 +56,12 @@ and analyses the current directory. Override either path when needed:
|
|
|
56
56
|
modwire --language typescript --dot-dir path/to/.modwire --architecture-root path/to/source
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
Use `--summary` for a compact module → layer map that omits individual source files:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
modwire --language python --summary
|
|
63
|
+
```
|
|
64
|
+
|
|
59
65
|
To check this repository's architecture:
|
|
60
66
|
|
|
61
67
|
```sh
|
|
@@ -21,6 +21,12 @@ and analyses the current directory. Override either path when needed:
|
|
|
21
21
|
modwire --language typescript --dot-dir path/to/.modwire --architecture-root path/to/source
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
Use `--summary` for a compact module → layer map that omits individual source files:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
modwire --language python --summary
|
|
28
|
+
```
|
|
29
|
+
|
|
24
30
|
To check this repository's architecture:
|
|
25
31
|
|
|
26
32
|
```sh
|
|
@@ -22,6 +22,11 @@ def run(argv: Sequence[str]) -> int:
|
|
|
22
22
|
parser.add_argument("--dot-dir", type=Path, default=Path(".modwire"))
|
|
23
23
|
parser.add_argument("--architecture-root", type=Path, default=Path("."))
|
|
24
24
|
parser.add_argument("--language", required=True)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--summary",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="Render modules with their non-empty layers, without individual files.",
|
|
29
|
+
)
|
|
25
30
|
arguments = parser.parse_args(argv)
|
|
26
31
|
|
|
27
32
|
container = create_sync_container(
|
|
@@ -33,6 +38,7 @@ def run(argv: Sequence[str]) -> int:
|
|
|
33
38
|
dot_dir=arguments.dot_dir,
|
|
34
39
|
architecture_root=arguments.architecture_root,
|
|
35
40
|
language=arguments.language,
|
|
41
|
+
summary=arguments.summary,
|
|
36
42
|
)
|
|
37
43
|
finally:
|
|
38
44
|
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,
|
|
21
|
+
__version__ = version = '1.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 1, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -23,8 +23,9 @@ class FitnessFacade:
|
|
|
23
23
|
dot_dir: Path,
|
|
24
24
|
architecture_root: Path,
|
|
25
25
|
language: str,
|
|
26
|
+
summary: bool = False,
|
|
26
27
|
) -> int:
|
|
27
28
|
config = self.config_service.load(dot_dir)
|
|
28
29
|
code_map = self.extraction_service.load(architecture_root, language)
|
|
29
30
|
reports = Modwire().architecture(config).report(code_map)
|
|
30
|
-
return self.pipeline_runner.run(reports)
|
|
31
|
+
return self.pipeline_runner.run(reports, summary=summary)
|
|
@@ -17,8 +17,8 @@ class ReportPipelineRunner:
|
|
|
17
17
|
steps: Mapping[Hashable, ReportPipelineStep]
|
|
18
18
|
console: Console
|
|
19
19
|
|
|
20
|
-
def run(self, reports: tuple[ReportNode, ...]) -> int:
|
|
21
|
-
context = ReportPipelineContext(reports=reports, console=self.console)
|
|
20
|
+
def run(self, reports: tuple[ReportNode, ...], *, summary: bool = False) -> int:
|
|
21
|
+
context = ReportPipelineContext(reports=reports, console=self.console, summary=summary)
|
|
22
22
|
result = self.process(context)
|
|
23
23
|
return int(result.failed)
|
|
24
24
|
|
|
@@ -16,11 +16,27 @@ class MapTree(ReportPipelineStep):
|
|
|
16
16
|
def process(self, context: ReportPipelineContext) -> ReportPipelineContext:
|
|
17
17
|
report = context.report(MapReport)
|
|
18
18
|
tree = Tree("Architecture Map")
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
if context.summary:
|
|
20
|
+
self._add_summary(tree, report.modules, report.layers)
|
|
21
|
+
else:
|
|
22
|
+
self._add_groups(tree, "Modules", report.modules)
|
|
23
|
+
self._add_groups(tree, "Layers", report.layers)
|
|
21
24
|
context.console.print(tree)
|
|
22
25
|
return context
|
|
23
26
|
|
|
27
|
+
def _add_summary(
|
|
28
|
+
self,
|
|
29
|
+
tree: Tree,
|
|
30
|
+
modules: tuple[ArchitectureGroup, ...],
|
|
31
|
+
layers: tuple[ArchitectureGroup, ...],
|
|
32
|
+
) -> None:
|
|
33
|
+
for module in modules:
|
|
34
|
+
module_branch = tree.add(module.name)
|
|
35
|
+
module_source_ids = set(module.source_ids)
|
|
36
|
+
for layer in layers:
|
|
37
|
+
if module_source_ids.intersection(layer.source_ids):
|
|
38
|
+
module_branch.add(layer.name)
|
|
39
|
+
|
|
24
40
|
def _add_groups(
|
|
25
41
|
self,
|
|
26
42
|
tree: Tree,
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from textwrap import dedent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_cli_succeeds_for_a_valid_project(tmp_path: Path) -> None:
|
|
10
|
+
write_project(tmp_path, max_functions_per_file=1)
|
|
11
|
+
|
|
12
|
+
result = run_modwire(tmp_path)
|
|
13
|
+
|
|
14
|
+
assert result.returncode == 0, result.stderr
|
|
15
|
+
assert "Architecture checks passed." in result.stdout
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_cli_returns_failure_for_a_shape_violation(tmp_path: Path) -> None:
|
|
19
|
+
write_project(tmp_path, max_functions_per_file=0)
|
|
20
|
+
|
|
21
|
+
result = run_modwire(tmp_path)
|
|
22
|
+
|
|
23
|
+
assert result.returncode == 1
|
|
24
|
+
assert "Shape violations:" in result.stdout
|
|
25
|
+
assert "max_functions_per_file" in result.stdout
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_cli_summary_renders_non_empty_layers_beneath_modules(tmp_path: Path) -> None:
|
|
29
|
+
write_layered_project(tmp_path)
|
|
30
|
+
|
|
31
|
+
result = run_modwire(tmp_path, "--summary")
|
|
32
|
+
|
|
33
|
+
assert result.returncode == 0, result.stderr
|
|
34
|
+
assert "orders" in result.stdout
|
|
35
|
+
for layer in ("wiring", "adapters", "use_cases", "ports", "domain"):
|
|
36
|
+
assert layer in result.stdout
|
|
37
|
+
assert "bootstrap.py" not in result.stdout
|
|
38
|
+
assert "repository.py" not in result.stdout
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_cli_keeps_detailed_map_by_default(tmp_path: Path) -> None:
|
|
42
|
+
write_layered_project(tmp_path)
|
|
43
|
+
|
|
44
|
+
result = run_modwire(tmp_path)
|
|
45
|
+
|
|
46
|
+
assert result.returncode == 0, result.stderr
|
|
47
|
+
assert "bootstrap.py" in result.stdout
|
|
48
|
+
assert "repository.py" in result.stdout
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run_modwire(project_root: Path, *arguments: str) -> subprocess.CompletedProcess[str]:
|
|
52
|
+
executable = Path(sys.executable).with_name("modwire")
|
|
53
|
+
return subprocess.run(
|
|
54
|
+
[str(executable), "--language", "python", *arguments],
|
|
55
|
+
cwd=project_root,
|
|
56
|
+
check=False,
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def write_project(project_root: Path, *, max_functions_per_file: int) -> None:
|
|
63
|
+
dot_dir = project_root / ".modwire"
|
|
64
|
+
source_dir = project_root / "src"
|
|
65
|
+
dot_dir.mkdir()
|
|
66
|
+
source_dir.mkdir()
|
|
67
|
+
(source_dir / "example.py").write_text("def example() -> None:\n pass\n")
|
|
68
|
+
(dot_dir / "architecture.yaml").write_text(
|
|
69
|
+
dedent(
|
|
70
|
+
f"""\
|
|
71
|
+
boundaries:
|
|
72
|
+
tags:
|
|
73
|
+
- name: package
|
|
74
|
+
match: src
|
|
75
|
+
flow:
|
|
76
|
+
module_tag: package
|
|
77
|
+
analyzers:
|
|
78
|
+
- module-boundaries
|
|
79
|
+
shape:
|
|
80
|
+
max_functions_per_file: {max_functions_per_file}
|
|
81
|
+
"""
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def write_layered_project(project_root: Path) -> None:
|
|
87
|
+
dot_dir = project_root / ".modwire"
|
|
88
|
+
source_dir = project_root / "src" / "orders"
|
|
89
|
+
dot_dir.mkdir()
|
|
90
|
+
for layer, filename in (
|
|
91
|
+
("wiring", "bootstrap.py"),
|
|
92
|
+
("adapters", "repository.py"),
|
|
93
|
+
("use_cases", "place_order.py"),
|
|
94
|
+
("ports", "order_repository.py"),
|
|
95
|
+
("domain", "order.py"),
|
|
96
|
+
):
|
|
97
|
+
layer_dir = source_dir / layer
|
|
98
|
+
layer_dir.mkdir(parents=True)
|
|
99
|
+
(layer_dir / filename).write_text("pass\n")
|
|
100
|
+
(dot_dir / "architecture.yaml").write_text(
|
|
101
|
+
dedent(
|
|
102
|
+
"""\
|
|
103
|
+
boundaries:
|
|
104
|
+
tags:
|
|
105
|
+
- name: module
|
|
106
|
+
match: src/*
|
|
107
|
+
- name: wiring
|
|
108
|
+
match: src/*/wiring
|
|
109
|
+
- name: adapters
|
|
110
|
+
match: src/*/adapters
|
|
111
|
+
- name: use_cases
|
|
112
|
+
match: src/*/use_cases
|
|
113
|
+
- name: ports
|
|
114
|
+
match: src/*/ports
|
|
115
|
+
- name: domain
|
|
116
|
+
match: src/*/domain
|
|
117
|
+
flow:
|
|
118
|
+
module_tag: module
|
|
119
|
+
layers: [wiring, adapters, use_cases, ports, domain]
|
|
120
|
+
"""
|
|
121
|
+
)
|
|
122
|
+
)
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
import sys
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from textwrap import dedent
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def test_cli_succeeds_for_a_valid_project(tmp_path: Path) -> None:
|
|
10
|
-
write_project(tmp_path, max_functions_per_file=1)
|
|
11
|
-
|
|
12
|
-
result = run_modwire(tmp_path)
|
|
13
|
-
|
|
14
|
-
assert result.returncode == 0, result.stderr
|
|
15
|
-
assert "Architecture checks passed." in result.stdout
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_cli_returns_failure_for_a_shape_violation(tmp_path: Path) -> None:
|
|
19
|
-
write_project(tmp_path, max_functions_per_file=0)
|
|
20
|
-
|
|
21
|
-
result = run_modwire(tmp_path)
|
|
22
|
-
|
|
23
|
-
assert result.returncode == 1
|
|
24
|
-
assert "Shape violations:" in result.stdout
|
|
25
|
-
assert "max_functions_per_file" in result.stdout
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def run_modwire(project_root: Path) -> subprocess.CompletedProcess[str]:
|
|
29
|
-
executable = Path(sys.executable).with_name("modwire")
|
|
30
|
-
return subprocess.run(
|
|
31
|
-
[str(executable), "--language", "python"],
|
|
32
|
-
cwd=project_root,
|
|
33
|
-
check=False,
|
|
34
|
-
capture_output=True,
|
|
35
|
-
text=True,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def write_project(project_root: Path, *, max_functions_per_file: int) -> None:
|
|
40
|
-
dot_dir = project_root / ".modwire"
|
|
41
|
-
source_dir = project_root / "src"
|
|
42
|
-
dot_dir.mkdir()
|
|
43
|
-
source_dir.mkdir()
|
|
44
|
-
(source_dir / "example.py").write_text("def example() -> None:\n pass\n")
|
|
45
|
-
(dot_dir / "architecture.yaml").write_text(
|
|
46
|
-
dedent(
|
|
47
|
-
f"""\
|
|
48
|
-
boundaries:
|
|
49
|
-
tags:
|
|
50
|
-
- name: package
|
|
51
|
-
match: src
|
|
52
|
-
flow:
|
|
53
|
-
module_tag: package
|
|
54
|
-
analyzers:
|
|
55
|
-
- module-boundaries
|
|
56
|
-
shape:
|
|
57
|
-
max_functions_per_file: {max_functions_per_file}
|
|
58
|
-
"""
|
|
59
|
-
)
|
|
60
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/unknown_files_warning.py
RENAMED
|
File without changes
|
{modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/unused_exports_insight.py
RENAMED
|
File without changes
|
|
File without changes
|