aja-codeintel 0.1.0__py3-none-any.whl
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.
- aja_codeintel-0.1.0.dist-info/METADATA +436 -0
- aja_codeintel-0.1.0.dist-info/RECORD +68 -0
- aja_codeintel-0.1.0.dist-info/WHEEL +5 -0
- aja_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
- aja_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
- aja_codeintel-0.1.0.dist-info/top_level.txt +1 -0
- codeintel_cli/__init__.py +1 -0
- codeintel_cli/__main__.py +4 -0
- codeintel_cli/cli.py +41 -0
- codeintel_cli/commands/__init__.py +1 -0
- codeintel_cli/commands/graph/__init__.py +18 -0
- codeintel_cli/commands/graph/deps_cmd.py +35 -0
- codeintel_cli/commands/graph/related_cmd.py +121 -0
- codeintel_cli/commands/graph/relsymbols_cmd.py +347 -0
- codeintel_cli/commands/graph/reverse_related_cmd.py +54 -0
- codeintel_cli/commands/nav/__init__.py +12 -0
- codeintel_cli/commands/nav/copy_cmd.py +101 -0
- codeintel_cli/commands/nav/open_cmd.py +18 -0
- codeintel_cli/commands/nav/where_cmd.py +21 -0
- codeintel_cli/commands/project/__init__.py +26 -0
- codeintel_cli/commands/project/context_cmd.py +326 -0
- codeintel_cli/commands/project/folder_cmd.py +51 -0
- codeintel_cli/commands/project/imports_cmd.py +90 -0
- codeintel_cli/commands/project/models_cmd.py +98 -0
- codeintel_cli/commands/project/modeltree_cmd.py +476 -0
- codeintel_cli/commands/project/new.py +0 -0
- codeintel_cli/commands/project/resolve_cmd.py +29 -0
- codeintel_cli/commands/project/scan_cmd.py +51 -0
- codeintel_cli/commands/project/servicemap_cmd.py +180 -0
- codeintel_cli/commands/project/tree_cmd.py +203 -0
- codeintel_cli/commands/project/version_cmd.py +14 -0
- codeintel_cli/context/java_context.py +180 -0
- codeintel_cli/context/java_rel.py +299 -0
- codeintel_cli/context/java_service.py +291 -0
- codeintel_cli/context/python_context.py +91 -0
- codeintel_cli/context/python_rel.py +251 -0
- codeintel_cli/context/python_service.py +205 -0
- codeintel_cli/core/fuzzy.py +72 -0
- codeintel_cli/core/opener.py +37 -0
- codeintel_cli/core/project.py +34 -0
- codeintel_cli/core/resolve_folder.py +68 -0
- codeintel_cli/core/resolve_model_target.py +92 -0
- codeintel_cli/core/resolve_target.py +53 -0
- codeintel_cli/core/timing.py +13 -0
- codeintel_cli/core/where.py +77 -0
- codeintel_cli/db/__init__.py +7 -0
- codeintel_cli/db/cache.py +224 -0
- codeintel_cli/db/operations.py +333 -0
- codeintel_cli/db/schema.py +102 -0
- codeintel_cli/errors.py +78 -0
- codeintel_cli/graph/__init__.py +1 -0
- codeintel_cli/graph/builder.py +149 -0
- codeintel_cli/graph/query.py +30 -0
- codeintel_cli/graph/traverse.py +49 -0
- codeintel_cli/lang/__init__.py +0 -0
- codeintel_cli/lang/java/__init__.py +0 -0
- codeintel_cli/lang/java/engine.py +18 -0
- codeintel_cli/lang/java/models.py +105 -0
- codeintel_cli/lang/java/resolve.py +49 -0
- codeintel_cli/lang/python/__init__.py +0 -0
- codeintel_cli/lang/python/engine.py +8 -0
- codeintel_cli/lang/python/models.py +86 -0
- codeintel_cli/lang/router.py +24 -0
- codeintel_cli/parser/imports.py +26 -0
- codeintel_cli/parser/resolve.py +49 -0
- codeintel_cli/parser/symbols.py +92 -0
- codeintel_cli/scanner/__init__.py +0 -0
- codeintel_cli/scanner/scanner.py +41 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import re
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ...core.resolve_target import resolve_target_file
|
|
9
|
+
from ...graph.builder import build_graph_with_counts, get_hub_files_by_ratio
|
|
10
|
+
from ...lang.router import extract_models_for_file
|
|
11
|
+
from ...lang.java.resolve import resolve_java_import_to_file
|
|
12
|
+
from ...parser.imports import extract_imports_from_file
|
|
13
|
+
from ...parser.resolve import resolve_import_to_file
|
|
14
|
+
from ...parser.symbols import extract_classes_from_file, extract_funcs_from_file
|
|
15
|
+
from ...db.cache import CacheManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
TYPE_RE = re.compile(r"\b(class|interface|enum|record)\s+([A-Za-z_][A-Za-z0-9_]*)\b")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _rel(p: Path, root: Path) -> str:
|
|
22
|
+
try:
|
|
23
|
+
return p.resolve().relative_to(root.resolve()).as_posix()
|
|
24
|
+
except Exception:
|
|
25
|
+
return p.resolve().as_posix()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _print_block(title: str, lines: list[str]) -> None:
|
|
29
|
+
if not lines:
|
|
30
|
+
return
|
|
31
|
+
typer.echo(title)
|
|
32
|
+
for x in lines:
|
|
33
|
+
typer.echo(f" {x}")
|
|
34
|
+
typer.echo("")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _layered_related(
|
|
38
|
+
graph: dict[Path, set[Path]],
|
|
39
|
+
start: Path,
|
|
40
|
+
depth: int,
|
|
41
|
+
include_reverse: bool,
|
|
42
|
+
hubs: set[Path],
|
|
43
|
+
) -> list[list[Path]]:
|
|
44
|
+
adj: dict[Path, set[Path]] = {k: set(v) for k, v in graph.items()}
|
|
45
|
+
|
|
46
|
+
if include_reverse:
|
|
47
|
+
rev: dict[Path, set[Path]] = {}
|
|
48
|
+
for src, deps in adj.items():
|
|
49
|
+
for dst in deps:
|
|
50
|
+
rev.setdefault(dst, set()).add(src)
|
|
51
|
+
for node, incoming in rev.items():
|
|
52
|
+
adj.setdefault(node, set()).update(incoming)
|
|
53
|
+
|
|
54
|
+
visited: set[Path] = {start}
|
|
55
|
+
q: deque[tuple[Path, int]] = deque([(start, 0)])
|
|
56
|
+
by_depth: dict[int, list[Path]] = {}
|
|
57
|
+
|
|
58
|
+
while q:
|
|
59
|
+
node, d = q.popleft()
|
|
60
|
+
if d == depth:
|
|
61
|
+
continue
|
|
62
|
+
for nxt in adj.get(node, set()):
|
|
63
|
+
if nxt in visited:
|
|
64
|
+
continue
|
|
65
|
+
if nxt in hubs:
|
|
66
|
+
continue
|
|
67
|
+
visited.add(nxt)
|
|
68
|
+
nd = d + 1
|
|
69
|
+
by_depth.setdefault(nd, []).append(nxt)
|
|
70
|
+
q.append((nxt, nd))
|
|
71
|
+
|
|
72
|
+
layers: list[list[Path]] = []
|
|
73
|
+
for d in range(1, depth + 1):
|
|
74
|
+
layers.append(sorted(by_depth.get(d, [])))
|
|
75
|
+
return layers
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _is_domain_model_path(p: Path) -> bool:
|
|
79
|
+
parts = {x.lower() for x in p.parts}
|
|
80
|
+
return bool(parts & {"model", "models", "entity", "entities"})
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _models_lines(scope: list[Path], root: Path) -> list[str]:
|
|
84
|
+
out: list[str] = []
|
|
85
|
+
for f in scope:
|
|
86
|
+
if not _is_domain_model_path(f):
|
|
87
|
+
continue
|
|
88
|
+
defs = extract_models_for_file(f, root)
|
|
89
|
+
if not defs:
|
|
90
|
+
continue
|
|
91
|
+
out.append(f"• {_rel(f, root)}")
|
|
92
|
+
for m in defs:
|
|
93
|
+
out.append(f" {m.name}")
|
|
94
|
+
for field in getattr(m, "fields", []):
|
|
95
|
+
out.append(f" {field.name}: {field.type}")
|
|
96
|
+
out.append("")
|
|
97
|
+
while out and out[-1] == "":
|
|
98
|
+
out.pop()
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _py_target_imports(target: Path, root: Path) -> tuple[list[str], list[str]]:
|
|
103
|
+
related: set[str] = set()
|
|
104
|
+
non_related: set[str] = set()
|
|
105
|
+
|
|
106
|
+
for mod, level in extract_imports_from_file(target):
|
|
107
|
+
mod = mod or ""
|
|
108
|
+
s = ("." * level + mod) if level else mod
|
|
109
|
+
if not s:
|
|
110
|
+
continue
|
|
111
|
+
resolved = resolve_import_to_file(mod, level, target, root)
|
|
112
|
+
if resolved is None:
|
|
113
|
+
non_related.add(s)
|
|
114
|
+
else:
|
|
115
|
+
related.add(s)
|
|
116
|
+
|
|
117
|
+
return sorted(related), sorted(non_related)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _py_related_imports(files: list[Path]) -> list[str]:
|
|
121
|
+
out: set[str] = set()
|
|
122
|
+
for f in files:
|
|
123
|
+
if f.suffix.lower() != ".py":
|
|
124
|
+
continue
|
|
125
|
+
for mod, level in extract_imports_from_file(f):
|
|
126
|
+
mod = mod or ""
|
|
127
|
+
s = ("." * level + mod) if level else mod
|
|
128
|
+
if s:
|
|
129
|
+
out.add(s)
|
|
130
|
+
return sorted(out)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _py_types(path: Path) -> list[str]:
|
|
134
|
+
out: list[str] = []
|
|
135
|
+
for c in extract_classes_from_file(path):
|
|
136
|
+
if not c.name.startswith("_"):
|
|
137
|
+
out.append(f"{c.name} (class)")
|
|
138
|
+
for fn in extract_funcs_from_file(path):
|
|
139
|
+
if fn.name.startswith("_") or fn.name.startswith("register_"):
|
|
140
|
+
continue
|
|
141
|
+
out.append(f"{fn.name} (func)")
|
|
142
|
+
return out
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _java_read(path: Path) -> str:
|
|
146
|
+
try:
|
|
147
|
+
return path.read_text(encoding="utf-8", errors="ignore")
|
|
148
|
+
except Exception:
|
|
149
|
+
return ""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _java_imports(path: Path) -> list[str]:
|
|
153
|
+
text = _java_read(path)
|
|
154
|
+
out: list[str] = []
|
|
155
|
+
for line in text.splitlines():
|
|
156
|
+
s = line.strip()
|
|
157
|
+
if not s.startswith("import "):
|
|
158
|
+
continue
|
|
159
|
+
if s.startswith("import static "):
|
|
160
|
+
continue
|
|
161
|
+
s = s.removeprefix("import ").rstrip(";").strip()
|
|
162
|
+
if not s or s.endswith(".*"):
|
|
163
|
+
continue
|
|
164
|
+
out.append(s)
|
|
165
|
+
return out
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _java_split_imports(imports: list[str], root: Path) -> tuple[list[str], list[str]]:
|
|
169
|
+
proj: set[str] = set()
|
|
170
|
+
ext: set[str] = set()
|
|
171
|
+
for imp in imports:
|
|
172
|
+
if resolve_java_import_to_file(imp, root) is None:
|
|
173
|
+
ext.add(imp)
|
|
174
|
+
else:
|
|
175
|
+
proj.add(imp)
|
|
176
|
+
return sorted(proj), sorted(ext)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _java_target_imports(target: Path, root: Path) -> tuple[list[str], list[str]]:
|
|
180
|
+
return _java_split_imports(_java_imports(target), root)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _java_related_imports(files: list[Path]) -> list[str]:
|
|
184
|
+
out: set[str] = set()
|
|
185
|
+
for f in files:
|
|
186
|
+
if f.suffix.lower() != ".java":
|
|
187
|
+
continue
|
|
188
|
+
for imp in _java_imports(f):
|
|
189
|
+
out.add(imp)
|
|
190
|
+
return sorted(out)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _java_types(path: Path) -> list[str]:
|
|
194
|
+
text = _java_read(path)
|
|
195
|
+
if not text:
|
|
196
|
+
return []
|
|
197
|
+
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S)
|
|
198
|
+
text = re.sub(r"//.*?$", "", text, flags=re.M)
|
|
199
|
+
m = TYPE_RE.search(text)
|
|
200
|
+
if not m:
|
|
201
|
+
return []
|
|
202
|
+
kind, name = m.group(1), m.group(2)
|
|
203
|
+
return [f"{name} ({kind})"]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _types_section(title: str, files: list[Path], root: Path, lang: str) -> None:
|
|
207
|
+
lines: list[str] = []
|
|
208
|
+
for f in files:
|
|
209
|
+
ts = _py_types(f) if lang == "python" else _java_types(f)
|
|
210
|
+
if not ts:
|
|
211
|
+
continue
|
|
212
|
+
lines.append(f"• {_rel(f, root)}")
|
|
213
|
+
for t in ts:
|
|
214
|
+
lines.append(f" {t}")
|
|
215
|
+
if lines:
|
|
216
|
+
typer.echo(title)
|
|
217
|
+
for x in lines:
|
|
218
|
+
typer.echo(x)
|
|
219
|
+
typer.echo("")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def register_context(app: typer.Typer) -> None:
|
|
223
|
+
@app.command(help="Context bundle (layered). Default depth=1.")
|
|
224
|
+
def context(
|
|
225
|
+
new_file: str = typer.Argument(...),
|
|
226
|
+
root: str = typer.Option(".", "--root"),
|
|
227
|
+
depth: int = typer.Option(1, "--depth", "-d"),
|
|
228
|
+
forward_only: bool = typer.Option(False, "--forward-only"),
|
|
229
|
+
include_hubs: bool = typer.Option(False, "--include-hubs"),
|
|
230
|
+
use_sqlite_cache: bool = typer.Option(True, "--use-sqlite-cache/--no-sqlite-cache"),
|
|
231
|
+
):
|
|
232
|
+
target, root_path = resolve_target_file(new_file, root=root)
|
|
233
|
+
|
|
234
|
+
if use_sqlite_cache:
|
|
235
|
+
with CacheManager(root_path) as cache:
|
|
236
|
+
if cache.needs_rescan():
|
|
237
|
+
cache.scan_project(verbose=False)
|
|
238
|
+
files = cache.get_cached_files()
|
|
239
|
+
else:
|
|
240
|
+
files = []
|
|
241
|
+
for p in root_path.rglob("*"):
|
|
242
|
+
if p.is_file() and p.suffix.lower() in {".py", ".java"}:
|
|
243
|
+
files.append(p.resolve())
|
|
244
|
+
files = sorted(set(files))
|
|
245
|
+
|
|
246
|
+
graph, dependents_count = build_graph_with_counts(
|
|
247
|
+
files,
|
|
248
|
+
root_path,
|
|
249
|
+
use_sqlite_cache=use_sqlite_cache,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
hubs: set[Path] = set()
|
|
253
|
+
if not include_hubs:
|
|
254
|
+
hubs = get_hub_files_by_ratio(dependents_count, len(files), 0.5)
|
|
255
|
+
|
|
256
|
+
layers = _layered_related(
|
|
257
|
+
graph=graph,
|
|
258
|
+
start=target.resolve(),
|
|
259
|
+
depth=depth,
|
|
260
|
+
include_reverse=not forward_only,
|
|
261
|
+
hubs=hubs,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
same_folder = sorted(
|
|
265
|
+
f.resolve()
|
|
266
|
+
for f in files
|
|
267
|
+
if f.parent.resolve() == target.parent.resolve()
|
|
268
|
+
and f.resolve() != target.resolve()
|
|
269
|
+
and f.name != "__init__.py"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
banned: set[Path] = {target.resolve(), *[p.resolve() for p in same_folder]}
|
|
273
|
+
new_layers: list[list[Path]] = []
|
|
274
|
+
for layer in layers:
|
|
275
|
+
clean: list[Path] = []
|
|
276
|
+
for p in layer:
|
|
277
|
+
pr = p.resolve()
|
|
278
|
+
if pr in banned:
|
|
279
|
+
continue
|
|
280
|
+
clean.append(pr)
|
|
281
|
+
banned.add(pr)
|
|
282
|
+
new_layers.append(clean)
|
|
283
|
+
layers = new_layers
|
|
284
|
+
|
|
285
|
+
scope_all: list[Path] = [*same_folder]
|
|
286
|
+
for layer in layers:
|
|
287
|
+
scope_all.extend([p.resolve() for p in layer])
|
|
288
|
+
|
|
289
|
+
lang = "java" if target.suffix.lower() == ".java" else "python"
|
|
290
|
+
|
|
291
|
+
typer.echo("")
|
|
292
|
+
typer.echo("CONTEXT")
|
|
293
|
+
typer.echo(f"Target: {_rel(target, root_path)}")
|
|
294
|
+
typer.echo(f"Depth: {depth} Mode: {'forward-only' if forward_only else 'forward+reverse'}")
|
|
295
|
+
typer.echo("")
|
|
296
|
+
|
|
297
|
+
model_lines = _models_lines(scope_all, root_path)
|
|
298
|
+
if model_lines:
|
|
299
|
+
typer.echo("MODELS")
|
|
300
|
+
for x in model_lines:
|
|
301
|
+
typer.echo(x)
|
|
302
|
+
typer.echo("")
|
|
303
|
+
|
|
304
|
+
if lang == "java":
|
|
305
|
+
t_proj, t_ext = _java_target_imports(target, root_path)
|
|
306
|
+
_print_block("TARGET IMPORTS", t_proj)
|
|
307
|
+
_print_block("TARGET NON-RELATED IMPORTS", t_ext)
|
|
308
|
+
_types_section("TARGET TYPES", [target], root_path, lang)
|
|
309
|
+
else:
|
|
310
|
+
t_rel, t_non = _py_target_imports(target, root_path)
|
|
311
|
+
_print_block("TARGET IMPORTS", t_rel)
|
|
312
|
+
_print_block("TARGET NON-RELATED IMPORTS", t_non)
|
|
313
|
+
|
|
314
|
+
_types_section("SAME FOLDER TYPES", same_folder, root_path, lang)
|
|
315
|
+
|
|
316
|
+
for i, layer in enumerate(layers, 1):
|
|
317
|
+
if not layer:
|
|
318
|
+
continue
|
|
319
|
+
_types_section(f"RELATED LEVEL {i} TYPES", layer, root_path, lang)
|
|
320
|
+
if lang == "java":
|
|
321
|
+
all_imps = _java_related_imports(layer)
|
|
322
|
+
proj, ext = _java_split_imports(all_imps, root_path)
|
|
323
|
+
_print_block(f"RELATED LEVEL {i} PROJECT IMPORTS", proj)
|
|
324
|
+
_print_block(f"RELATED LEVEL {i} EXTERNAL IMPORTS", ext)
|
|
325
|
+
else:
|
|
326
|
+
_print_block(f"RELATED LEVEL {i} IMPORTS", _py_related_imports(layer))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from ...core.project import is_skipped_path
|
|
7
|
+
from ...core.resolve_folder import resolve_target_folder
|
|
8
|
+
from ...scanner.scanner import find_source_files
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register_folder(app: typer.Typer) -> None:
|
|
12
|
+
@app.command(help="List source files under a folder (recursive).")
|
|
13
|
+
def folder(
|
|
14
|
+
folder: str = typer.Argument(..., help="Folder path or name (supports fuzzy)"),
|
|
15
|
+
recursive: bool = typer.Option(True, "--recursive/--no-recursive"),
|
|
16
|
+
relative: bool = typer.Option(True, "--relative/--absolute"),
|
|
17
|
+
lang: str = typer.Option("all", "--lang", help="all | python | java"),
|
|
18
|
+
):
|
|
19
|
+
folder_path, root = resolve_target_folder(folder)
|
|
20
|
+
|
|
21
|
+
lang = lang.lower().strip()
|
|
22
|
+
if lang not in {"all", "python", "java"}:
|
|
23
|
+
raise typer.BadParameter("Invalid --lang. Use: all | python | java")
|
|
24
|
+
|
|
25
|
+
exts = (".py", ".java") if lang == "all" else (".py",) if lang == "python" else (".java",)
|
|
26
|
+
|
|
27
|
+
if recursive:
|
|
28
|
+
files = find_source_files(folder_path, exts)
|
|
29
|
+
else:
|
|
30
|
+
files = []
|
|
31
|
+
for ext in exts:
|
|
32
|
+
for p in folder_path.glob(f"*{ext}"):
|
|
33
|
+
if not p.is_file():
|
|
34
|
+
continue
|
|
35
|
+
if is_skipped_path(p):
|
|
36
|
+
continue
|
|
37
|
+
files.append(p.resolve())
|
|
38
|
+
files = sorted(set(files))
|
|
39
|
+
|
|
40
|
+
if not files:
|
|
41
|
+
typer.echo("(No matching files found.)")
|
|
42
|
+
raise typer.Exit(0)
|
|
43
|
+
|
|
44
|
+
for f in files:
|
|
45
|
+
if relative:
|
|
46
|
+
try:
|
|
47
|
+
typer.echo(str(f.relative_to(root)))
|
|
48
|
+
except Exception:
|
|
49
|
+
typer.echo(str(f))
|
|
50
|
+
else:
|
|
51
|
+
typer.echo(str(f))
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from ...errors import InvalidPathError
|
|
7
|
+
from ...parser.imports import extract_imports_from_file
|
|
8
|
+
from ...core.resolve_target import resolve_target_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _java_imports(path: Path) -> list[str]:
|
|
12
|
+
try:
|
|
13
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
14
|
+
except Exception:
|
|
15
|
+
return []
|
|
16
|
+
|
|
17
|
+
out: list[str] = []
|
|
18
|
+
for line in text.splitlines():
|
|
19
|
+
s = line.strip()
|
|
20
|
+
if not s.startswith("import "):
|
|
21
|
+
continue
|
|
22
|
+
if s.startswith("import static "):
|
|
23
|
+
continue
|
|
24
|
+
s = s.removeprefix("import ").rstrip(";").strip()
|
|
25
|
+
if not s or s.endswith(".*"):
|
|
26
|
+
continue
|
|
27
|
+
out.append(s)
|
|
28
|
+
return out
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register_imports(app: typer.Typer) -> None:
|
|
32
|
+
@app.command(help="Print local imports found in a file (python/java).")
|
|
33
|
+
def imports(
|
|
34
|
+
file: str = typer.Argument(..., help="File or fuzzy name (.py or .java)."),
|
|
35
|
+
java_stdlib: bool = typer.Option(
|
|
36
|
+
False,
|
|
37
|
+
"--java-stdlib",
|
|
38
|
+
help="Include Java/JDK imports (java.*, javax.*, jakarta.*, etc.).",
|
|
39
|
+
),
|
|
40
|
+
):
|
|
41
|
+
q = (file or "").strip()
|
|
42
|
+
if not q:
|
|
43
|
+
raise InvalidPathError(message="File not found", path=Path(file))
|
|
44
|
+
|
|
45
|
+
target, _ = resolve_target_file(q)
|
|
46
|
+
|
|
47
|
+
if not target.exists() or not target.is_file():
|
|
48
|
+
raise InvalidPathError(message="File not found", path=target)
|
|
49
|
+
|
|
50
|
+
ext = target.suffix.lower()
|
|
51
|
+
|
|
52
|
+
if ext == ".py":
|
|
53
|
+
mods = extract_imports_from_file(target)
|
|
54
|
+
rels = [(m, lvl) for (m, lvl) in mods if lvl > 0]
|
|
55
|
+
|
|
56
|
+
if not rels:
|
|
57
|
+
typer.echo("No local relative imports found.")
|
|
58
|
+
raise typer.Exit(0)
|
|
59
|
+
|
|
60
|
+
for module, level in rels:
|
|
61
|
+
dots = "." * level
|
|
62
|
+
typer.echo(f"{dots}{module}" if module else dots)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
if ext == ".java":
|
|
66
|
+
imps = _java_imports(target)
|
|
67
|
+
|
|
68
|
+
if not java_stdlib:
|
|
69
|
+
imps = [
|
|
70
|
+
x
|
|
71
|
+
for x in imps
|
|
72
|
+
if not (
|
|
73
|
+
x.startswith("java.")
|
|
74
|
+
or x.startswith("javax.")
|
|
75
|
+
or x.startswith("jakarta.")
|
|
76
|
+
or x.startswith("org.junit.")
|
|
77
|
+
or x.startswith("org.springframework.")
|
|
78
|
+
)
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
if not imps:
|
|
82
|
+
typer.echo("No Java imports found.")
|
|
83
|
+
raise typer.Exit(0)
|
|
84
|
+
|
|
85
|
+
for x in imps:
|
|
86
|
+
typer.echo(x)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
typer.echo("Unsupported file type. Use .py or .java.")
|
|
90
|
+
raise typer.Exit(2)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from ...core.project import find_project_root
|
|
7
|
+
from ...scanner.scanner import find_all_supported_files
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _import_extractors():
|
|
11
|
+
py = None
|
|
12
|
+
jv = None
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from ...lang.python.models import extract_python_models as _py # type: ignore
|
|
16
|
+
|
|
17
|
+
py = _py
|
|
18
|
+
except Exception:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from ...context.python_context import extract_python_models as _py # type: ignore
|
|
23
|
+
|
|
24
|
+
py = _py
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from ...lang.java.models import extract_java_models as _jv # type: ignore
|
|
30
|
+
|
|
31
|
+
jv = _jv
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from ...context.java_context import extract_java_models as _jv # type: ignore
|
|
37
|
+
|
|
38
|
+
jv = _jv
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
return py, jv
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _is_model_path(rel: Path) -> bool:
|
|
46
|
+
keys = {"model", "models", "entity", "entities", "domain", "domains"}
|
|
47
|
+
parts = [p.lower() for p in rel.parts]
|
|
48
|
+
if "src" in parts and "test" in parts:
|
|
49
|
+
return False
|
|
50
|
+
return any(part in keys for part in parts)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def register_models(app: typer.Typer) -> None:
|
|
54
|
+
@app.command(help="List domain models (from model/entity folders only).")
|
|
55
|
+
def models(
|
|
56
|
+
root: str = typer.Argument(".", help="Project root folder"),
|
|
57
|
+
):
|
|
58
|
+
root_path = Path(root).resolve()
|
|
59
|
+
project_root = find_project_root(root_path)
|
|
60
|
+
|
|
61
|
+
extract_python_models, extract_java_models = _import_extractors()
|
|
62
|
+
|
|
63
|
+
files = find_all_supported_files(project_root)
|
|
64
|
+
|
|
65
|
+
candidates: list[Path] = []
|
|
66
|
+
pr = project_root.resolve()
|
|
67
|
+
for f in files:
|
|
68
|
+
fr = f.resolve()
|
|
69
|
+
try:
|
|
70
|
+
rel = fr.relative_to(pr)
|
|
71
|
+
except Exception:
|
|
72
|
+
continue
|
|
73
|
+
if _is_model_path(rel):
|
|
74
|
+
candidates.append(fr)
|
|
75
|
+
|
|
76
|
+
out: list[str] = []
|
|
77
|
+
|
|
78
|
+
for f in sorted(candidates, key=lambda x: str(x).lower()):
|
|
79
|
+
ext = f.suffix.lower()
|
|
80
|
+
|
|
81
|
+
if ext == ".py" and extract_python_models is not None:
|
|
82
|
+
defs = extract_python_models(f, project_root)
|
|
83
|
+
elif ext == ".java" and extract_java_models is not None:
|
|
84
|
+
defs = extract_java_models(f, project_root)
|
|
85
|
+
else:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
for d in defs:
|
|
89
|
+
out.append(f"{d.name} ({d.kind}) {d.file}")
|
|
90
|
+
for fld in d.fields:
|
|
91
|
+
out.append(f" - {fld.name}: {fld.type}")
|
|
92
|
+
|
|
93
|
+
if not out:
|
|
94
|
+
typer.echo("No domain models found.")
|
|
95
|
+
raise typer.Exit(0)
|
|
96
|
+
|
|
97
|
+
for line in out:
|
|
98
|
+
typer.echo(line)
|