module Language Kotlin = 'kotlin' Swift = 'swift' Dart = 'dart' def self.all [Kotlin, Swift, Dart] end def self.init(language) if all.include?(language) # Do something with the valid language else raise ArgumentError, "Invalid language. Please use one of: #{all.join(', ')}" end end end class CodeGenerator def initialize( json:, language:, parent_class_name:) @json = json @language = language @parent_class_name = parent_class_name @generator = create_language_generator end def generate @generator.generate_content end private def create_language_generator case @language when Language::Kotlin KotlinCodeGenerator.new(@json, @parent_class_name) when Language::Swift SwiftCodeGenerator.new(@json, @parent_class_name) when Language::Dart DartCodeGenerator.new(@json, @parent_class_name) else raise ArgumentError, "Unsupported language: #{@language}" end end end class BaseCodeGenerator def initialize(json, parent_class_name) @parent_class_name = parent_class_name @json = json end def generate_content content = "// Generated by Solara\n" content += language_specific_imports classes = [] generate_classes(@parent_class_name, @json, classes, generate_json: true) classes.reverse_each do |class_content| content += class_content content += "\n" end content end def generate_classes(class_name, json, classes, generate_json: false) content = class_declaration(class_name) constructor_params = [] json.each do |key, value| type = value_type(value, key) content += property_declaration(key, type, json) constructor_params << constructor_parameter(key, type) end content += property_declaration("asJson", "String", json) content += constructor_declaration(class_name, constructor_params) content += instance_declaration(class_name, json) content += json_methods(json, class_name) content += class_closing classes << content json.each do |key, value| if value.is_a?(Hash) nested_class_name = "#{key[0].upcase}#{key[1..-1]}" # Capitalize first character generate_classes(nested_class_name, value, classes) elsif value.is_a?(Array) && value.any? { |item| item.is_a?(Hash) } nested_class_name = "#{key[0].upcase}#{key[1..-1]}Item" # Capitalize first character generate_classes(nested_class_name, value.first, classes) end end end def class_declaration(class_name) raise NotImplementedError, "Subclasses must implement class_declaration" end def property_declaration(key, type, json) raise NotImplementedError, "Subclasses must implement property_declaration" end def constructor_parameter(key, type) raise NotImplementedError, "Subclasses must implement constructor_parameter" end def constructor_declaration(class_name, params) raise NotImplementedError, "Subclasses must implement constructor_declaration" end def instance_declaration(class_name, json) raise NotImplementedError, "Subclasses must implement instance_declaration" end def json_methods(json, class_name) raise NotImplementedError, "Subclasses must implement json_methods" end def class_closing raise NotImplementedError, "Subclasses must implement class_closing" end def language_specific_imports raise NotImplementedError, "Subclasses must implement language_specific_imports" end def value_type(value, class_prefix) raise NotImplementedError, "Subclasses must implement value_type" end def value_for(value, class_prefix, indent) raise NotImplementedError, "Subclasses must implement value_type" end def color_for(value) raise NotImplementedError, "Subclasses must implement color_for" end def language_specific_null raise NotImplementedError, "Subclasses must implement language_specific_null" end end class KotlinCodeGenerator < BaseCodeGenerator def language_specific_imports "import android.graphics.Color\n" + "import java.io.Serializable\n\n" end def class_declaration(class_name) "\ndata class #{class_name}(\n" end def property_declaration(key, type, json) if key == "asJson" json_string = json.to_json.gsub('"', '\\"') " val #{key}: String = \"#{json_string}\",\n" else " val #{key}: #{type},\n" end end def constructor_parameter(key, type) "val #{key}: #{type}" end def constructor_declaration(class_name, params) "): Serializable {\n" end def instance_declaration(class_name, json) " companion object {\n val instance = #{class_name}(\n#{json.map { |k, v| " #{k} = #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n }\n" end def json_methods(json, class_name) "" end def class_closing "}\n" end def value_for(value, class_prefix, indent) case value when String if value.start_with?('#') && value.length == 7 # Assume it's a color color_for(value) else "\"#{value}\"" # Use double quotes for Kotlin strings end when Integer, Float value.to_s when TrueClass, FalseClass value.to_s when Array if value.empty? "emptyList()" # Use Kotlin's emptyList() for empty arrays elsif value.all? { |item| item.is_a?(Hash) } array_items = value.map do |item| item_values = item.map { |k, v| "#{k} = #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ") "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )" end.join(",\n#{indent} ") "listOf(\n#{indent} #{array_items}\n#{indent})" else array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ") "listOf(\n#{indent} #{array_items}\n#{indent})" # Use listOf for non-empty lists end when Hash "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.instance" else language_specific_null end end def value_type(value, class_prefix) case value when String then 'String' when Integer then 'Int' when Float then 'Float' when TrueClass, FalseClass then 'Boolean' when Array if value.empty? 'List' elsif value.all? { |item| item.is_a?(String) } 'List' elsif value.all? { |item| item.is_a?(Integer) } 'List' elsif value.all? { |item| item.is_a?(Float) } 'List' elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) } 'List' elsif value.all? { |item| item.is_a?(Hash) } "List<#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item>" else 'List' end when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}" else 'Any' end end def color_for(value) "Color.parseColor(\"#{value}\")" end def language_specific_null 'null' end end class SwiftCodeGenerator < BaseCodeGenerator def language_specific_imports "import UIKit\n\n" end def class_declaration(class_name) "struct #{class_name}: Codable {\n" end def property_declaration(key, type, json) if key == "asJson" json_string = json.to_json.gsub('"', '\\"') " let #{key}: String = \"#{json_string}\"\n" else " let #{key}: #{type}\n" end end def constructor_parameter(key, type) "#{key}: #{type}" end def constructor_declaration(class_name, params) "\n init(\n#{params.map { |p| " #{p}"}.join(",\n")}) {\n#{params.map { |p| " self.#{p.split(':').first} = #{p.split(':').first}" }.join("\n")}\n }\n\n" end def instance_declaration(class_name, json) " static let shared = #{class_name}(\n#{json.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n" end def json_methods(json, class_name) " func toJson() -> String? {\n" + " let encoder = JSONEncoder()\n" + " if let jsonData = try? encoder.encode(self) {\n" + " return String(data: jsonData, encoding: .utf8)\n" + " }\n" + " return nil\n" + " }\n\n" + " static func fromJson(_ json: String) -> #{class_name}? {\n" + " let decoder = JSONDecoder()\n" + " if let jsonData = json.data(using: .utf8),\n" + " let result = try? decoder.decode(#{class_name}.self, from: jsonData) {\n" + " return result\n" + " }\n" + " return nil\n" + " }\n" end def class_closing "}\n" end def value_for(value, class_prefix, indent) case value when String if value.start_with?('#') && value.length == 7 # Assume it's a color color_for(value) else "\"#{value}\"" end when Integer, Float value.to_s when TrueClass, FalseClass value.to_s when Array if value.empty? "[]" elsif value.all? { |item| item.is_a?(Hash) } array_items = value.map do |item| item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ") "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )" end.join(",\n#{indent} ") "[\n#{indent} #{array_items}\n#{indent}]" else array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ") "[\n#{indent} #{array_items}\n#{indent}]" end when Hash "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.shared" else language_specific_null end end def value_type(value, class_prefix) case value when String then 'String' when Integer then 'Int' when Float then 'Double' when TrueClass, FalseClass then 'Bool' when Array if value.empty? '[Any]' elsif value.all? { |item| item.is_a?(String) } '[String]' elsif value.all? { |item| item.is_a?(Integer) } '[Int]' elsif value.all? { |item| item.is_a?(Float) } '[Double]' elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) } '[Bool]' elsif value.all? { |item| item.is_a?(Hash) } "[#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item]" else '[Any]' end when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}" else 'Any' end end def color_for(value) r, g, b = value[1..2].to_i(16), value[3..4].to_i(16), value[5..6].to_i(16) "UIColor(red: #{r}/255.0, green: #{g}/255.0, blue: #{b}/255.0, alpha: 1.0)" end def language_specific_null 'nil' end end class DartCodeGenerator < BaseCodeGenerator def language_specific_imports "import 'package:flutter/material.dart';\n" + "import 'dart:convert';\n\n" end def class_declaration(class_name) "class #{class_name} {\n" end def property_declaration(key, type, json) if key == "asJson" json_string = json.to_json.gsub('"', '\\"') " final String #{key} = \"#{json_string}\";\n" else " final #{type} #{key};\n" end end def constructor_parameter(key, type) "required this.#{key}" end def constructor_declaration(class_name, params) params.empty? ? "\n const #{class_name}();\n\n" : "\n const #{class_name}({\n#{params.map { |p| " #{p}"}.join(",\n")}});\n\n" end def instance_declaration(class_name, json) " static const #{class_name} instance = #{class_name}(\n#{json.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n );\n" end def json_methods(json, class_name) " Map toJson() => {\n" + " #{json.keys.map { |k| "'#{k}': #{k}" }.join(",\n ")}\n" + " };\n\n" + " factory #{class_name}.fromJson(Map json) => #{class_name}(\n" + " #{json.keys.map { |k| "#{k}: json['#{k}']" }.join(",\n ")}\n" + " );\n\n" + " String toJsonString() => jsonEncode(toJson());\n\n" + " factory #{class_name}.fromJsonString(String jsonString) =>\n" + " #{class_name}.fromJson(jsonDecode(jsonString) as Map);\n" end def class_closing "}\n" end def value_for(value, class_prefix, indent) case value when String if value.start_with?('#') && value.length == 7 # Assume it's a color color_for(value) else "\"#{value}\"" end when Integer, Float value.to_s when TrueClass, FalseClass value.to_s when Array if value.empty? "[]" elsif value.all? { |item| item.is_a?(Hash) } array_items = value.map do |item| item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ") "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )" end.join(",\n#{indent} ") "[\n#{indent} #{array_items}\n#{indent}]" else array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ") "[\n#{indent} #{array_items}\n#{indent}]" end when Hash "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.instance" else language_specific_null end end def value_type(value, class_prefix) case value when String then 'String' when Integer then 'int' when Float then 'double' when TrueClass, FalseClass then 'bool' when Array if value.empty? 'List' elsif value.all? { |item| item.is_a?(String) } 'List' elsif value.all? { |item| item.is_a?(Integer) } 'List' elsif value.all? { |item| item.is_a?(Float) } 'List' elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) } 'List' elsif value.all? { |item| item.is_a?(Hash) } "List<#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item>" else 'List' end when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}" else 'dynamic' end end def color_for(value) "Color(0xFF#{value[1..-1]})" end def language_specific_null 'null' end end