@leejungkiin/awkit 1.7.1 → 1.7.4
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.
- package/bin/awk.js +576 -84
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +148 -167
- package/core/GEMINI.md.bak +149 -116
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +35 -2
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +144 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +47 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/project-identity/android.json +2 -2
- package/templates/project-identity/backend-nestjs.json +2 -2
- package/templates/project-identity/expo.json +2 -2
- package/templates/project-identity/ios.json +2 -2
- package/templates/project-identity/web-nextjs.json +2 -2
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- package/workflows/roles/safety-router.md +34 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Core Data / SwiftData Model Inspector
|
|
4
|
+
|
|
5
|
+
Parse .xcdatamodeld packages and @Model declarations from project source files.
|
|
6
|
+
Zero external dependencies — uses stdlib xml and regex.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python scripts/model_inspector.py --project-path /path/to/project
|
|
10
|
+
python scripts/model_inspector.py --project-path . --verbose
|
|
11
|
+
python scripts/model_inspector.py --project-path . --json
|
|
12
|
+
python scripts/model_inspector.py --project-path . --show-versions
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import plistlib
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from xml.etree import ElementTree
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ModelInspector:
|
|
25
|
+
"""Inspects Core Data and SwiftData models from project source files."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, project_path: str):
|
|
28
|
+
"""Initialize inspector.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
project_path: Path to Xcode project root directory
|
|
32
|
+
"""
|
|
33
|
+
self.project_path = Path(project_path).resolve()
|
|
34
|
+
|
|
35
|
+
def execute(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
core_data_only: bool = False,
|
|
39
|
+
swiftdata_only: bool = False,
|
|
40
|
+
show_versions: bool = False,
|
|
41
|
+
) -> tuple[bool, dict]:
|
|
42
|
+
"""Inspect models and return structured results.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
core_data_only: Skip SwiftData scanning
|
|
46
|
+
swiftdata_only: Skip Core Data scanning
|
|
47
|
+
show_versions: Include version history details
|
|
48
|
+
Returns:
|
|
49
|
+
(success, results_dict) tuple
|
|
50
|
+
"""
|
|
51
|
+
if not self.project_path.is_dir():
|
|
52
|
+
return False, {"error": f"Directory not found: {self.project_path}"}
|
|
53
|
+
|
|
54
|
+
results: dict = {"core_data": [], "swiftdata": []}
|
|
55
|
+
|
|
56
|
+
if not swiftdata_only:
|
|
57
|
+
packages = self._find_xcdatamodeld()
|
|
58
|
+
for package in packages:
|
|
59
|
+
parsed = self._parse_xcdatamodeld(package, show_versions=show_versions)
|
|
60
|
+
if parsed:
|
|
61
|
+
results["core_data"].append(parsed)
|
|
62
|
+
|
|
63
|
+
if not core_data_only:
|
|
64
|
+
models = self._find_swiftdata_models()
|
|
65
|
+
results["swiftdata"] = models
|
|
66
|
+
|
|
67
|
+
has_results = bool(results["core_data"]) or bool(results["swiftdata"])
|
|
68
|
+
return has_results, results
|
|
69
|
+
|
|
70
|
+
# === RAW SOURCE EXTRACTION ===
|
|
71
|
+
|
|
72
|
+
def get_raw_source(self, model_name: str) -> tuple[bool, str]:
|
|
73
|
+
"""Get raw source for a named model.
|
|
74
|
+
|
|
75
|
+
Searches SwiftData @Model classes first, then Core Data XML entities.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
model_name: Class or entity name to look up
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
(success, raw_source) tuple
|
|
82
|
+
"""
|
|
83
|
+
# Search SwiftData files
|
|
84
|
+
swift_files = sorted(self.project_path.rglob("*.swift"))
|
|
85
|
+
skip_dirs = {"DerivedData", "Pods", "Carthage"}
|
|
86
|
+
|
|
87
|
+
model_pattern = re.compile(
|
|
88
|
+
r"@Model\s*\n\s*(?:final\s+)?class\s+(\w+)",
|
|
89
|
+
re.MULTILINE,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
for swift_file in swift_files:
|
|
93
|
+
if any(
|
|
94
|
+
part in skip_dirs or part.startswith(".")
|
|
95
|
+
for part in swift_file.relative_to(self.project_path).parts
|
|
96
|
+
):
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
content = swift_file.read_text(encoding="utf-8")
|
|
101
|
+
except (OSError, UnicodeDecodeError):
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
for match in model_pattern.finditer(content):
|
|
105
|
+
if match.group(1) == model_name:
|
|
106
|
+
class_start = match.start()
|
|
107
|
+
body = self._extract_class_body(content, match.end())
|
|
108
|
+
if body is None:
|
|
109
|
+
continue
|
|
110
|
+
# Find closing brace position
|
|
111
|
+
brace_start = content.find("{", match.end())
|
|
112
|
+
depth = 0
|
|
113
|
+
end = brace_start
|
|
114
|
+
for i in range(brace_start, len(content)):
|
|
115
|
+
if content[i] == "{":
|
|
116
|
+
depth += 1
|
|
117
|
+
elif content[i] == "}":
|
|
118
|
+
depth -= 1
|
|
119
|
+
if depth == 0:
|
|
120
|
+
end = i + 1
|
|
121
|
+
break
|
|
122
|
+
rel_path = swift_file.relative_to(self.project_path)
|
|
123
|
+
raw = content[class_start:end]
|
|
124
|
+
return True, f"// {rel_path}\n{raw}"
|
|
125
|
+
|
|
126
|
+
# Search Core Data XML
|
|
127
|
+
for package in self._find_xcdatamodeld():
|
|
128
|
+
current_version = self._detect_current_version(package)
|
|
129
|
+
versions = [d for d in package.iterdir() if d.suffix == ".xcdatamodel"]
|
|
130
|
+
target = current_version or (versions[0].name if versions else None)
|
|
131
|
+
if not target:
|
|
132
|
+
continue
|
|
133
|
+
contents_path = package / target / "contents"
|
|
134
|
+
if not contents_path.exists():
|
|
135
|
+
continue
|
|
136
|
+
try:
|
|
137
|
+
tree = ElementTree.parse(contents_path)
|
|
138
|
+
except ElementTree.ParseError:
|
|
139
|
+
continue
|
|
140
|
+
for entity_elem in tree.getroot().findall("entity"):
|
|
141
|
+
if entity_elem.get("name") == model_name:
|
|
142
|
+
raw_xml = ElementTree.tostring(entity_elem, encoding="unicode")
|
|
143
|
+
return True, f"<!-- {package.name}/{target}/contents -->\n{raw_xml}"
|
|
144
|
+
|
|
145
|
+
return False, f"Model '{model_name}' not found"
|
|
146
|
+
|
|
147
|
+
# === CORE DATA PARSING ===
|
|
148
|
+
|
|
149
|
+
def _find_xcdatamodeld(self) -> list[Path]:
|
|
150
|
+
"""Find all .xcdatamodeld packages in the project."""
|
|
151
|
+
return sorted(self.project_path.rglob("*.xcdatamodeld"))
|
|
152
|
+
|
|
153
|
+
def _parse_xcdatamodeld(
|
|
154
|
+
self, package_path: Path, *, show_versions: bool = False
|
|
155
|
+
) -> dict | None:
|
|
156
|
+
"""Parse a .xcdatamodeld package into structured data.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
package_path: Path to the .xcdatamodeld directory
|
|
160
|
+
show_versions: Include all version details
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Parsed model data or None on error
|
|
164
|
+
"""
|
|
165
|
+
current_version = self._detect_current_version(package_path)
|
|
166
|
+
versions = sorted(
|
|
167
|
+
[d.name for d in package_path.iterdir() if d.suffix == ".xcdatamodel"],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
result: dict = {
|
|
171
|
+
"package": package_path.name,
|
|
172
|
+
"current_version": current_version,
|
|
173
|
+
"version_count": len(versions),
|
|
174
|
+
"entities": [],
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if show_versions:
|
|
178
|
+
result["versions"] = [{"name": v, "is_current": v == current_version} for v in versions]
|
|
179
|
+
|
|
180
|
+
# Parse the current version (or first available)
|
|
181
|
+
target_version = current_version or (versions[0] if versions else None)
|
|
182
|
+
if not target_version:
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
contents_path = package_path / target_version / "contents"
|
|
186
|
+
if not contents_path.exists():
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
entities = self._parse_contents_xml(contents_path)
|
|
190
|
+
result["entities"] = entities
|
|
191
|
+
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
def _detect_current_version(self, package_path: Path) -> str | None:
|
|
195
|
+
"""Read .xccurrentversion plist to find active model version.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
package_path: Path to the .xcdatamodeld directory
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Current version name or None
|
|
202
|
+
"""
|
|
203
|
+
plist_path = package_path / ".xccurrentversion"
|
|
204
|
+
if not plist_path.exists():
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
with open(plist_path, "rb") as f:
|
|
209
|
+
plist = plistlib.load(f)
|
|
210
|
+
return plist.get("_XCCurrentVersionName")
|
|
211
|
+
except Exception:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
def _parse_contents_xml(self, xml_path: Path) -> list[dict]:
|
|
215
|
+
"""Parse a Core Data contents XML file.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
xml_path: Path to the contents XML file
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
List of entity dicts with attributes, relationships, and fetch requests
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
tree = ElementTree.parse(xml_path)
|
|
225
|
+
except ElementTree.ParseError:
|
|
226
|
+
return []
|
|
227
|
+
|
|
228
|
+
root = tree.getroot()
|
|
229
|
+
entities = []
|
|
230
|
+
|
|
231
|
+
for entity_elem in root.findall("entity"):
|
|
232
|
+
entity: dict = {
|
|
233
|
+
"name": entity_elem.get("name", ""),
|
|
234
|
+
"is_abstract": entity_elem.get("isAbstract") == "YES",
|
|
235
|
+
"parent_entity": entity_elem.get("parentEntity"),
|
|
236
|
+
"represented_class": entity_elem.get("representedClassName"),
|
|
237
|
+
"attributes": [],
|
|
238
|
+
"relationships": [],
|
|
239
|
+
"fetch_requests": [],
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for attr in entity_elem.findall("attribute"):
|
|
243
|
+
entity["attributes"].append(
|
|
244
|
+
{
|
|
245
|
+
"name": attr.get("name", ""),
|
|
246
|
+
"type": attr.get("attributeType", ""),
|
|
247
|
+
"optional": attr.get("optional") == "YES",
|
|
248
|
+
"default_value": attr.get("defaultValueString"),
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
for rel in entity_elem.findall("relationship"):
|
|
253
|
+
entity["relationships"].append(
|
|
254
|
+
{
|
|
255
|
+
"name": rel.get("name", ""),
|
|
256
|
+
"destination": rel.get("destinationEntity", ""),
|
|
257
|
+
"to_many": rel.get("toMany") == "YES",
|
|
258
|
+
"inverse": rel.get("inverseName"),
|
|
259
|
+
"optional": rel.get("optional") == "YES",
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
for req in entity_elem.findall("fetchRequest"):
|
|
264
|
+
entity["fetch_requests"].append(
|
|
265
|
+
{
|
|
266
|
+
"name": req.get("name", ""),
|
|
267
|
+
"predicate": req.get("predicateString", ""),
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Remove None parent_entity for cleaner output
|
|
272
|
+
if entity["parent_entity"] is None:
|
|
273
|
+
del entity["parent_entity"]
|
|
274
|
+
|
|
275
|
+
entities.append(entity)
|
|
276
|
+
|
|
277
|
+
return entities
|
|
278
|
+
|
|
279
|
+
# === SWIFTDATA EXTRACTION ===
|
|
280
|
+
|
|
281
|
+
def _find_swiftdata_models(self) -> list[dict]:
|
|
282
|
+
"""Best-effort regex extraction of @Model classes from .swift files.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
List of detected model dicts
|
|
286
|
+
"""
|
|
287
|
+
models = []
|
|
288
|
+
swift_files = sorted(self.project_path.rglob("*.swift"))
|
|
289
|
+
|
|
290
|
+
# Skip common non-source directories and any dotdirs (e.g. .archived, .build, .git)
|
|
291
|
+
skip_dirs = {"DerivedData", "Pods", "Carthage"}
|
|
292
|
+
|
|
293
|
+
for swift_file in swift_files:
|
|
294
|
+
if any(
|
|
295
|
+
part in skip_dirs or part.startswith(".")
|
|
296
|
+
for part in swift_file.relative_to(self.project_path).parts
|
|
297
|
+
):
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
content = swift_file.read_text(encoding="utf-8")
|
|
302
|
+
except (OSError, UnicodeDecodeError):
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
file_models = self._extract_models_from_swift(content, swift_file)
|
|
306
|
+
models.extend(file_models)
|
|
307
|
+
|
|
308
|
+
return models
|
|
309
|
+
|
|
310
|
+
def _extract_models_from_swift(self, content: str, file_path: Path) -> list[dict]:
|
|
311
|
+
"""Extract @Model class declarations from Swift source.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
content: Swift source file content
|
|
315
|
+
file_path: Path to the source file (for reporting)
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
List of model dicts found in this file
|
|
319
|
+
"""
|
|
320
|
+
models = []
|
|
321
|
+
|
|
322
|
+
# Match @Model decorator followed by class declaration
|
|
323
|
+
model_pattern = re.compile(
|
|
324
|
+
r"@Model\s*\n\s*(?:final\s+)?class\s+(\w+)",
|
|
325
|
+
re.MULTILINE,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
for match in model_pattern.finditer(content):
|
|
329
|
+
class_name = match.group(1)
|
|
330
|
+
class_start = match.end()
|
|
331
|
+
|
|
332
|
+
# Find the class body by counting braces
|
|
333
|
+
body = self._extract_class_body(content, class_start)
|
|
334
|
+
if body is None:
|
|
335
|
+
continue
|
|
336
|
+
|
|
337
|
+
properties = self._extract_swift_properties(body)
|
|
338
|
+
relationships = self._extract_swift_relationships(body)
|
|
339
|
+
|
|
340
|
+
models.append(
|
|
341
|
+
{
|
|
342
|
+
"class_name": class_name,
|
|
343
|
+
"file": str(file_path.relative_to(self.project_path)),
|
|
344
|
+
"properties": properties,
|
|
345
|
+
"relationships": relationships,
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return models
|
|
350
|
+
|
|
351
|
+
def _extract_class_body(self, content: str, start: int) -> str | None:
|
|
352
|
+
"""Extract class body by matching braces.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
content: Full file content
|
|
356
|
+
start: Position after class name
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Class body string or None if braces don't balance
|
|
360
|
+
"""
|
|
361
|
+
brace_start = content.find("{", start)
|
|
362
|
+
if brace_start == -1:
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
depth = 0
|
|
366
|
+
for i in range(brace_start, len(content)):
|
|
367
|
+
if content[i] == "{":
|
|
368
|
+
depth += 1
|
|
369
|
+
elif content[i] == "}":
|
|
370
|
+
depth -= 1
|
|
371
|
+
if depth == 0:
|
|
372
|
+
return content[brace_start + 1 : i]
|
|
373
|
+
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
def _extract_swift_properties(self, body: str) -> list[dict]:
|
|
377
|
+
"""Extract stored properties from a Swift class body.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
body: Class body content
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
List of property dicts
|
|
384
|
+
"""
|
|
385
|
+
properties = []
|
|
386
|
+
# Match var/let declarations (skip computed properties with { get })
|
|
387
|
+
prop_pattern = re.compile(
|
|
388
|
+
r"^\s*(?:var|let)\s+(\w+)\s*:\s*([^\n{=]+?)(?:\s*=\s*[^\n]+)?$",
|
|
389
|
+
re.MULTILINE,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
for match in prop_pattern.finditer(body):
|
|
393
|
+
name = match.group(1)
|
|
394
|
+
# Strip trailing inline comments (// ...)
|
|
395
|
+
prop_type = re.sub(r"\s*//.*$", "", match.group(2)).strip()
|
|
396
|
+
|
|
397
|
+
# Skip if preceded by @Relationship on current or previous line
|
|
398
|
+
line_start = body.rfind("\n", 0, match.start()) + 1
|
|
399
|
+
prev_line_start = body.rfind("\n", 0, max(0, line_start - 1)) + 1
|
|
400
|
+
preceding = body[prev_line_start : match.start()]
|
|
401
|
+
if "@Relationship" in preceding:
|
|
402
|
+
continue
|
|
403
|
+
|
|
404
|
+
properties.append({"name": name, "type": prop_type})
|
|
405
|
+
|
|
406
|
+
return properties
|
|
407
|
+
|
|
408
|
+
def _extract_swift_relationships(self, body: str) -> list[dict]:
|
|
409
|
+
"""Extract @Relationship properties from a Swift class body.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
body: Class body content
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
List of relationship dicts
|
|
416
|
+
"""
|
|
417
|
+
relationships = []
|
|
418
|
+
rel_pattern = re.compile(
|
|
419
|
+
r"@Relationship[^\n]*\n\s*(?:var|let)\s+(\w+)\s*:\s*([^\n{=]+)",
|
|
420
|
+
re.MULTILINE,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
for match in rel_pattern.finditer(body):
|
|
424
|
+
name = match.group(1)
|
|
425
|
+
rel_type = re.sub(r"\s*//.*$", "", match.group(2)).strip()
|
|
426
|
+
to_many = rel_type.startswith("[") or "Array<" in rel_type
|
|
427
|
+
|
|
428
|
+
relationships.append(
|
|
429
|
+
{
|
|
430
|
+
"name": name,
|
|
431
|
+
"type": rel_type,
|
|
432
|
+
"to_many": to_many,
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return relationships
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# === OUTPUT FORMATTING ===
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def format_default(results: dict) -> str:
|
|
443
|
+
"""Format results as token-efficient summary.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
results: Parsed model data
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Formatted string (3-5 lines)
|
|
450
|
+
"""
|
|
451
|
+
lines = []
|
|
452
|
+
|
|
453
|
+
for model in results["core_data"]:
|
|
454
|
+
entities = model["entities"]
|
|
455
|
+
attr_count = sum(len(e["attributes"]) for e in entities)
|
|
456
|
+
rel_count = sum(len(e["relationships"]) for e in entities)
|
|
457
|
+
|
|
458
|
+
version_info = f"{model['version_count']} version"
|
|
459
|
+
if model["version_count"] != 1:
|
|
460
|
+
version_info += "s"
|
|
461
|
+
if model["current_version"]:
|
|
462
|
+
current = model["current_version"].replace(".xcdatamodel", "")
|
|
463
|
+
version_info += f", current: {current}"
|
|
464
|
+
|
|
465
|
+
lines.append(f"Core Data: {model['package']} ({version_info})")
|
|
466
|
+
lines.append(
|
|
467
|
+
f" {len(entities)} entities, {attr_count} attributes, {rel_count} relationships"
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
if results["swiftdata"]:
|
|
471
|
+
count = len(results["swiftdata"])
|
|
472
|
+
names = ", ".join(m["class_name"] for m in results["swiftdata"])
|
|
473
|
+
lines.append(f"SwiftData: {count} @Model class{'es' if count != 1 else ''} ({names})")
|
|
474
|
+
|
|
475
|
+
return "\n".join(lines)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def format_verbose(results: dict) -> str:
|
|
479
|
+
"""Format results with full details.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
results: Parsed model data
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
Formatted string with entity/attribute/relationship details
|
|
486
|
+
"""
|
|
487
|
+
lines = []
|
|
488
|
+
|
|
489
|
+
for model in results["core_data"]:
|
|
490
|
+
lines.append(f"Core Data: {model['package']}")
|
|
491
|
+
|
|
492
|
+
if model.get("versions"):
|
|
493
|
+
version_list = []
|
|
494
|
+
for v in model["versions"]:
|
|
495
|
+
name = v["name"].replace(".xcdatamodel", "")
|
|
496
|
+
suffix = " (current)" if v["is_current"] else ""
|
|
497
|
+
version_list.append(f"{name}{suffix}")
|
|
498
|
+
lines.append(f" Versions: {', '.join(version_list)}")
|
|
499
|
+
|
|
500
|
+
if model.get("current_version"):
|
|
501
|
+
current = model["current_version"].replace(".xcdatamodel", "")
|
|
502
|
+
lines.append(f" Current version: {current}")
|
|
503
|
+
|
|
504
|
+
lines.append("")
|
|
505
|
+
|
|
506
|
+
for entity in model["entities"]:
|
|
507
|
+
attr_count = len(entity["attributes"])
|
|
508
|
+
rel_count = len(entity["relationships"])
|
|
509
|
+
abstract = " (abstract)" if entity.get("is_abstract") else ""
|
|
510
|
+
parent = f" extends {entity['parent_entity']}" if entity.get("parent_entity") else ""
|
|
511
|
+
lines.append(
|
|
512
|
+
f" Entity: {entity['name']}{abstract}{parent}"
|
|
513
|
+
f" ({attr_count} attributes, {rel_count} relationships)"
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
if entity["attributes"]:
|
|
517
|
+
lines.append(" Attributes:")
|
|
518
|
+
for attr in entity["attributes"]:
|
|
519
|
+
optional = "optional" if attr["optional"] else "required"
|
|
520
|
+
default = f", default: {attr['default_value']}" if attr["default_value"] else ""
|
|
521
|
+
lines.append(f" - {attr['name']}: {attr['type']} ({optional}{default})")
|
|
522
|
+
|
|
523
|
+
if entity["relationships"]:
|
|
524
|
+
lines.append(" Relationships:")
|
|
525
|
+
for rel in entity["relationships"]:
|
|
526
|
+
cardinality = "toMany" if rel["to_many"] else "toOne"
|
|
527
|
+
inverse = f", inverse: {rel['inverse']}" if rel["inverse"] else ""
|
|
528
|
+
lines.append(
|
|
529
|
+
f" - {rel['name']}: {rel['destination']} ({cardinality}{inverse})"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
if entity["fetch_requests"]:
|
|
533
|
+
lines.append(" Fetch Requests:")
|
|
534
|
+
for req in entity["fetch_requests"]:
|
|
535
|
+
lines.append(f" - {req['name']}: {req['predicate']}")
|
|
536
|
+
|
|
537
|
+
lines.append("")
|
|
538
|
+
|
|
539
|
+
if results["swiftdata"]:
|
|
540
|
+
lines.append("SwiftData:")
|
|
541
|
+
for model in results["swiftdata"]:
|
|
542
|
+
prop_count = len(model["properties"])
|
|
543
|
+
rel_count = len(model["relationships"])
|
|
544
|
+
lines.append(
|
|
545
|
+
f" @Model {model['class_name']} ({prop_count} properties, {rel_count} relationships)"
|
|
546
|
+
)
|
|
547
|
+
lines.append(f" File: {model['file']}")
|
|
548
|
+
|
|
549
|
+
if model["properties"]:
|
|
550
|
+
for prop in model["properties"]:
|
|
551
|
+
lines.append(f" - {prop['name']}: {prop['type']}")
|
|
552
|
+
|
|
553
|
+
if model["relationships"]:
|
|
554
|
+
for rel in model["relationships"]:
|
|
555
|
+
cardinality = "toMany" if rel["to_many"] else "toOne"
|
|
556
|
+
lines.append(f" @Relationship {rel['name']}: {rel['type']} ({cardinality})")
|
|
557
|
+
|
|
558
|
+
lines.append("")
|
|
559
|
+
|
|
560
|
+
return "\n".join(lines).rstrip()
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def main():
|
|
564
|
+
"""Main entry point."""
|
|
565
|
+
parser = argparse.ArgumentParser(
|
|
566
|
+
description="Inspect Core Data and SwiftData models from project files",
|
|
567
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
568
|
+
epilog="""
|
|
569
|
+
Examples:
|
|
570
|
+
python scripts/model_inspector.py --project-path /path/to/project
|
|
571
|
+
python scripts/model_inspector.py --project-path . --verbose
|
|
572
|
+
python scripts/model_inspector.py --project-path . --json
|
|
573
|
+
python scripts/model_inspector.py --project-path . --show-versions
|
|
574
|
+
python scripts/model_inspector.py --project-path . --core-data-only
|
|
575
|
+
python scripts/model_inspector.py --project-path . --raw TrainingSession
|
|
576
|
+
""",
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
inspection_group = parser.add_argument_group("Inspection Options")
|
|
580
|
+
inspection_group.add_argument(
|
|
581
|
+
"--project-path",
|
|
582
|
+
default=".",
|
|
583
|
+
help="Path to Xcode project root (default: current directory)",
|
|
584
|
+
)
|
|
585
|
+
inspection_group.add_argument(
|
|
586
|
+
"--core-data-only",
|
|
587
|
+
action="store_true",
|
|
588
|
+
help="Only inspect Core Data .xcdatamodeld packages",
|
|
589
|
+
)
|
|
590
|
+
inspection_group.add_argument(
|
|
591
|
+
"--swiftdata-only",
|
|
592
|
+
action="store_true",
|
|
593
|
+
help="Only inspect SwiftData @Model classes",
|
|
594
|
+
)
|
|
595
|
+
inspection_group.add_argument(
|
|
596
|
+
"--show-versions",
|
|
597
|
+
action="store_true",
|
|
598
|
+
help="List all model versions with current version highlighted",
|
|
599
|
+
)
|
|
600
|
+
inspection_group.add_argument(
|
|
601
|
+
"--raw",
|
|
602
|
+
metavar="MODEL_NAME",
|
|
603
|
+
help="Dump raw source for a specific model (Swift class or Core Data entity)",
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
output_group = parser.add_argument_group("Output Options")
|
|
607
|
+
output_group.add_argument("--json", action="store_true", help="Output as JSON")
|
|
608
|
+
output_group.add_argument("--verbose", action="store_true", help="Show full details")
|
|
609
|
+
|
|
610
|
+
args = parser.parse_args()
|
|
611
|
+
|
|
612
|
+
inspector = ModelInspector(project_path=args.project_path)
|
|
613
|
+
|
|
614
|
+
if args.raw:
|
|
615
|
+
success, raw = inspector.get_raw_source(args.raw)
|
|
616
|
+
if success:
|
|
617
|
+
print(raw)
|
|
618
|
+
else:
|
|
619
|
+
print(f"Error: {raw}", file=sys.stderr)
|
|
620
|
+
sys.exit(1)
|
|
621
|
+
return
|
|
622
|
+
|
|
623
|
+
success, results = inspector.execute(
|
|
624
|
+
core_data_only=args.core_data_only,
|
|
625
|
+
swiftdata_only=args.swiftdata_only,
|
|
626
|
+
show_versions=args.show_versions,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
if not success:
|
|
630
|
+
if "error" in results:
|
|
631
|
+
print(f"Error: {results['error']}", file=sys.stderr)
|
|
632
|
+
else:
|
|
633
|
+
print("No Core Data or SwiftData models found", file=sys.stderr)
|
|
634
|
+
sys.exit(1)
|
|
635
|
+
|
|
636
|
+
if args.json:
|
|
637
|
+
print(json.dumps(results, indent=2, default=str))
|
|
638
|
+
elif args.verbose or args.show_versions:
|
|
639
|
+
print(format_verbose(results))
|
|
640
|
+
else:
|
|
641
|
+
print(format_default(results))
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
if __name__ == "__main__":
|
|
645
|
+
main()
|