|
|
|
|
@ -3,15 +3,78 @@ import re |
|
|
|
|
import os |
|
|
|
|
from pathlib import Path |
|
|
|
|
from typing import Dict, List, Any |
|
|
|
|
from collections import defaultdict |
|
|
|
|
import argparse |
|
|
|
|
import sys |
|
|
|
|
import logging |
|
|
|
|
from datetime import datetime |
|
|
|
|
import inflect |
|
|
|
|
|
|
|
|
|
class RelationshipAnalyzer: |
|
|
|
|
def __init__(self): |
|
|
|
|
self.parent_relationships = defaultdict(list) # model_name -> list of parent relationships |
|
|
|
|
self.children_relationships = defaultdict(list) # model_name -> list of children relationships |
|
|
|
|
|
|
|
|
|
def analyze_all_models(self, input_dir: str) -> None: |
|
|
|
|
"""Analyze all JSON files to build complete relationship map.""" |
|
|
|
|
input_path = Path(input_dir) |
|
|
|
|
json_files = list(input_path.glob("*.json")) |
|
|
|
|
|
|
|
|
|
for json_file in json_files: |
|
|
|
|
with open(json_file, 'r') as f: |
|
|
|
|
json_data = json.load(f) |
|
|
|
|
|
|
|
|
|
for model in json_data["models"]: |
|
|
|
|
model_name = model["name"] |
|
|
|
|
properties = model.get("properties", []) |
|
|
|
|
|
|
|
|
|
# Find foreign key properties (parents) |
|
|
|
|
for prop in properties: |
|
|
|
|
if "foreignKey" in prop: |
|
|
|
|
foreign_key = prop["foreignKey"].rstrip('###') |
|
|
|
|
located_on_main_store = prop["foreignKey"].endswith('###') |
|
|
|
|
|
|
|
|
|
if located_on_main_store: |
|
|
|
|
# Store parent relationship |
|
|
|
|
self.parent_relationships[model_name].append({ |
|
|
|
|
"name": prop["name"], |
|
|
|
|
"foreignKey": foreign_key, |
|
|
|
|
"storeLookup": ".main" |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
# Store children relationship (reverse) |
|
|
|
|
self.children_relationships[foreign_key].append({ |
|
|
|
|
"name": prop["name"], |
|
|
|
|
"childModel": model_name, |
|
|
|
|
"storeLookup": ".child" |
|
|
|
|
}) |
|
|
|
|
else: |
|
|
|
|
# Store parent relationship |
|
|
|
|
self.parent_relationships[model_name].append({ |
|
|
|
|
"name": prop["name"], |
|
|
|
|
"foreignKey": foreign_key, |
|
|
|
|
"storeLookup": ".same" |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
# Store children relationship (reverse) |
|
|
|
|
self.children_relationships[foreign_key].append({ |
|
|
|
|
"name": prop["name"], |
|
|
|
|
"childModel": model_name, |
|
|
|
|
"storeLookup": ".same" |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
def get_parent_relationships(self, model_name: str) -> List[Dict[str, Any]]: |
|
|
|
|
"""Get parent relationships for a model.""" |
|
|
|
|
return self.parent_relationships.get(model_name, []) |
|
|
|
|
|
|
|
|
|
def get_children_relationships(self, model_name: str) -> List[Dict[str, Any]]: |
|
|
|
|
"""Get children relationships for a model.""" |
|
|
|
|
return self.children_relationships.get(model_name, []) |
|
|
|
|
|
|
|
|
|
class LeStorageGenerator: |
|
|
|
|
def __init__(self, json_data: Dict[str, Any]): |
|
|
|
|
self.data = json_data |
|
|
|
|
def __init__(self, json_data: Dict[str, Any], relationship_analyzer: RelationshipAnalyzer = None): |
|
|
|
|
self.json_data = json_data |
|
|
|
|
self.relationship_analyzer = relationship_analyzer |
|
|
|
|
self.pluralizer = inflect.engine() |
|
|
|
|
|
|
|
|
|
def generate_model(self, model_data: Dict[str, Any]) -> str: |
|
|
|
|
@ -441,30 +504,72 @@ class LeStorageGenerator: |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]: |
|
|
|
|
# Find all properties with foreign keys |
|
|
|
|
foreign_key_props = [p for p in properties if "foreignKey" in p] |
|
|
|
|
# if not self.relationship_analyzer: |
|
|
|
|
# # Fallback to old behavior if no analyzer provided |
|
|
|
|
# return self._generate_legacy_relationships(model_name, properties) |
|
|
|
|
|
|
|
|
|
lines = [] |
|
|
|
|
|
|
|
|
|
# Generate parentRelationships method |
|
|
|
|
lines.extend(self._generate_parent_relationships(model_name)) |
|
|
|
|
lines.append("") |
|
|
|
|
|
|
|
|
|
# Generate childrenRelationships method |
|
|
|
|
lines.extend(self._generate_children_relationships(model_name)) |
|
|
|
|
lines.append("") |
|
|
|
|
|
|
|
|
|
# Generate combined relationships method |
|
|
|
|
lines.extend(self._generate_combined_relationships(model_name)) |
|
|
|
|
|
|
|
|
|
return lines |
|
|
|
|
|
|
|
|
|
def _generate_parent_relationships(self, model_name: str) -> List[str]: |
|
|
|
|
"""Generate parentRelationships() method.""" |
|
|
|
|
parent_rels = self.relationship_analyzer.get_parent_relationships(model_name) |
|
|
|
|
|
|
|
|
|
if not foreign_key_props: |
|
|
|
|
# If no foreign keys, return empty array |
|
|
|
|
if not parent_rels: |
|
|
|
|
return [ |
|
|
|
|
" public static func relationships() -> [Relationship] {", |
|
|
|
|
" public static func parentRelationships() -> [Relationship] {", |
|
|
|
|
" return []", |
|
|
|
|
" }" |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
lines = [ |
|
|
|
|
" public static func relationships() -> [Relationship] {", |
|
|
|
|
" public static func parentRelationships() -> [Relationship] {", |
|
|
|
|
" return [" |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
for rel in parent_rels: |
|
|
|
|
# main_store = "true" if rel["storeLookup"] else "false" |
|
|
|
|
lines.append(f" Relationship(type: {rel['foreignKey']}.self, keyPath: \\Base{model_name}.{rel['name']}, storeLookup: {rel["storeLookup"]}),") |
|
|
|
|
|
|
|
|
|
lines.extend([ |
|
|
|
|
" ]", |
|
|
|
|
" }" |
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
return lines |
|
|
|
|
|
|
|
|
|
def _generate_children_relationships(self, model_name: str) -> List[str]: |
|
|
|
|
"""Generate childrenRelationships() method.""" |
|
|
|
|
children_rels = self.relationship_analyzer.get_children_relationships(model_name) |
|
|
|
|
|
|
|
|
|
if not children_rels: |
|
|
|
|
return [ |
|
|
|
|
" public static func childrenRelationships() -> [Relationship] {", |
|
|
|
|
" return []", |
|
|
|
|
" }" |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
lines = [ |
|
|
|
|
" public static func childrenRelationships() -> [Relationship] {", |
|
|
|
|
" return [" |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
# Generate relationship entries |
|
|
|
|
for prop in foreign_key_props: |
|
|
|
|
name = prop["name"] |
|
|
|
|
located_on_main_store = "true" if prop["foreignKey"].endswith('###') else "false" |
|
|
|
|
foreign_key = prop["foreignKey"].rstrip('###') # Remove asterisk if present |
|
|
|
|
lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, mainStoreLookup: {located_on_main_store}),") |
|
|
|
|
for rel in children_rels: |
|
|
|
|
# main_store = "true" if rel["storeLookup"] else "false" |
|
|
|
|
lines.append(f" Relationship(type: {rel['childModel']}.self, keyPath: \\Base{rel['childModel']}.{rel['name']}, storeLookup: {rel["storeLookup"]}),") |
|
|
|
|
|
|
|
|
|
# Close the array and function |
|
|
|
|
lines.extend([ |
|
|
|
|
" ]", |
|
|
|
|
" }" |
|
|
|
|
@ -472,6 +577,66 @@ class LeStorageGenerator: |
|
|
|
|
|
|
|
|
|
return lines |
|
|
|
|
|
|
|
|
|
def _generate_combined_relationships(self, model_name: str) -> List[str]: |
|
|
|
|
"""Generate relationships() method that combines parent and children.""" |
|
|
|
|
parent_rels = self.relationship_analyzer.get_parent_relationships(model_name) |
|
|
|
|
children_rels = self.relationship_analyzer.get_children_relationships(model_name) |
|
|
|
|
|
|
|
|
|
if not parent_rels and not children_rels: |
|
|
|
|
return [ |
|
|
|
|
" public static func relationships() -> [Relationship] {", |
|
|
|
|
" return []", |
|
|
|
|
" }" |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
lines = [ |
|
|
|
|
" public static func relationships() -> [Relationship] {", |
|
|
|
|
" var relationships: [Relationship] = []", |
|
|
|
|
" relationships.append(contentsOf: parentRelationships())", |
|
|
|
|
" relationships.append(contentsOf: childrenRelationships())", |
|
|
|
|
" return relationships", |
|
|
|
|
" }" |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
return lines |
|
|
|
|
|
|
|
|
|
# def _generate_legacy_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]: |
|
|
|
|
# """Legacy relationship generation for backward compatibility.""" |
|
|
|
|
# # Find all properties with foreign keys |
|
|
|
|
# foreign_key_props = [p for p in properties if "foreignKey" in p] |
|
|
|
|
# |
|
|
|
|
# if not foreign_key_props: |
|
|
|
|
# # If no foreign keys, return empty array |
|
|
|
|
# return [ |
|
|
|
|
# " public static func relationships() -> [Relationship] {", |
|
|
|
|
# " return []", |
|
|
|
|
# " }" |
|
|
|
|
# ] |
|
|
|
|
# |
|
|
|
|
# lines = [ |
|
|
|
|
# " public static func relationships() -> [Relationship] {", |
|
|
|
|
# " return [" |
|
|
|
|
# ] |
|
|
|
|
# |
|
|
|
|
# # Generate relationship entries |
|
|
|
|
# for prop in foreign_key_props: |
|
|
|
|
# name = prop["name"] |
|
|
|
|
# located_on_main_store = "true" if prop["foreignKey"].endswith('###') else "false" |
|
|
|
|
# foreign_key = prop["foreignKey"].rstrip('###') # Remove asterisk if present |
|
|
|
|
# |
|
|
|
|
# if located_on_main_store: |
|
|
|
|
# lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, storeLookup: {located_on_main_store}),") |
|
|
|
|
# else: |
|
|
|
|
# lines.append(f" Relationship(type: {foreign_key}.self, keyPath: \\Base{model_name}.{name}, storeLookup: {located_on_main_store}),") |
|
|
|
|
# |
|
|
|
|
# # Close the array and function |
|
|
|
|
# lines.extend([ |
|
|
|
|
# " ]", |
|
|
|
|
# " }" |
|
|
|
|
# ]) |
|
|
|
|
# |
|
|
|
|
# return lines |
|
|
|
|
|
|
|
|
|
def _get_default_value(self, type_name: str) -> str: |
|
|
|
|
"""Get default value for non-optional types""" |
|
|
|
|
if "String" in type_name: |
|
|
|
|
@ -516,14 +681,21 @@ def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, d |
|
|
|
|
return 0 |
|
|
|
|
|
|
|
|
|
logger.info(f"Found {len(json_files)} JSON files to process") |
|
|
|
|
|
|
|
|
|
# First pass: Analyze all relationships |
|
|
|
|
logger.info("Analyzing relationships across all models...") |
|
|
|
|
relationship_analyzer = RelationshipAnalyzer() |
|
|
|
|
relationship_analyzer.analyze_all_models(input_dir) |
|
|
|
|
|
|
|
|
|
successful_files = 0 |
|
|
|
|
|
|
|
|
|
# Second pass: Generate models with complete relationship information |
|
|
|
|
for json_file in json_files: |
|
|
|
|
try: |
|
|
|
|
with open(json_file, 'r') as f: |
|
|
|
|
json_data = json.load(f) |
|
|
|
|
|
|
|
|
|
generator = LeStorageGenerator(json_data) |
|
|
|
|
generator = LeStorageGenerator(json_data, relationship_analyzer) |
|
|
|
|
|
|
|
|
|
# Generate each model in the JSON file |
|
|
|
|
for model in json_data["models"]: |
|
|
|
|
|