@ -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 :
@ -117,7 +180,7 @@ class LeStorageGenerator:
def _generate_constructor ( self , model_name : str , properties : List [ Dict [ str , Any ] ] ) - > List [ str ] :
""" Generate a constructor with all properties as parameters with default values. """
lines = [ " public init( " ]
# Generate parameter list
@ -158,15 +221,15 @@ class LeStorageGenerator:
lines . append ( f " self. { name } = { name } " )
lines . append ( " } " )
lines . extend ( [
" required public override init() { " ,
" super.init() " ,
" } " ,
] )
return lines
def _generate_didset_methods ( self , properties ) - > List [ str ] :
@ -410,7 +473,7 @@ class LeStorageGenerator:
name = prop [ " name " ]
foreign_key = prop [ " foreignKey " ] . rstrip ( ' ### ' ) # Remove asterisk if present
foreign_variable = name + " Value "
# Generate the foreign key check and deletion logic
lines . append ( f " if { model_variable } . { name } != self. { name } , let { name } Object = self. { foreign_variable } (), { name } Object.shared == true, let store = { name } Object.store {{ " )
lines . append ( f " store.deleteUnusedShared( { name } Object) " )
@ -441,30 +504,45 @@ 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 = [ ]
if not foreign_key_props :
# If no foreign keys, return empty array
# 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 parent_rels :
return [
" public static func relationships() -> [Relationship] { " ,
" public static func pa rentR elationships() -> [Relationship] { " ,
" return [] " ,
" } "
]
lines = [
" public static func relationships() -> [Relationship] { " ,
" public static func pa rentR elationships() -> [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 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 " ] } ), " )
# Close the array and function
lines . extend ( [
" ] " ,
" } "
@ -472,6 +550,93 @@ class LeStorageGenerator:
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 [ "
]
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 " ] } ), " )
lines . extend ( [
" ] " ,
" } "
] )
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 " ] :