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.
Files changed (29) hide show
  1. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/PKG-INFO +7 -1
  2. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/README.md +6 -0
  3. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/__main__.py +6 -0
  4. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/_version.py +2 -2
  5. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/facade.py +2 -1
  6. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/context.py +1 -0
  7. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/runner.py +2 -2
  8. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/map_tree.py +18 -2
  9. modwire_cli-1.1.0/tests/test_cli.py +122 -0
  10. modwire_cli-1.0.1/tests/test_cli.py +0 -60
  11. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/.gitignore +0 -0
  12. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/LICENSE +0 -0
  13. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/pyproject.toml +0 -0
  14. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/__init__.py +0 -0
  15. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/config.py +0 -0
  16. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/extraction.py +0 -0
  17. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/__init__.py +0 -0
  18. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/step.py +0 -0
  19. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/__init__.py +0 -0
  20. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/callables_insight.py +0 -0
  21. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/clusters_insight.py +0 -0
  22. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/coherence_insight.py +0 -0
  23. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/flow_violations.py +0 -0
  24. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/hotspots_insight.py +0 -0
  25. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/shape_violations.py +0 -0
  26. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/success.py +0 -0
  27. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/unknown_files_warning.py +0 -0
  28. {modwire_cli-1.0.1 → modwire_cli-1.1.0}/src/modwire_cli/pipeline/steps/unused_exports_insight.py +0 -0
  29. {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.1
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.1'
22
- __version_tuple__ = version_tuple = (1, 0, 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)
@@ -11,6 +11,7 @@ from rich.console import Console
11
11
  class ReportPipelineContext:
12
12
  reports: tuple[ReportNode, ...]
13
13
  console: Console
14
+ summary: bool = False
14
15
  failed: bool = False
15
16
 
16
17
  def has_report(self, report_type: type[ReportNode]) -> bool:
@@ -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
- self._add_groups(tree, "Modules", report.modules)
20
- self._add_groups(tree, "Layers", report.layers)
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