solara 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
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