solara 0.7.2 → 0.7.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f359a3ba4f7f58b06a5c26efa077d45900f5d98952c8ffb85ed80407b5060b5
4
- data.tar.gz: 1f9bf9dc53e840e154f7c8ad3474f04cf4423bbd0ee97f201aa751e1a03423d0
3
+ metadata.gz: 58813f26f146e2116b645174ff1c07b4d55fd681c95aeac51d14816668b44211
4
+ data.tar.gz: 212aebb78263261836b669f60c78fcb3148c79c0aee218cd90477458865ecc96
5
5
  SHA512:
6
- metadata.gz: bbf72d63463d30bd732fc79bb254c66aeba2f65773bb488ac697688656b462860c3e1b9b0ac4a0d506ccc7d249dad2cf4130b2e0f7da06d8d1862765f8cf81dc
7
- data.tar.gz: 36ddb1dcdbcf3c3c0540ea5c78461083bc5fc98751a94e864cd81278ead26919006b80bd037d2a57d5d9be557a0b7916a829e04420366abb99db7ba6fb4ad905
6
+ metadata.gz: 4f31381757be0faeeb566131c45a9242b63a8b89fb610ccd6b6abd5bb7b99937890ee6f2e5420ac95022d3e534aeeb48e29dc427206f18217871768caaa84a58
7
+ data.tar.gz: aa9af4d947d47c5a5ae7d43f336e4ee1c4d16b8c4224778f19579061f8215e3508887143607688f831d89229ba7d2c89a5925f9c6f4b0487d73632fb7356f33c
@@ -1,5 +1,6 @@
1
1
  Dir.glob("#{__dir__}/../scripts/platform/android/*.rb").each { |file| require file }
2
2
  Dir.glob("#{__dir__}/../scripts/platform/ios/*.rb").each { |file| require file }
3
+ Dir.glob("#{__dir__}/../scripts/code_generator/*.rb").each { |file| require file }
3
4
 
4
5
  class BrandConfigManager
5
6
  def initialize(brand_key)
@@ -0,0 +1,33 @@
1
+ class ClassNameRegistry
2
+ def initialize(custom_types = {})
3
+ @registry = {}
4
+ @custom_types = custom_types
5
+ end
6
+
7
+ def register(key, class_name)
8
+ @registry[key] = class_name
9
+ end
10
+
11
+ def get_class_name(key, default_name)
12
+ @custom_types[key] || @registry[key] || default_name
13
+ end
14
+
15
+ def get_type(value, default_type)
16
+ type_key = generate_type_key(value, default_type)
17
+ @custom_types[type_key] || default_type
18
+ end
19
+
20
+ private
21
+
22
+ def generate_type_key(value, default_type)
23
+ case value
24
+ when String then "String"
25
+ when Integer then "int"
26
+ when Float then "double"
27
+ when TrueClass, FalseClass then "bool"
28
+ when Array then "List"
29
+ when Hash then default_type # class name
30
+ else default_type
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ module Language
2
+ Kotlin = 'kotlin'
3
+ Swift = 'swift'
4
+ Dart = 'dart'
5
+
6
+ def self.all
7
+ [Kotlin, Swift, Dart]
8
+ end
9
+
10
+ def self.init(language)
11
+ if all.include?(language)
12
+ # Do something with the valid language
13
+ else
14
+ raise ArgumentError, "Invalid language. Please use one of: #{all.join(', ')}"
15
+ end
16
+ end
17
+ end
18
+
19
+ class CodeGenerator
20
+ def initialize(
21
+ json:,
22
+ language:,
23
+ parent_class_name:,
24
+ custom_types: {}
25
+ )
26
+ @json = json
27
+ @language = language
28
+ @parent_class_name = parent_class_name
29
+ @custom_types = custom_types
30
+ @generator = create_language_generator
31
+ end
32
+
33
+ def generate
34
+ @generator.generate unless @generator.nil?
35
+ end
36
+
37
+ private
38
+
39
+ def create_language_generator
40
+ registry = ClassNameRegistry.new(@custom_types)
41
+
42
+ case @language
43
+ when Language::Kotlin
44
+ KotlinCodeGenerator.new(@json, @parent_class_name, registry)
45
+ when Language::Swift
46
+ SwiftCodeGenerator.new(@json, @parent_class_name, registry)
47
+ when Language::Dart
48
+ DartCodeGenerator.new(@json, @parent_class_name, registry)
49
+ else
50
+ raise ArgumentError, "Unsupported language: #{@language}"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,112 @@
1
+ class DartCodeGenerator
2
+ def initialize(json, parent_class_name, registry = nil)
3
+ @json = json
4
+ @parent_class_name = parent_class_name
5
+ @registry = registry || ClassNameRegistry.new
6
+ @generated_classes = []
7
+ end
8
+
9
+ def generate
10
+ imports = "import 'package:flutter/material.dart';\n\n"
11
+ generate_class(@json, @parent_class_name)
12
+ imports + @generated_classes.reverse.join("\n\n")
13
+ end
14
+
15
+ private
16
+
17
+ def generate_class(json_obj, class_name)
18
+ @registry.register(class_name, class_name)
19
+ class_name = @registry.get_class_name(class_name, class_name)
20
+ return if @generated_classes.any? { |c| c.include?("class #{class_name}") }
21
+
22
+ properties = []
23
+ constructor_params = []
24
+ companion_values = []
25
+ instance_values = []
26
+
27
+ json_obj.each do |key, value|
28
+ nested_class_name = "#{class_name}#{StringCase.capitalize(key)}"
29
+ @registry.register(nested_class_name, nested_class_name)
30
+ nested_class_name = @registry.get_class_name(nested_class_name, nested_class_name)
31
+
32
+ type = determine_type(value, nested_class_name, "#{class_name}.#{key}")
33
+
34
+ if value.is_a?(Hash)
35
+ generate_class(value, nested_class_name)
36
+ companion_values << "static final #{key}Value = #{nested_class_name}.instance;"
37
+ elsif value.is_a?(Array) && !value.empty? && value.first.is_a?(Hash)
38
+ generate_class(value.first, nested_class_name)
39
+ type = "List<#{nested_class_name}>"
40
+ list_values = value.map { |_| "#{nested_class_name}.instance" }
41
+ companion_values << "static final #{key}Value = <#{nested_class_name}>[#{list_values.join(", ")}];"
42
+ else
43
+ companion_values << "static final #{key}Value = #{dart_value(value)};"
44
+ end
45
+
46
+ properties << "final #{type} #{key};"
47
+ constructor_params << "required this.#{key}"
48
+ instance_values << "#{key}: #{key}Value"
49
+ end
50
+
51
+ class_code = <<~DART
52
+ class #{class_name} {
53
+ #{properties.join("\n ")}
54
+
55
+ const #{class_name}({#{constructor_params.join(", ")}});
56
+
57
+ static #{class_name}? _instance;
58
+
59
+ #{companion_values.join("\n ")}
60
+
61
+ static #{class_name} get instance {
62
+ _instance ??= #{class_name}(
63
+ #{instance_values.join(",\n ")}
64
+ );
65
+ return _instance!;
66
+ }
67
+ }
68
+ DART
69
+
70
+ @generated_classes << class_code
71
+ end
72
+
73
+ def determine_type(value, class_name, registry_key)
74
+ base_type = if value.is_a?(String) && ColorDetector.new(value).color?
75
+ "Color"
76
+ elsif value.is_a?(String)
77
+ "String"
78
+ elsif value.is_a?(Integer)
79
+ "int"
80
+ elsif value.is_a?(Float)
81
+ "double"
82
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
83
+ "bool"
84
+ elsif value.is_a?(Array)
85
+ item_type = value.empty? ? "dynamic" : determine_type(value.first, class_name, "#{registry_key}[]")
86
+ "List<#{item_type}>"
87
+ elsif value.is_a?(Hash)
88
+ class_name
89
+ else
90
+ "dynamic"
91
+ end
92
+
93
+ @registry.get_type(value, base_type)
94
+ end
95
+
96
+ def dart_value(value)
97
+ if value.is_a?(String) && ColorDetector.new(value).color?
98
+ hex = value.gsub('#', '')
99
+ if hex.length == 8
100
+ return "Color(0x#{hex})"
101
+ else
102
+ return "Color(0xFF#{hex})"
103
+ end
104
+ end
105
+ return "\"#{value}\"" if value.is_a?(String)
106
+ return value.to_s if value.is_a?(Integer) || value.is_a?(Float)
107
+ return value.to_s.downcase if value.is_a?(TrueClass) || value.is_a?(FalseClass)
108
+ return "<#{determine_type(value.first, '', '')}>[#{value.map { |v| dart_value(v) }.join(", ")}]" if value.is_a?(Array)
109
+ return "null" if value.nil?
110
+ value.to_s
111
+ end
112
+ end
@@ -0,0 +1,111 @@
1
+ class KotlinCodeGenerator
2
+ def initialize(json, parent_class_name, registry = nil)
3
+ @json = json
4
+ @parent_class_name = parent_class_name
5
+ @registry = registry || ClassNameRegistry.new
6
+ @generated_classes = []
7
+ end
8
+
9
+ def generate
10
+ imports = "import android.graphics.Color\n\n"
11
+ generate_class(@json, @parent_class_name)
12
+ imports + @generated_classes.reverse.join("\n\n")
13
+ end
14
+
15
+ private
16
+
17
+ def generate_class(json_obj, class_name)
18
+ @registry.register(class_name, class_name)
19
+ class_name = @registry.get_class_name(class_name, class_name)
20
+ return if @generated_classes.any? { |c| c.include?("data class #{class_name}") }
21
+
22
+ properties = []
23
+ companion_values = []
24
+
25
+ json_obj.each do |key, value|
26
+ nested_class_name = "#{class_name}#{StringCase.capitalize(key)}"
27
+ @registry.register(nested_class_name, nested_class_name)
28
+ nested_class_name = @registry.get_class_name(nested_class_name, nested_class_name)
29
+
30
+ type = determine_type(value, nested_class_name, "#{class_name}.#{key}")
31
+
32
+ if value.is_a?(Hash)
33
+ generate_class(value, nested_class_name)
34
+ companion_values << "private val #{key}Value = #{nested_class_name}.instance"
35
+ elsif value.is_a?(Array) && !value.empty? && value.first.is_a?(Hash)
36
+ generate_class(value.first, nested_class_name)
37
+ type = "List<#{nested_class_name}>"
38
+ list_values = value.map { |_| "#{nested_class_name}.instance" }
39
+ companion_values << "private val #{key}Value = listOf(#{list_values.join(", ")})"
40
+ else
41
+ companion_values << "private val #{key}Value = #{kotlin_value(value)}"
42
+ end
43
+
44
+ properties << "val #{key}: #{type}"
45
+ end
46
+
47
+ class_code = <<~KOTLIN
48
+ data class #{class_name}(
49
+ #{properties.join(",\n ")}
50
+ ) {
51
+ companion object {
52
+ private var _instance: #{class_name}? = null
53
+
54
+ #{companion_values.join("\n ")}
55
+
56
+ val instance: #{class_name}
57
+ get() {
58
+ if (_instance == null) {
59
+ _instance = #{class_name}(
60
+ #{json_obj.keys.map { |key| "#{key} = #{key}Value" }.join(",\n ")}
61
+ )
62
+ }
63
+ return _instance!!
64
+ }
65
+ }
66
+ }
67
+ KOTLIN
68
+
69
+ @generated_classes << class_code
70
+ end
71
+
72
+ def determine_type(value, class_name, registry_key)
73
+ base_type = if value.is_a?(String) && ColorDetector.new(value).color?
74
+ "Int"
75
+ elsif value.is_a?(String)
76
+ "String"
77
+ elsif value.is_a?(Integer)
78
+ "Int"
79
+ elsif value.is_a?(Float)
80
+ "Double"
81
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
82
+ "Boolean"
83
+ elsif value.is_a?(Array)
84
+ item_type = value.empty? ? "Any" : determine_type(value.first, class_name, "#{registry_key}[]")
85
+ "List<#{item_type}>"
86
+ elsif value.is_a?(Hash)
87
+ class_name
88
+ else
89
+ "Any"
90
+ end
91
+
92
+ @registry.get_type(value, base_type)
93
+ end
94
+
95
+ def kotlin_value(value)
96
+ if value.is_a?(String) && ColorDetector.new(value).color?
97
+ hex = value.gsub('#', '')
98
+ if hex.length == 8
99
+ return "Color.parseColor(\"##{hex}\")"
100
+ else
101
+ return "Color.parseColor(\"##{hex}\")"
102
+ end
103
+ end
104
+ return "\"#{value}\"" if value.is_a?(String)
105
+ return value.to_s if value.is_a?(Integer) || value.is_a?(Float)
106
+ return value.to_s.downcase if value.is_a?(TrueClass) || value.is_a?(FalseClass)
107
+ return "listOf(#{value.map { |v| kotlin_value(v) }.join(", ")})" if value.is_a?(Array)
108
+ return "null" if value.nil?
109
+ value.to_s
110
+ end
111
+ end
@@ -0,0 +1,108 @@
1
+ class SwiftCodeGenerator
2
+ def initialize(json, parent_class_name, registry = nil)
3
+ @json = json
4
+ @parent_class_name = parent_class_name
5
+ @registry = registry || ClassNameRegistry.new
6
+ @generated_classes = []
7
+ end
8
+
9
+ def generate
10
+ imports = "import UIKit\n\n"
11
+ generate_class(@json, @parent_class_name)
12
+ imports + @generated_classes.reverse.join("\n\n")
13
+ end
14
+
15
+ private
16
+
17
+ def generate_class(json_obj, class_name)
18
+ @registry.register(class_name, class_name)
19
+ class_name = @registry.get_class_name(class_name, class_name)
20
+ return if @generated_classes.any? { |c| c.include?("struct #{class_name}") }
21
+
22
+ properties = []
23
+ static_values = []
24
+
25
+ json_obj.each do |key, value|
26
+ nested_class_name = "#{class_name}#{StringCase.capitalize(key)}"
27
+ @registry.register(nested_class_name, nested_class_name)
28
+ nested_class_name = @registry.get_class_name(nested_class_name, nested_class_name)
29
+
30
+ type = determine_type(value, nested_class_name, "#{class_name}.#{key}")
31
+
32
+ if value.is_a?(Hash)
33
+ generate_class(value, nested_class_name)
34
+ static_values << "private static let #{key}Value = #{nested_class_name}.shared"
35
+ elsif value.is_a?(Array) && !value.empty? && value.first.is_a?(Hash)
36
+ generate_class(value.first, nested_class_name)
37
+ type = "[#{nested_class_name}]"
38
+ list_values = value.map { |_| "#{nested_class_name}.shared" }
39
+ static_values << "private static let #{key}Value = [#{list_values.join(", ")}]"
40
+ else
41
+ static_values << "private static let #{key}Value = #{swift_value(value)}"
42
+ end
43
+
44
+ properties << "let #{key}: #{type}"
45
+ end
46
+
47
+ class_code = <<~SWIFT
48
+ struct #{class_name} {
49
+ #{properties.join("\n ")}
50
+
51
+ private static var _shared: #{class_name}?
52
+
53
+ #{static_values.join("\n ")}
54
+
55
+ static var shared: #{class_name} {
56
+ if _shared == nil {
57
+ _shared = #{class_name}(
58
+ #{json_obj.keys.map { |key| "#{key}: #{key}Value" }.join(",\n ")}
59
+ )
60
+ }
61
+ return _shared!
62
+ }
63
+ }
64
+ SWIFT
65
+
66
+ @generated_classes << class_code
67
+ end
68
+
69
+ def determine_type(value, class_name, registry_key)
70
+ base_type = if value.is_a?(String) && ColorDetector.new(value).color?
71
+ "UIColor"
72
+ elsif value.is_a?(String)
73
+ "String"
74
+ elsif value.is_a?(Integer)
75
+ "Int"
76
+ elsif value.is_a?(Float)
77
+ "Double"
78
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
79
+ "Bool"
80
+ elsif value.is_a?(Array)
81
+ item_type = value.empty? ? "Any" : determine_type(value.first, class_name, "#{registry_key}[]")
82
+ "[#{item_type}]"
83
+ elsif value.is_a?(Hash)
84
+ class_name
85
+ else
86
+ "Any"
87
+ end
88
+
89
+ @registry.get_type(value, base_type)
90
+ end
91
+
92
+ def swift_value(value)
93
+ if value.is_a?(String) && ColorDetector.new(value).color?
94
+ hex = value.gsub('#', '')
95
+ r = "Double(0x#{hex[0,2]}) / 255.0"
96
+ g = "Double(0x#{hex[2,2]}) / 255.0"
97
+ b = "Double(0x#{hex[4,2]}) / 255.0"
98
+ a = hex.length == 8 ? "Double(0x#{hex[6,2]}) / 255.0" : "1.0"
99
+ return "UIColor(red: #{r}, green: #{g}, blue: #{b}, alpha: #{a})"
100
+ end
101
+ return "\"#{value}\"" if value.is_a?(String)
102
+ return value.to_s if value.is_a?(Integer) || value.is_a?(Float)
103
+ return value.to_s.downcase if value.is_a?(TrueClass) || value.is_a?(FalseClass)
104
+ return "[#{value.map { |v| swift_value(v) }.join(", ")}]" if value.is_a?(Array)
105
+ return "nil" if value.nil?
106
+ value.to_s
107
+ end
108
+ end
@@ -0,0 +1,10 @@
1
+ class ColorDetector
2
+ def initialize(value)
3
+ @value = value
4
+ end
5
+
6
+ def color?
7
+ # Check for 6-character (RGB) or 8-character (RGBA) hex color formats
8
+ @value.is_a?(String) && @value.match?(/^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/)
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module Solara
2
- VERSION = "0.7.2"
2
+ VERSION = "0.7.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Malek Kamel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-28 00:00:00.000000000 Z
11
+ date: 2024-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -245,7 +245,12 @@ files:
245
245
  - solara/lib/core/scripts/brand_importer.rb
246
246
  - solara/lib/core/scripts/brand_offboarder.rb
247
247
  - solara/lib/core/scripts/brand_resources_manager.rb
248
- - solara/lib/core/scripts/code_generator.rb
248
+ - solara/lib/core/scripts/code_generator/class_name_registry.rb
249
+ - solara/lib/core/scripts/code_generator/code_generator.rb
250
+ - solara/lib/core/scripts/code_generator/dart_code_generator.rb
251
+ - solara/lib/core/scripts/code_generator/kotlin_code_generator.rb
252
+ - solara/lib/core/scripts/code_generator/swift_code_generator.rb
253
+ - solara/lib/core/scripts/color_detector.rb
249
254
  - solara/lib/core/scripts/directory_creator.rb
250
255
  - solara/lib/core/scripts/file_manager.rb
251
256
  - solara/lib/core/scripts/file_path.rb
@@ -1,715 +0,0 @@
1
- module Language
2
- Kotlin = 'kotlin'
3
- Swift = 'swift'
4
- Dart = 'dart'
5
-
6
- def self.all
7
- [Kotlin, Swift, Dart]
8
- end
9
-
10
- def self.init(language)
11
- if all.include?(language)
12
- # Do something with the valid language
13
- else
14
- raise ArgumentError, "Invalid language. Please use one of: #{all.join(', ')}"
15
- end
16
- end
17
- end
18
-
19
- class ClassNameRegistry
20
- def initialize(custom_types)
21
- @custom_types = custom_types
22
- @registry = {}
23
- end
24
-
25
- def register(original_name, prefixed_name, context = [])
26
- full_context = (context + [original_name]).join('.')
27
- @registry[full_context] = prefixed_name
28
- end
29
-
30
- def get(name, context = [])
31
- full_context = (context + [name]).join('.')
32
- result = @registry[full_context] || name
33
- @custom_types[result] || result
34
- end
35
-
36
- def original_names
37
- @registry.keys
38
- end
39
-
40
- def prefixed_names
41
- @registry.values
42
- end
43
- end
44
-
45
- class CodeGenerator
46
- def initialize(
47
- json:,
48
- language:,
49
- parent_class_name:,
50
- custom_types: {}
51
- )
52
- @json = json
53
- @language = language
54
- @parent_class_name = parent_class_name
55
- @custom_types = custom_types
56
- @generator = create_language_generator
57
- end
58
-
59
- def generate
60
- @generator.generate_content
61
- end
62
-
63
- private
64
-
65
- def create_language_generator
66
- case @language
67
- when Language::Kotlin
68
- KotlinCodeGenerator.new(@json, @parent_class_name, @custom_types)
69
- when Language::Swift
70
- SwiftCodeGenerator.new(@json, @parent_class_name, @custom_types)
71
- when Language::Dart
72
- DartCodeGenerator.new(@json, @parent_class_name, @custom_types)
73
- else
74
- raise ArgumentError, "Unsupported language: #{@language}"
75
- end
76
- end
77
- end
78
-
79
- class BaseCodeGenerator
80
- def initialize(json, parent_class_name, custom_types)
81
- @parent_class_name = parent_class_name
82
- @json = json
83
- @custom_types = custom_types
84
- @class_registry = ClassNameRegistry.new(custom_types)
85
- register_class_names(@parent_class_name, @json)
86
- end
87
-
88
- def generate_content
89
- content = "// Generated by Solara\n"
90
- content += language_specific_imports
91
-
92
- classes = []
93
- generate_classes(@parent_class_name, @json, classes, generate_json: true)
94
-
95
- classes.reverse_each do |class_content|
96
- content += class_content
97
- content += "\n"
98
- end
99
-
100
- other_classes = language_specific_classes
101
- other_classes.each do |item|
102
- content += item
103
- end
104
-
105
- content + "\n"
106
- end
107
-
108
- private
109
-
110
- def register_class_names(class_name, json, context = [])
111
- full_context = context + [class_name]
112
- prefixed_name = full_context.join
113
- @class_registry.register(class_name, prefixed_name, context)
114
-
115
- json.each do |key, value|
116
- if value.is_a?(Hash)
117
- nested_class_name = capitalize(key)
118
- register_class_names(nested_class_name, value, full_context)
119
- elsif value.is_a?(Array) && value.any? { |item| item.is_a?(Hash) }
120
- nested_class_name = "#{capitalize(key)}Item"
121
- register_class_names(nested_class_name, value.first, full_context)
122
- end
123
- end
124
- end
125
-
126
- def generate_classes(class_name, json, classes, context = [], generate_json: false)
127
- full_context = context + [class_name]
128
- prefixed_class_name = @class_registry.get(class_name, context)
129
- content = class_declaration(prefixed_class_name)
130
- constructor_params = []
131
-
132
- json.each do |key, value|
133
- type = value_type(value, key, full_context)
134
- content += property_declaration(key, type, json)
135
- constructor_params << constructor_parameter(key, type)
136
- end
137
-
138
- content += property_declaration("asJson", "String", json)
139
-
140
- content += constructor_declaration(prefixed_class_name, constructor_params)
141
- content += instance_declaration(prefixed_class_name, json, full_context)
142
- content += json_methods(json, prefixed_class_name, full_context)
143
- content += class_closing
144
-
145
- classes << content
146
-
147
- json.each do |key, value|
148
- if value.is_a?(Hash)
149
- nested_class_name = capitalize(key)
150
- generate_classes(nested_class_name, value, classes, full_context)
151
- elsif value.is_a?(Array) && value.any? { |item| item.is_a?(Hash) }
152
- nested_class_name = "#{capitalize(key)}Item"
153
- generate_classes(nested_class_name, value.first, classes, full_context)
154
- end
155
- end
156
- end
157
-
158
- def class_declaration(class_name)
159
- raise NotImplementedError, "Subclasses must implement class_declaration"
160
- end
161
-
162
- def property_declaration(key, type, json)
163
- raise NotImplementedError, "Subclasses must implement property_declaration"
164
- end
165
-
166
- def color_type
167
- raise NotImplementedError, "Subclasses must implement property_declaration"
168
- end
169
-
170
- def constructor_parameter(key, type)
171
- raise NotImplementedError, "Subclasses must implement constructor_parameter"
172
- end
173
-
174
- def constructor_declaration(class_name, params)
175
- raise NotImplementedError, "Subclasses must implement constructor_declaration"
176
- end
177
-
178
- def instance_declaration(class_name, json, context)
179
- raise NotImplementedError, "Subclasses must implement instance_declaration"
180
- end
181
-
182
- def json_methods(json, class_name, context)
183
- raise NotImplementedError, "Subclasses must implement json_methods"
184
- end
185
-
186
- def class_closing
187
- raise NotImplementedError, "Subclasses must implement class_closing"
188
- end
189
-
190
- def language_specific_imports
191
- raise NotImplementedError, "Subclasses must implement language_specific_imports"
192
- end
193
-
194
- def value_type(value, class_prefix, context = [])
195
- raise NotImplementedError, "Subclasses must implement value_type"
196
- end
197
-
198
- def value_for(value, class_prefix, indent, context = [])
199
- raise NotImplementedError, "Subclasses must implement value_for"
200
- end
201
-
202
- def color_for(value)
203
- raise NotImplementedError, "Subclasses must implement color_for"
204
- end
205
-
206
- def language_specific_null
207
- raise NotImplementedError, "Subclasses must implement language_specific_null"
208
- end
209
-
210
- def language_specific_classes
211
- []
212
- end
213
-
214
- def capitalize(string)
215
- StringCase.capitalize(string)
216
- end
217
- end
218
-
219
- class KotlinCodeGenerator < BaseCodeGenerator
220
- def language_specific_imports
221
- "import android.graphics.Color\n" +
222
- "import java.io.Serializable\n\n"
223
- end
224
-
225
- def class_declaration(class_name)
226
- "\ndata class #{class_name}(\n"
227
- end
228
-
229
- def property_declaration(key, type, json)
230
- if key == "asJson"
231
- json_string = json.to_json.gsub('"', '\\"')
232
- " val #{key}: String = \"#{json_string}\",\n"
233
- elsif json[key].is_a?(String) && ColorDetector.new(json[key]).color?
234
- " val #{key}: #{color_type},\n"
235
- else
236
- " val #{key}: #{type},\n"
237
- end
238
- end
239
-
240
- def color_type
241
- "Int"
242
- end
243
-
244
- def constructor_parameter(key, type)
245
- "val #{key}: #{type}"
246
- end
247
-
248
- def constructor_declaration(class_name, params)
249
- "): Serializable {\n"
250
- end
251
-
252
- def instance_declaration(class_name, json, context)
253
- " companion object {\n val instance = #{class_name}(\n#{json.map { |k, v| " #{k} = #{value_for(v, k, ' ', context)}" }.join(",\n")}\n )\n }\n"
254
- end
255
-
256
- def json_methods(json, class_name, context)
257
- ""
258
- end
259
-
260
- def class_closing
261
- "}\n"
262
- end
263
-
264
- def value_for(value, class_prefix, indent, context = [])
265
- case value
266
- when String
267
- if ColorDetector.new(value).color?
268
- color_for(value)
269
- else
270
- "\"#{value}\"" # Use double quotes for Kotlin strings
271
- end
272
- when Integer
273
- value.to_s
274
- when Float
275
- "#{value}F" # Add 'F' suffix for float values
276
- when TrueClass, FalseClass
277
- value.to_s
278
- when Array
279
- if value.empty?
280
- "emptyList()" # Use Kotlin's emptyList() for empty arrays
281
- elsif value.all? { |item| item.is_a?(Hash) }
282
- array_items = value.map do |item|
283
- item_values = item.map { |k, v| "#{k} = #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
284
- "#{@class_registry.get("#{capitalize(class_prefix)}Item")}(\n#{indent} #{item_values}\n#{indent} )"
285
- end.join(",\n#{indent} ")
286
- "listOf(\n#{indent} #{array_items}\n#{indent})"
287
- else
288
- array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
289
- "listOf(\n#{indent} #{array_items}\n#{indent})" # Use listOf for non-empty lists
290
- end
291
- when Hash
292
- "#{@class_registry.get(capitalize(class_prefix), context)}.instance"
293
- else
294
- language_specific_null
295
- end
296
- end
297
-
298
- def value_type(value, class_prefix, context = [])
299
- case value
300
- when String
301
- if ColorDetector.new(value).color?
302
- return color_type
303
- end
304
- return 'String'
305
- when Integer then 'Int'
306
- when Float then 'Float'
307
- when TrueClass, FalseClass then 'Boolean'
308
- when Array
309
- if value.empty?
310
- 'List<Any>'
311
- elsif value.all? { |item| item.is_a?(String) }
312
- 'List<String>'
313
- elsif value.all? { |item| item.is_a?(Integer) }
314
- 'List<Int>'
315
- elsif value.all? { |item| item.is_a?(Float) }
316
- 'List<Float>'
317
- elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
318
- 'List<Boolean>'
319
- elsif value.all? { |item| item.is_a?(Hash) }
320
- "List<#{@class_registry.get("#{capitalize(class_prefix)}Item")}>"
321
- else
322
- 'List<Any>'
323
- end
324
- when Hash then @class_registry.get(capitalize(class_prefix), context)
325
- else 'Any'
326
- end
327
- end
328
-
329
- def color_for(value)
330
- "Color.parseColor(\"#{value}\")"
331
- end
332
-
333
- def language_specific_null
334
- "null"
335
- end
336
- end
337
-
338
- class SwiftCodeGenerator < BaseCodeGenerator
339
- def language_specific_imports
340
- "import Foundation\n" +
341
- "import UIKit\n\n"
342
- end
343
-
344
- def class_declaration(class_name)
345
- "\nclass #{class_name}: Codable {\n"
346
- end
347
-
348
- def property_declaration(key, type, json)
349
- if key == "asJson"
350
- json_string = json.to_json.gsub('"', '\\"')
351
- " let #{key}: String = \"#{json_string}\"\n"
352
- elsif json[key].is_a?(String) && ColorDetector.new(json[key]).color?
353
- " let #{key}: #{color_type}\n"
354
- else
355
- " let #{key}: #{type}\n"
356
- end
357
- end
358
-
359
- def color_type
360
- "UIColor"
361
- end
362
-
363
- def constructor_parameter(key, type)
364
- "#{key}: #{type}"
365
- end
366
-
367
- def constructor_declaration(class_name, params)
368
- " init(#{params.join(", ")}) {\n" +
369
- params.map { |param| " self.#{param.split(":").first} = #{param.split(":").first}" }.join("\n") +
370
- "\n }\n\n"
371
- end
372
-
373
- def instance_declaration(class_name, json, context)
374
- " static let shared = #{class_name}(\n" +
375
- json.map { |k, v| " #{k}: #{value_for(v, k, ' ', context)}" }.join(",\n") +
376
- "\n )\n\n"
377
- end
378
-
379
- def json_methods(json, class_name, context)
380
- prefixed_class_name = @class_registry.get(capitalize(class_name))
381
- coding_keys = json.map { |key, _| " case #{key}" }.join("\n")
382
-
383
- <<-SWIFT
384
- private enum CodingKeys: String, CodingKey {
385
- #{coding_keys}
386
- }
387
-
388
- func toJson() -> String? {
389
- let encoder = JSONEncoder()
390
- if let jsonData = try? encoder.encode(self) {
391
- return String(data: jsonData, encoding: .utf8)
392
- }
393
- return nil
394
- }
395
-
396
- required init(from decoder: Decoder) throws {
397
- let container = try decoder.container(keyedBy: CodingKeys.self)
398
- #{json.map { |key, value|
399
- if value.is_a?(String) && ColorDetector.new(value).color?
400
- "self.#{key} = UIColor(hex: try container.decode(String.self, forKey: .#{key}))"
401
- else
402
- "self.#{key} = try container.decode(#{value_type(value, key, context)}.self, forKey: .#{key})"
403
- end
404
- }.join("\n ")}
405
- }
406
-
407
- func encode(to encoder: Encoder) throws {
408
- var container = encoder.container(keyedBy: CodingKeys.self)
409
- #{json.map { |key, value|
410
- if value.is_a?(String) && ColorDetector.new(value).color?
411
- "try container.encode(#{key}.toHexString(), forKey: .#{key})"
412
- else
413
- "try container.encode(#{key}, forKey: .#{key})"
414
- end
415
- }.join("\n ")}
416
- }
417
-
418
- static func fromJson(_ json: String) -> #{prefixed_class_name}? {
419
- let decoder = JSONDecoder()
420
- if let jsonData = json.data(using: .utf8),
421
- let result = try? decoder.decode(#{prefixed_class_name}.self, from: jsonData) {
422
- return result
423
- }
424
- return nil
425
- }
426
- SWIFT
427
- end
428
-
429
- def class_closing
430
- "}\n"
431
- end
432
-
433
- def value_for(value, class_prefix, indent, context = [])
434
- case value
435
- when String
436
- if ColorDetector.new(value).color?
437
- color_for(value)
438
- else
439
- "\"#{value}\""
440
- end
441
- when Integer, Float
442
- value.to_s
443
- when TrueClass, FalseClass
444
- value.to_s
445
- when Array
446
- if value.empty?
447
- "[]"
448
- elsif value.all? { |item| item.is_a?(Hash) }
449
- array_items = value.map do |item|
450
- item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
451
- "#{@class_registry.get("#{capitalize(class_prefix)}Item")}(\n#{indent} #{item_values}\n#{indent} )"
452
- end.join(",\n#{indent} ")
453
- "[\n#{indent} #{array_items}\n#{indent}]"
454
- else
455
- array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
456
- "[\n#{indent} #{array_items}\n#{indent}]"
457
- end
458
- when Hash
459
- "#{@class_registry.get(capitalize(class_prefix), context)}.shared"
460
- else
461
- language_specific_null
462
- end
463
- end
464
-
465
- def value_type(value, class_prefix, context = [])
466
- case value
467
- when String
468
- if ColorDetector.new(value).color?
469
- return color_type
470
- end
471
- return 'String'
472
- when Integer then 'Int'
473
- when Float then 'Double'
474
- when TrueClass, FalseClass then 'Bool'
475
- when Array
476
- if value.empty?
477
- '[Any]'
478
- elsif value.all? { |item| item.is_a?(String) }
479
- '[String]'
480
- elsif value.all? { |item| item.is_a?(Integer) }
481
- '[Int]'
482
- elsif value.all? { |item| item.is_a?(Float) }
483
- '[Double]'
484
- elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
485
- '[Bool]'
486
- elsif value.all? { |item| item.is_a?(Hash) }
487
- "[#{@class_registry.get("#{capitalize(class_prefix)}Item")}]"
488
- else
489
- '[Any]'
490
- end
491
- when Hash then @class_registry.get(capitalize(class_prefix), context)
492
- else 'Any'
493
- end
494
- end
495
-
496
- def color_for(value)
497
- "UIColor(hex: \"#{value}\")"
498
- end
499
-
500
- def language_specific_null
501
- "nil"
502
- end
503
-
504
- def language_specific_classes
505
- [
506
- <<-SWIFT
507
- private extension UIColor {
508
- convenience init(hex: String) {
509
- let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
510
- var int = UInt64()
511
- Scanner(string: hex).scanHexInt64(&int)
512
- let a, r, g, b: UInt64
513
- switch hex.count {
514
- case 3: // RGB (12-bit)
515
- (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
516
- case 6: // RGB (24-bit)
517
- (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
518
- case 8: // ARGB (32-bit)
519
- (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
520
- default:
521
- (a, r, g, b) = (255, 0, 0, 0)
522
- }
523
- self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
524
- }
525
-
526
- func toHexString() -> String {
527
- var r:CGFloat = 0
528
- var g:CGFloat = 0
529
- var b:CGFloat = 0
530
- var a:CGFloat = 0
531
-
532
- getRed(&r, green: &g, blue: &b, alpha: &a)
533
-
534
- let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
535
-
536
- return String(format: "#%06x", rgb)
537
- }
538
- }
539
- SWIFT
540
- ]
541
- end
542
- end
543
-
544
- class DartCodeGenerator < BaseCodeGenerator
545
- def language_specific_imports
546
- "import 'dart:convert';\n" +
547
- "import 'package:flutter/material.dart';\n\n"
548
- end
549
-
550
- def class_declaration(class_name)
551
- "\nclass #{class_name} {\n"
552
- end
553
-
554
- def property_declaration(key, type, json)
555
- if key == "asJson"
556
- json_string = json.to_json.gsub('"', '\\"')
557
- " final String #{key} = \"#{json_string}\";\n"
558
- elsif json[key].is_a?(String) && ColorDetector.new(json[key]).color?
559
- " final #{color_type} #{key};\n"
560
- else
561
- " final #{type} #{key};\n"
562
- end
563
- end
564
-
565
- def color_type
566
- "Color"
567
- end
568
-
569
- def constructor_parameter(key, type)
570
- "\n required this.#{key}"
571
- end
572
-
573
- def constructor_declaration(class_name, params)
574
- " const #{class_name}({#{params.join(", ")}});\n\n"
575
- end
576
-
577
- def instance_declaration(class_name, json, context)
578
- " static const #{class_name} instance = #{class_name}(\n" +
579
- json.map { |k, v| " #{k}: #{value_for(v, k, ' ', context)}" }.join(",\n") +
580
- "\n );\n\n"
581
- end
582
-
583
- def json_methods(json, class_name, context)
584
- prefixed_class_name = @class_registry.get(class_name)
585
- from_json_content = json.map do |key, value|
586
- if value.is_a?(String) && ColorDetector.new(value).color?
587
- " #{key}: Color(int.parse(json['#{key}'].substring(1, 7), radix: 16) + 0xFF000000)"
588
- else
589
- " #{key}: json['#{key}']"
590
- end
591
- end.join(",\n")
592
-
593
- to_json_content = json.map do |key, value|
594
- if value.is_a?(String) && ColorDetector.new(value).color?
595
- "'#{key}': '#${#{key}.value.toRadixString(16).padLeft(8, '0').substring(2)}'"
596
- else
597
- "'#{key}': #{key}"
598
- end
599
- end.join(",\n ")
600
-
601
- <<-DART
602
- factory #{prefixed_class_name}.fromJson(Map<String, dynamic> json) {
603
- return #{prefixed_class_name}(
604
- #{from_json_content}
605
- );
606
- }
607
-
608
- Map<String, dynamic> toJson() {
609
- return {
610
- #{to_json_content}
611
- };
612
- }
613
-
614
- String toJsonString() {
615
- return json.encode(toJson());
616
- }
617
-
618
- static #{prefixed_class_name}? fromJsonString(String jsonString) {
619
- try {
620
- final Map<String, dynamic> jsonMap = json.decode(jsonString);
621
- return #{prefixed_class_name}.fromJson(jsonMap);
622
- } catch (e) {
623
- print('Error parsing JSON: $e');
624
- return null;
625
- }
626
- }
627
- DART
628
- end
629
-
630
- def class_closing
631
- "}\n"
632
- end
633
-
634
- def value_for(value, class_prefix, indent, context = [])
635
- case value
636
- when String
637
- if ColorDetector.new(value).color?
638
- color_for(value)
639
- else
640
- "\"#{value}\""
641
- end
642
- when Integer, Float
643
- value.to_s
644
- when TrueClass, FalseClass
645
- value.to_s
646
- when Array
647
- if value.empty?
648
- "[]"
649
- elsif value.all? { |item| item.is_a?(Hash) }
650
- array_items = value.map do |item|
651
- item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
652
- "#{@class_registry.get("#{capitalize(class_prefix)}Item")}(\n#{indent} #{item_values}\n#{indent} )"
653
- end.join(",\n#{indent} ")
654
- "[\n#{indent} #{array_items}\n#{indent}]"
655
- else
656
- array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
657
- "[\n#{indent} #{array_items}\n#{indent}]"
658
- end
659
- when Hash
660
- "#{@class_registry.get(capitalize(class_prefix), context)}.instance"
661
- else
662
- language_specific_null
663
- end
664
- end
665
-
666
- def value_type(value, class_prefix, context = [])
667
- case value
668
- when String
669
- if ColorDetector.new(value).color?
670
- return color_type
671
- end
672
- return 'String'
673
- when Integer then 'int'
674
- when Float then 'double'
675
- when TrueClass, FalseClass then 'bool'
676
- when Array
677
- if value.empty?
678
- 'List<dynamic>'
679
- elsif value.all? { |item| item.is_a?(String) }
680
- 'List<String>'
681
- elsif value.all? { |item| item.is_a?(Integer) }
682
- 'List<int>'
683
- elsif value.all? { |item| item.is_a?(Float) }
684
- 'List<double>'
685
- elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
686
- 'List<bool>'
687
- elsif value.all? { |item| item.is_a?(Hash) }
688
- "List<#{@class_registry.get("#{capitalize(class_prefix)}Item")}>"
689
- else
690
- 'List<dynamic>'
691
- end
692
- when Hash then @class_registry.get(capitalize(class_prefix), context)
693
- else 'dynamic'
694
- end
695
- end
696
-
697
- def color_for(value)
698
- "Color(0xFF#{value[1..-1]})"
699
- end
700
-
701
- def language_specific_null
702
- "null"
703
- end
704
- end
705
-
706
- class ColorDetector
707
- def initialize(value)
708
- @value = value
709
- end
710
-
711
- def color?
712
- # Check for 6-character (RGB) or 8-character (RGBA) hex color formats
713
- @value.is_a?(String) && @value.match?(/^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/)
714
- end
715
- end