#!/usr/bin/env python3
|
"""Locate likely RSF backend files for a feature/module keyword.
|
|
Usage:
|
python skills/rsf-server-maintainer/scripts/locate_module.py basStation
|
python skills/rsf-server-maintainer/scripts/locate_module.py order --repo C:/env/code/wms-master/rsf-server
|
"""
|
|
from __future__ import annotations
|
|
import argparse
|
import re
|
from pathlib import Path
|
from typing import Iterable, List, Tuple
|
|
|
def parse_args() -> argparse.Namespace:
|
parser = argparse.ArgumentParser(
|
description="Locate related controller/service/mapper/entity/XML files by keyword.")
|
parser.add_argument("keyword", help="Module keyword, for example: basStation, order, user")
|
parser.add_argument(
|
"--repo",
|
default=str(Path(__file__).resolve().parents[3]),
|
help="Path to rsf-server repository root (default: auto-detect from script location)",
|
)
|
parser.add_argument(
|
"--limit",
|
type=int,
|
default=80,
|
help="Max results per section (default: 80)",
|
)
|
return parser.parse_args()
|
|
|
def normalize_keyword(value: str) -> str:
|
return value.strip().lower()
|
|
|
def collect_files(root: Path, patterns: Iterable[str]) -> List[Path]:
|
files: List[Path] = []
|
for pattern in patterns:
|
files.extend(root.glob(pattern))
|
return [p for p in files if p.is_file()]
|
|
|
def find_name_matches(files: Iterable[Path], keyword: str) -> List[Path]:
|
matches = []
|
for file in files:
|
text = str(file).lower()
|
if keyword in text:
|
matches.append(file)
|
return sorted(set(matches))
|
|
|
def find_line_matches(files: Iterable[Path], keyword: str, pattern: re.Pattern[str]) -> List[Tuple[Path, int, str]]:
|
out: List[Tuple[Path, int, str]] = []
|
for file in files:
|
try:
|
content = file.read_text(encoding="utf-8", errors="ignore").splitlines()
|
except OSError:
|
continue
|
for index, line in enumerate(content, start=1):
|
lower = line.lower()
|
if keyword in lower and pattern.search(line):
|
out.append((file, index, line.strip()))
|
return out
|
|
|
def print_file_section(title: str, repo: Path, files: List[Path], limit: int) -> None:
|
print(f"\n[{title}] {len(files)}")
|
for path in files[:limit]:
|
rel = path.relative_to(repo)
|
print(f" - {rel.as_posix()}")
|
if len(files) > limit:
|
print(f" ... ({len(files) - limit} more)")
|
|
|
def print_line_section(title: str, repo: Path, rows: List[Tuple[Path, int, str]], limit: int) -> None:
|
print(f"\n[{title}] {len(rows)}")
|
for file, line_no, line in rows[:limit]:
|
rel = file.relative_to(repo)
|
print(f" - {rel.as_posix()}:{line_no}: {line}")
|
if len(rows) > limit:
|
print(f" ... ({len(rows) - limit} more)")
|
|
|
def main() -> int:
|
args = parse_args()
|
repo = Path(args.repo).resolve()
|
keyword = normalize_keyword(args.keyword)
|
|
if not keyword:
|
raise SystemExit("keyword must not be empty")
|
|
java_root = repo / "src/main/java/com/vincent/rsf/server"
|
mapper_root = repo / "src/main/resources/mapper"
|
|
if not java_root.exists() or not mapper_root.exists():
|
raise SystemExit(f"Invalid repo root for rsf-server: {repo}")
|
|
java_files = collect_files(
|
repo,
|
[
|
"src/main/java/com/vincent/rsf/server/**/*.java",
|
"src/main/resources/mapper/**/*.xml",
|
],
|
)
|
|
name_matches = find_name_matches(java_files, keyword)
|
|
controller_files = collect_files(repo, ["src/main/java/com/vincent/rsf/server/**/controller/**/*.java"])
|
mapping_pattern = re.compile(r"@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping)")
|
authority_pattern = re.compile(r"@PreAuthorize")
|
|
endpoint_rows = find_line_matches(controller_files, keyword, mapping_pattern)
|
authority_rows = find_line_matches(controller_files, keyword, authority_pattern)
|
|
print(f"repo: {repo}")
|
print(f"keyword: {keyword}")
|
|
print_file_section("Path matches", repo, name_matches, args.limit)
|
print_line_section("Endpoint annotation matches", repo, endpoint_rows, args.limit)
|
print_line_section("Authority annotation matches", repo, authority_rows, args.limit)
|
|
return 0
|
|
|
if __name__ == "__main__":
|
raise SystemExit(main())
|