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 +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
|