code2schema 0.1.2__tar.gz → 0.1.4__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 (23) hide show
  1. {code2schema-0.1.2 → code2schema-0.1.4}/PKG-INFO +6 -6
  2. {code2schema-0.1.2 → code2schema-0.1.4}/README.md +5 -5
  3. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/__init__.py +1 -1
  4. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/analyzer/events.py +29 -23
  5. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/cli.py +104 -44
  6. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema.egg-info/PKG-INFO +6 -6
  7. {code2schema-0.1.2 → code2schema-0.1.4}/pyproject.toml +1 -1
  8. {code2schema-0.1.2 → code2schema-0.1.4}/tests/test_code2schema.py +47 -0
  9. {code2schema-0.1.2 → code2schema-0.1.4}/LICENSE +0 -0
  10. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/analyzer/__init__.py +0 -0
  11. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/analyzer/cqrs.py +0 -0
  12. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/analyzer/graph.py +0 -0
  13. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/codegen/__init__.py +0 -0
  14. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/codegen/visualizer.py +0 -0
  15. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/core/__init__.py +0 -0
  16. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/core/extractor.py +0 -0
  17. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema/core/models.py +0 -0
  18. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema.egg-info/SOURCES.txt +0 -0
  19. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema.egg-info/dependency_links.txt +0 -0
  20. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema.egg-info/entry_points.txt +0 -0
  21. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema.egg-info/requires.txt +0 -0
  22. {code2schema-0.1.2 → code2schema-0.1.4}/code2schema.egg-info/top_level.txt +0 -0
  23. {code2schema-0.1.2 → code2schema-0.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2schema
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema
5
5
  License: Apache-2.0
6
6
  Keywords: ast,cqrs,static-analysis,code-quality,schema
@@ -34,13 +34,13 @@ Dynamic: license-file
34
34
 
35
35
  ## AI Cost Tracking
36
36
 
37
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.2-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.15-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
37
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
39
39
 
40
- - 🤖 **LLM usage:** $0.1500 (1 commits)
41
- - 👤 **Human dev:** ~$200 (2.0h @ $100/h, 30min dedup)
40
+ - 🤖 **LLM usage:** $0.4500 (3 commits)
41
+ - 👤 **Human dev:** ~$350 (3.5h @ $100/h, 30min dedup)
42
42
 
43
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
43
+ Generated on 2026-05-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
44
44
 
45
45
  ---
46
46
 
@@ -3,13 +3,13 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.2-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.15-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $0.1500 (1 commits)
10
- - 👤 **Human dev:** ~$200 (2.0h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $0.4500 (3 commits)
10
+ - 👤 **Human dev:** ~$350 (3.5h @ $100/h, 30min dedup)
11
11
 
12
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
12
+ Generated on 2026-05-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
@@ -7,4 +7,4 @@ __all__ = [
7
7
  "SchemaIR", "CQRSRole", "FunctionIR", "ModuleIR",
8
8
  "extract_project", "extract_module", "analyze",
9
9
  ]
10
- __version__ = "0.1.2"
10
+ __version__ = "0.1.4"
@@ -72,39 +72,33 @@ class EventModel:
72
72
 
73
73
  # ── Inference ─────────────────────────────────────────────────────────────────
74
74
 
75
- def infer_event_model(modules: List[ModuleIR]) -> EventModel:
76
- """Przechodzi po IR i buduje model zdarzeń."""
77
- model = EventModel()
78
-
79
- all_funcs: dict[str, FunctionIR] = {}
80
- for mod in modules:
81
- for f in mod.functions:
82
- all_funcs[f.qualified_name] = f
83
- all_funcs[f.name] = f # short alias
84
-
85
- # Krok 1: znajdź emiterów
86
- emitters: dict[str, DomainEvent] = {}
75
+ def _find_emitters(modules: List[ModuleIR]) -> List[DomainEvent]:
76
+ """Krok 1: znajdź funkcje emitujące zdarzenia."""
77
+ events: List[DomainEvent] = []
87
78
  for mod in modules:
88
79
  for f in mod.functions:
89
80
  if _EVENT_EMIT_PATTERNS.search(f.name) or any(
90
81
  _EVENT_EMIT_PATTERNS.search(c) for c in f.calls
91
82
  ):
92
83
  event_name = _derive_event_name(f.name)
93
- ev = DomainEvent(name=event_name, emitted_by=f.qualified_name)
94
- emitters[f.name] = ev
95
- model.events.append(ev)
84
+ events.append(DomainEvent(name=event_name, emitted_by=f.qualified_name))
85
+ return events
96
86
 
97
- # Krok 2: znajdź handlerów
87
+
88
+ def _find_handlers(events: List[DomainEvent], modules: List[ModuleIR]) -> None:
89
+ """Krok 2: dopasuj handlery do zdarzeń (mutuje events in-place)."""
98
90
  for mod in modules:
99
91
  for f in mod.functions:
100
92
  if _EVENT_HANDLER_PATTERNS.search(f.name):
101
- # sprawdź, czy handler pasuje do jakiegoś zdarzenia
102
- for ev in model.events:
93
+ for ev in events:
103
94
  keyword = ev.name.lower().replace("event", "").strip()
104
95
  if keyword and keyword in f.name.lower():
105
96
  ev.handled_by.append(f.qualified_name)
106
97
 
107
- # Krok 3: command handlers
98
+
99
+ def _find_command_handlers(modules: List[ModuleIR]) -> List[CommandHandler]:
100
+ """Krok 3: znajdź command handlery i ich emitowane zdarzenia."""
101
+ commands: List[CommandHandler] = []
108
102
  for mod in modules:
109
103
  for f in mod.functions:
110
104
  if f.role == CQRSRole.COMMAND or _AGGREGATE_PATTERNS.search(f.name):
@@ -114,24 +108,36 @@ def infer_event_model(modules: List[ModuleIR]) -> EventModel:
114
108
  if _EVENT_EMIT_PATTERNS.search(c)
115
109
  ]
116
110
  if emits or f.role == CQRSRole.COMMAND:
117
- model.commands.append(
111
+ commands.append(
118
112
  CommandHandler(
119
113
  name=f.qualified_name,
120
114
  command=f.name,
121
115
  emits=emits,
122
116
  )
123
117
  )
118
+ return commands
119
+
124
120
 
125
- # Krok 4: agregaty moduły z operacjami CRUD
121
+ def _find_aggregates(modules: List[ModuleIR]) -> List[str]:
122
+ """Krok 4: znajdź moduły będące agregatami (CRUD score >= 2)."""
123
+ aggregates: List[str] = []
126
124
  for mod in modules:
127
125
  crud_score = sum(
128
126
  1 for f in mod.functions
129
127
  if _AGGREGATE_PATTERNS.search(f.name)
130
128
  )
131
129
  if crud_score >= 2:
132
- model.aggregates.append(mod.name)
130
+ aggregates.append(mod.name)
131
+ return aggregates
133
132
 
134
- return model
133
+
134
+ def infer_event_model(modules: List[ModuleIR]) -> EventModel:
135
+ """Przechodzi po IR i buduje model zdarzeń."""
136
+ events = _find_emitters(modules)
137
+ _find_handlers(events, modules)
138
+ commands = _find_command_handlers(modules)
139
+ aggregates = _find_aggregates(modules)
140
+ return EventModel(commands=commands, events=events, aggregates=aggregates)
135
141
 
136
142
 
137
143
  def _derive_event_name(func_name: str) -> str:
@@ -20,22 +20,28 @@ from code2schema.codegen.visualizer import write_html
20
20
  from code2schema.core.extractor import extract_project
21
21
 
22
22
 
23
+ _SPECIAL_PROJECT_DIRS = {"backend", "frontend", "src", "app", "api"}
24
+ _GENERIC_PATH_PARTS = {"home", "github", "workspace", "projects", "src"}
25
+
26
+
23
27
  def _project_name_from_path(path: Path) -> str:
24
28
  """Extract project name from path (e.g., /path/to/c2004/backend -> c2004)."""
25
29
  # Try to find a meaningful name from the path
26
30
  parts = path.parts
27
31
  for i, part in enumerate(reversed(parts)):
28
- if part in ("backend", "frontend", "src", "app", "api"):
32
+ if part in _SPECIAL_PROJECT_DIRS:
29
33
  # Use parent directory name
30
34
  if i < len(parts) - 1:
31
35
  return parts[-(i + 2)]
32
36
  # Skip common generic names
33
- if part not in ("home", "github", "workspace", "projects", "src"):
37
+ if part not in _GENERIC_PATH_PARTS:
34
38
  return part
35
39
  return path.name or "project"
36
40
 
37
41
 
38
- def main(argv=None):
42
+ def _build_parser() -> argparse.ArgumentParser:
43
+ """Build CLI argument parser."""
44
+ from code2schema import __version__
39
45
  parser = argparse.ArgumentParser(prog="code2schema",
40
46
  description="Semantic Compiler: Code → CQRS → Schema / Proto / Graph")
41
47
  parser.add_argument("path")
@@ -54,41 +60,65 @@ def main(argv=None):
54
60
  parser.add_argument("--no-rules", action="store_true")
55
61
  parser.add_argument("--exclude", nargs="*", default=[])
56
62
  parser.add_argument("-q", "--quiet", action="store_true")
57
- args = parser.parse_args(argv)
63
+ parser.add_argument("-V", "--version", action="version",
64
+ version=f"%(prog)s {__version__}")
65
+ return parser
58
66
 
59
- root = Path(args.path)
60
- if not root.exists():
61
- print(f"ERROR: {root}", file=sys.stderr)
62
- return 1
63
67
 
64
- # Determine project name and output directory
68
+ def _resolve_output_dir(root: Path) -> Path:
69
+ if not root.is_dir():
70
+ return root.parent
71
+ if root.name in _SPECIAL_PROJECT_DIRS:
72
+ return root.parent
73
+ return root
74
+
75
+
76
+ def _resolve_required_path(out_dir: Path, value: str | None, default_name: str) -> Path:
77
+ return out_dir / (value or default_name)
78
+
79
+
80
+ def _resolve_optional_path(
81
+ out_dir: Path,
82
+ value: str | bool | None,
83
+ default_name: str,
84
+ ) -> Path | None:
85
+ if isinstance(value, str):
86
+ return out_dir / value
87
+ if value is True:
88
+ return out_dir / default_name
89
+ return None
90
+
91
+
92
+ def _resolve_paths(
93
+ args: argparse.Namespace,
94
+ ) -> tuple[Path, Path, Path | None, Path | None, Path | None]:
95
+ """Resolve root, project name and output file paths from CLI args."""
96
+ root = Path(args.path)
65
97
  proj_name = _project_name_from_path(root)
66
- # Use parent dir if path ends with backend/frontend/src/app/api, otherwise use path itself
67
- if root.is_dir() and root.name in ("backend", "frontend", "src", "app", "api"):
68
- out_dir = root.parent
69
- else:
70
- out_dir = root if root.is_dir() else root.parent
98
+ out_dir = _resolve_output_dir(root)
71
99
 
72
- out_path = (out_dir / args.out) if args.out else (out_dir / f"{proj_name}_schema.json")
73
- proto_path = out_dir / args.proto if isinstance(args.proto, str) else (out_dir / f"{proj_name}_api.proto" if args.proto is True else None)
74
- md_path = out_dir / args.md if isinstance(args.md, str) else (out_dir / f"{proj_name}_report.md" if args.md is True else None)
75
- html_path = out_dir / args.html if isinstance(args.html, str) else (out_dir / f"{proj_name}_viz.html" if args.html is True else None)
100
+ out_path = _resolve_required_path(out_dir, args.out, f"{proj_name}_schema.json")
101
+ proto_path = _resolve_optional_path(out_dir, args.proto, f"{proj_name}_api.proto")
102
+ md_path = _resolve_optional_path(out_dir, args.md, f"{proj_name}_report.md")
103
+ html_path = _resolve_optional_path(out_dir, args.html, f"{proj_name}_viz.html")
104
+ return root, out_path, proto_path, md_path, html_path
76
105
 
77
- t0 = time.perf_counter()
78
- if not args.quiet:
79
- print(f"⏳ Parsing: {root}")
80
106
 
107
+ def _run_extraction(args, root):
108
+ """Parse project and run CQRS analysis. Returns (modules, schema, G) or None on failure."""
81
109
  modules = extract_project(root, exclude=args.exclude or None)
82
110
  if not modules:
83
111
  print("ERROR: Brak plików .py.", file=sys.stderr)
84
- return 1
85
-
112
+ return None
86
113
  schema = analyze(modules)
87
114
  if args.no_rules:
88
115
  schema.rules = []
89
-
90
116
  G = build_rich_graph(schema)
117
+ return modules, schema, G
118
+
91
119
 
120
+ def _run_reports(args, modules, schema, G):
121
+ """Print optional reports: cycles, graph summary, events."""
92
122
  if args.cycles:
93
123
  cycles = detect_cycles(G)
94
124
  if cycles:
@@ -107,6 +137,9 @@ def main(argv=None):
107
137
  print("\n── Event Model ────────────────────────")
108
138
  print(em.summary())
109
139
 
140
+
141
+ def _write_outputs(args, schema, G, out_path, proto_path, md_path, html_path):
142
+ """Write all output files (JSON, proto, md, html, graphml, dot)."""
110
143
  write_json(schema, out_path)
111
144
  if proto_path:
112
145
  write_proto(schema, proto_path)
@@ -119,27 +152,54 @@ def main(argv=None):
119
152
  if args.dot:
120
153
  write_dot(G, Path(args.dot))
121
154
 
155
+
156
+ def _print_summary(modules, schema, G, t0, out_path, proto_path, md_path, html_path):
157
+ """Print final summary with metrics and output file names."""
158
+ funcs = schema.all_functions()
159
+ outputs = [f" → {out_path.name}"]
160
+ if proto_path:
161
+ outputs.append(f" → {proto_path.name}")
162
+ if md_path:
163
+ outputs.append(f" → {md_path.name}")
164
+ if html_path:
165
+ outputs.append(f" → {html_path.name}")
166
+ print(
167
+ f"\n✅ Gotowe ({time.perf_counter()-t0:.2f}s)\n"
168
+ f" Modules : {len(modules)}\n"
169
+ f" Functions: {len(funcs)}\n"
170
+ f" Queries : {len(schema.queries())}\n"
171
+ f" Commands : {len(schema.commands())}\n"
172
+ f" Orchest. : {len(schema.orchestrators())}\n"
173
+ f" Workflows: {len(schema.workflows)}\n"
174
+ f" Rules : {len(schema.rules)}\n"
175
+ f" Graph : {G.number_of_nodes()}N / {G.number_of_edges()}E\n"
176
+ + "\n".join(outputs)
177
+ )
178
+
179
+
180
+ def main(argv=None):
181
+ parser = _build_parser()
182
+ args = parser.parse_args(argv)
183
+
184
+ root, out_path, proto_path, md_path, html_path = _resolve_paths(args)
185
+ if not root.exists():
186
+ print(f"ERROR: {root}", file=sys.stderr)
187
+ return 1
188
+
189
+ t0 = time.perf_counter()
190
+ if not args.quiet:
191
+ print(f"⏳ Parsing: {root}")
192
+
193
+ result = _run_extraction(args, root)
194
+ if result is None:
195
+ return 1
196
+ modules, schema, G = result
197
+
198
+ _run_reports(args, modules, schema, G)
199
+ _write_outputs(args, schema, G, out_path, proto_path, md_path, html_path)
200
+
122
201
  if not args.quiet:
123
- funcs = schema.all_functions()
124
- outputs = [f" → {out_path.name}"]
125
- if proto_path:
126
- outputs.append(f" → {proto_path.name}")
127
- if md_path:
128
- outputs.append(f" → {md_path.name}")
129
- if html_path:
130
- outputs.append(f" → {html_path.name}")
131
- print(
132
- f"\n✅ Gotowe ({time.perf_counter()-t0:.2f}s)\n"
133
- f" Modules : {len(modules)}\n"
134
- f" Functions: {len(funcs)}\n"
135
- f" Queries : {len(schema.queries())}\n"
136
- f" Commands : {len(schema.commands())}\n"
137
- f" Orchest. : {len(schema.orchestrators())}\n"
138
- f" Workflows: {len(schema.workflows)}\n"
139
- f" Rules : {len(schema.rules)}\n"
140
- f" Graph : {G.number_of_nodes()}N / {G.number_of_edges()}E\n"
141
- + "\n".join(outputs)
142
- )
202
+ _print_summary(modules, schema, G, t0, out_path, proto_path, md_path, html_path)
143
203
  return 0
144
204
 
145
205
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2schema
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema
5
5
  License: Apache-2.0
6
6
  Keywords: ast,cqrs,static-analysis,code-quality,schema
@@ -34,13 +34,13 @@ Dynamic: license-file
34
34
 
35
35
  ## AI Cost Tracking
36
36
 
37
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.2-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.15-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
37
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
39
39
 
40
- - 🤖 **LLM usage:** $0.1500 (1 commits)
41
- - 👤 **Human dev:** ~$200 (2.0h @ $100/h, 30min dedup)
40
+ - 🤖 **LLM usage:** $0.4500 (3 commits)
41
+ - 👤 **Human dev:** ~$350 (3.5h @ $100/h, 30min dedup)
42
42
 
43
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
43
+ Generated on 2026-05-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
44
44
 
45
45
  ---
46
46
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2schema"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,7 @@
1
1
  """Testy jednostkowe code2schema."""
2
2
  from __future__ import annotations
3
3
 
4
+ import argparse
4
5
  import ast
5
6
  import textwrap
6
7
  from pathlib import Path
@@ -10,6 +11,7 @@ import pytest
10
11
  from code2schema.core.models import CQRSRole, SideEffect
11
12
  from code2schema.core.extractor import extract_module
12
13
  from code2schema.analyzer.cqrs import _infer_role, analyze, build_call_graph
14
+ from code2schema.cli import _resolve_paths
13
15
  from code2schema.codegen import to_json, to_proto, to_markdown
14
16
 
15
17
 
@@ -216,3 +218,48 @@ def test_event_model_summary(sample_module):
216
218
  summary = em.summary()
217
219
  assert "Commands" in summary
218
220
  assert "Domain Events" in summary
221
+
222
+
223
+ # ── CLI Paths ─────────────────────────────────────────────────────────────────
224
+
225
+ def test_resolve_paths_for_special_project_dir(tmp_path: Path):
226
+ backend_dir = tmp_path / "c2004" / "backend"
227
+ backend_dir.mkdir(parents=True)
228
+
229
+ args = argparse.Namespace(
230
+ path=str(backend_dir),
231
+ out=None,
232
+ proto=True,
233
+ md=True,
234
+ html=True,
235
+ )
236
+
237
+ root, out_path, proto_path, md_path, html_path = _resolve_paths(args)
238
+
239
+ assert root == backend_dir
240
+ assert out_path == backend_dir.parent / "c2004_schema.json"
241
+ assert proto_path == backend_dir.parent / "c2004_api.proto"
242
+ assert md_path == backend_dir.parent / "c2004_report.md"
243
+ assert html_path == backend_dir.parent / "c2004_viz.html"
244
+
245
+
246
+ def test_resolve_paths_for_file_input_with_custom_outputs(tmp_path: Path):
247
+ file_path = tmp_path / "repo" / "main.py"
248
+ file_path.parent.mkdir(parents=True)
249
+ file_path.write_text("def main():\n pass\n")
250
+
251
+ args = argparse.Namespace(
252
+ path=str(file_path),
253
+ out="schema.json",
254
+ proto="api.proto",
255
+ md=None,
256
+ html=None,
257
+ )
258
+
259
+ root, out_path, proto_path, md_path, html_path = _resolve_paths(args)
260
+
261
+ assert root == file_path
262
+ assert out_path == file_path.parent / "schema.json"
263
+ assert proto_path == file_path.parent / "api.proto"
264
+ assert md_path is None
265
+ assert html_path is None
File without changes
File without changes