@ -3,15 +3,78 @@ import re
import os
import os
from pathlib import Path
from pathlib import Path
from typing import Dict , List , Any
from typing import Dict , List , Any
from collections import defaultdict
import argparse
import argparse
import sys
import sys
import logging
import logging
from datetime import datetime
from datetime import datetime
import inflect
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 :
class LeStorageGenerator :
def __init__ ( self , json_data : Dict [ str , Any ] ) :
def __init__ ( self , json_data : Dict [ str , Any ] , relationship_analyzer : RelationshipAnalyzer = None ) :
self . data = json_data
self . json_data = json_data
self . relationship_analyzer = relationship_analyzer
self . pluralizer = inflect . engine ( )
self . pluralizer = inflect . engine ( )
def generate_model ( self , model_data : Dict [ str , Any ] ) - > str :
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 ] :
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. """
""" Generate a constructor with all properties as parameters with default values. """
lines = [ " public init( " ]
lines = [ " public init( " ]
# Generate parameter list
# Generate parameter list
@ -158,15 +221,15 @@ class LeStorageGenerator:
lines . append ( f " self. { name } = { name } " )
lines . append ( f " self. { name } = { name } " )
lines . append ( " } " )
lines . append ( " } " )
lines . extend ( [
lines . extend ( [
" required public override init() { " ,
" required public override init() { " ,
" super.init() " ,
" super.init() " ,
" } " ,
" } " ,
] )
] )
return lines
return lines
def _generate_didset_methods ( self , properties ) - > List [ str ] :
def _generate_didset_methods ( self , properties ) - > List [ str ] :
@ -410,7 +473,7 @@ class LeStorageGenerator:
name = prop [ " name " ]
name = prop [ " name " ]
foreign_key = prop [ " foreignKey " ] . rstrip ( ' ### ' ) # Remove asterisk if present
foreign_key = prop [ " foreignKey " ] . rstrip ( ' ### ' ) # Remove asterisk if present
foreign_variable = name + " Value "
foreign_variable = name + " Value "
# Generate the foreign key check and deletion logic
# 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 " 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) " )
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 ] :
def _generate_relationships ( self , model_name , properties : List [ Dict [ str , Any ] ] ) - > List [ str ] :
# Find all properties with foreign keys
# if not self.relationship_analyzer:
foreign_key_props = [ p for p in properties if " foreignKey " in p ]
# # Fallback to old behavior if no analyzer provided
# return self._generate_legacy_relationships(model_name, properties)
lines = [ ]
if not foreign_key_props :
# Generate parentRelationships method
# If no foreign keys, return empty array
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 [
return [
" public static func relationships() -> [Relationship] { " ,
" public static func pa rentR elationships() -> [Relationship] { " ,
" return [] " ,
" return [] " ,
" } "
" } "
]
]
lines = [
lines = [
" public static func relationships() -> [Relationship] { " ,
" public static func pa rentR elationships() -> [Relationship] { " ,
" return [ "
" return [ "
]
]
# Generate relationship entries
for rel in parent_rels :
for prop in foreign_key_props :
# main_store = "true" if rel["storeLookup"] else "false"
name = prop [ " name " ]
lines . append ( f " Relationship(type: { rel [ ' foreignKey ' ] } .self, keyPath: \\ Base { model_name } . { rel [ ' name ' ] } , storeLookup: { rel [ " storeLookup " ] } ), " )
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 } ), " )
# Close the array and function
lines . extend ( [
lines . extend ( [
" ] " ,
" ] " ,
" } "
" } "
@ -472,6 +550,93 @@ class LeStorageGenerator:
return lines
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 :
def _get_default_value ( self , type_name : str ) - > str :
""" Get default value for non-optional types """
""" Get default value for non-optional types """
if " String " in type_name :
if " String " in type_name :
@ -516,14 +681,21 @@ def process_directory(input_dir: str, output_dir: str, logger: logging.Logger, d
return 0
return 0
logger . info ( f " Found { len ( json_files ) } JSON files to process " )
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
successful_files = 0
# Second pass: Generate models with complete relationship information
for json_file in json_files :
for json_file in json_files :
try :
try :
with open ( json_file , ' r ' ) as f :
with open ( json_file , ' r ' ) as f :
json_data = json . load ( f )
json_data = json . load ( f )
generator = LeStorageGenerator ( json_data )
generator = LeStorageGenerator ( json_data , relationship_analyzer )
# Generate each model in the JSON file
# Generate each model in the JSON file
for model in json_data [ " models " ] :
for model in json_data [ " models " ] :