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 +4 -4
- data/solara/lib/core/scripts/brand_config_manager.rb +1 -0
- data/solara/lib/core/scripts/code_generator/class_name_registry.rb +33 -0
- data/solara/lib/core/scripts/code_generator/code_generator.rb +53 -0
- data/solara/lib/core/scripts/code_generator/dart_code_generator.rb +112 -0
- data/solara/lib/core/scripts/code_generator/kotlin_code_generator.rb +111 -0
- data/solara/lib/core/scripts/code_generator/swift_code_generator.rb +108 -0
- data/solara/lib/core/scripts/color_detector.rb +10 -0
- data/solara/lib/solara/version.rb +1 -1
- metadata +8 -3
- data/solara/lib/core/scripts/code_generator.rb +0 -715
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58813f26f146e2116b645174ff1c07b4d55fd681c95aeac51d14816668b44211
|
4
|
+
data.tar.gz: 212aebb78263261836b669f60c78fcb3148c79c0aee218cd90477458865ecc96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
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-
|
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
|