solara 0.2.4 → 0.3.0

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: 3569c89fbad4ec23d107130f47962dc637776fd22eddea41671ce884b4ec0f84
4
- data.tar.gz: f02d2e5812438e81be770a3a45851b631dd9e1bcd2a190a7a0c36ad7d539cfc9
3
+ metadata.gz: a886519a93f39b2e87df6f2f427fa72e6baf9d4f3520424a7c86c207cb684e56
4
+ data.tar.gz: e5f0f1673493a4177eaf70ba1af01ef6de900074f8c934c6b15600197f2af474
5
5
  SHA512:
6
- metadata.gz: a877b5a58528e138d5b1f40958eb1fc015c2c0ecddc2d4f7a5c2db26e781e5a49aa065e9dd4c475d2ce14211399592dc3400c92f6fe50704d2b1e26c7b0cfc73
7
- data.tar.gz: 39bfd976632d87ef961de7a51f4cf12440a6e39900025bf3287efe7f302f9628967477743fabbb9398caf3b29c23b74d2305c4fdba5baed917af59ce0d9b505e
6
+ metadata.gz: cd784de6b07c8208c290f6cdb00827e0aa2e44f0f10d19e584592bca6d1aa2d7c7bb195c599286500dbacb71662956b6247f1fb7ca82daf40f0c60ab0022e4ad
7
+ data.tar.gz: 0e920bd5ba5938eac7933c40708df506762620bb3466ff743851361f414845d396c363e66145008b3b46a539ca3633f2117ecee6c44cc862764f3014112a8df6
@@ -1,4 +1,3 @@
1
- // app.js
2
1
  import BrandDetailModel from './BrandDetailModel.js';
3
2
  import BrandDetailView from './BrandDetailView.js';
4
3
  import BrandDetailController from './BrandDetailController.js';
@@ -13,13 +13,17 @@ class BrandConfigManager
13
13
  Solara.logger.start_step("Generate #{name} for #{platform}")
14
14
  config = load_config(FilePath.brand_config(@brand_key))
15
15
  add_basic_brand_info(config, platform)
16
- config_generator = BrandConfigGenerator.new(
17
- config,
18
- FilePath.generated_config(name, platform),
19
- language,
20
- platform
16
+ config_generator = CodeGenerator.new(
17
+ json: config,
18
+ language: language,
19
+ parent_class_name: 'BrandConfig',
21
20
  )
22
- config_generator.generate
21
+ output_dir = FilePath.generated_config(name, platform)
22
+ content = config_generator.generate
23
+ FileManager.create_file_if_not_exist(output_dir)
24
+ File.write(output_dir, content)
25
+ Solara.logger.debug("Generated brand config #{@output_dir} for: #{@language}")
26
+
23
27
  Solara.logger.end_step("Generate #{name} for #{platform}")
24
28
  end
25
29
 
@@ -0,0 +1,491 @@
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
+ @json = json
25
+ @language = language
26
+ @parent_class_name = parent_class_name
27
+ @generator = create_language_generator
28
+ end
29
+
30
+ def generate
31
+ @generator.generate_content
32
+ end
33
+
34
+ private
35
+
36
+ def create_language_generator
37
+ case @language
38
+ when Language::Kotlin
39
+ KotlinCodeGenerator.new(@json, @parent_class_name)
40
+ when Language::Swift
41
+ SwiftCodeGenerator.new(@json, @parent_class_name)
42
+ when Language::Dart
43
+ DartCodeGenerator.new(@json, @parent_class_name)
44
+ else
45
+ raise ArgumentError, "Unsupported language: #{@language}"
46
+ end
47
+ end
48
+ end
49
+
50
+ class BaseCodeGenerator
51
+ def initialize(json, parent_class_name)
52
+ @parent_class_name = parent_class_name
53
+ @json = json
54
+ end
55
+
56
+ def generate_content
57
+ content = "// Generated by Solara\n"
58
+ content += language_specific_imports
59
+
60
+ classes = []
61
+ generate_classes(@parent_class_name, @json, classes, generate_json: true)
62
+
63
+ classes.reverse_each do |class_content|
64
+ content += class_content
65
+ content += "\n"
66
+ end
67
+
68
+ content
69
+ end
70
+
71
+ def generate_classes(class_name, json, classes, generate_json: false)
72
+ content = class_declaration(class_name)
73
+ constructor_params = []
74
+
75
+ json.each do |key, value|
76
+ type = value_type(value, key)
77
+ content += property_declaration(key, type, json)
78
+ constructor_params << constructor_parameter(key, type)
79
+ end
80
+
81
+ content += property_declaration("asJson", "String", json)
82
+
83
+ content += constructor_declaration(class_name, constructor_params)
84
+ content += instance_declaration(class_name, json)
85
+ content += json_methods(json, class_name)
86
+ content += class_closing
87
+
88
+ classes << content
89
+
90
+ json.each do |key, value|
91
+ if value.is_a?(Hash)
92
+ nested_class_name = "#{key[0].upcase}#{key[1..-1]}" # Capitalize first character
93
+ generate_classes(nested_class_name, value, classes)
94
+ elsif value.is_a?(Array) && value.any? { |item| item.is_a?(Hash) }
95
+ nested_class_name = "#{key[0].upcase}#{key[1..-1]}Item" # Capitalize first character
96
+ generate_classes(nested_class_name, value.first, classes)
97
+ end
98
+ end
99
+ end
100
+
101
+ def class_declaration(class_name)
102
+ raise NotImplementedError, "Subclasses must implement class_declaration"
103
+ end
104
+
105
+ def property_declaration(key, type, json)
106
+ raise NotImplementedError, "Subclasses must implement property_declaration"
107
+ end
108
+
109
+ def constructor_parameter(key, type)
110
+ raise NotImplementedError, "Subclasses must implement constructor_parameter"
111
+ end
112
+
113
+ def constructor_declaration(class_name, params)
114
+ raise NotImplementedError, "Subclasses must implement constructor_declaration"
115
+ end
116
+
117
+ def instance_declaration(class_name, json)
118
+ raise NotImplementedError, "Subclasses must implement instance_declaration"
119
+ end
120
+
121
+ def json_methods(json, class_name)
122
+ raise NotImplementedError, "Subclasses must implement json_methods"
123
+ end
124
+
125
+ def class_closing
126
+ raise NotImplementedError, "Subclasses must implement class_closing"
127
+ end
128
+
129
+ def language_specific_imports
130
+ raise NotImplementedError, "Subclasses must implement language_specific_imports"
131
+ end
132
+
133
+ def value_type(value, class_prefix)
134
+ raise NotImplementedError, "Subclasses must implement value_type"
135
+ end
136
+
137
+ def value_for(value, class_prefix, indent)
138
+ raise NotImplementedError, "Subclasses must implement value_type"
139
+ end
140
+
141
+ def color_for(value)
142
+ raise NotImplementedError, "Subclasses must implement color_for"
143
+ end
144
+
145
+ def language_specific_null
146
+ raise NotImplementedError, "Subclasses must implement language_specific_null"
147
+ end
148
+ end
149
+
150
+ class KotlinCodeGenerator < BaseCodeGenerator
151
+ def language_specific_imports
152
+ "import android.graphics.Color\n" +
153
+ "import java.io.Serializable\n\n"
154
+ end
155
+
156
+ def class_declaration(class_name)
157
+ "\ndata class #{class_name}(\n"
158
+ end
159
+
160
+ def property_declaration(key, type, json)
161
+ if key == "asJson"
162
+ json_string = json.to_json.gsub('"', '\\"')
163
+ " val #{key}: String = \"#{json_string}\",\n"
164
+ else
165
+ " val #{key}: #{type},\n"
166
+ end
167
+ end
168
+
169
+ def constructor_parameter(key, type)
170
+ "val #{key}: #{type}"
171
+ end
172
+
173
+ def constructor_declaration(class_name, params)
174
+ "): Serializable {\n"
175
+ end
176
+
177
+ def instance_declaration(class_name, json)
178
+ " companion object {\n val instance = #{class_name}(\n#{json.map { |k, v| " #{k} = #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n }\n"
179
+ end
180
+
181
+ def json_methods(json, class_name)
182
+ ""
183
+ end
184
+
185
+ def class_closing
186
+ "}\n"
187
+ end
188
+
189
+ def value_for(value, class_prefix, indent)
190
+ case value
191
+ when String
192
+ if value.start_with?('#') && value.length == 7 # Assume it's a color
193
+ color_for(value)
194
+ else
195
+ "\"#{value}\"" # Use double quotes for Kotlin strings
196
+ end
197
+ when Integer, Float
198
+ value.to_s
199
+ when TrueClass, FalseClass
200
+ value.to_s
201
+ when Array
202
+ if value.empty?
203
+ "emptyList()" # Use Kotlin's emptyList() for empty arrays
204
+ elsif value.all? { |item| item.is_a?(Hash) }
205
+ array_items = value.map do |item|
206
+ item_values = item.map { |k, v| "#{k} = #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
207
+ "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )"
208
+ end.join(",\n#{indent} ")
209
+ "listOf(\n#{indent} #{array_items}\n#{indent})"
210
+ else
211
+ array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
212
+ "listOf(\n#{indent} #{array_items}\n#{indent})" # Use listOf for non-empty lists
213
+ end
214
+ when Hash
215
+ "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.instance"
216
+ else
217
+ language_specific_null
218
+ end
219
+ end
220
+
221
+ def value_type(value, class_prefix)
222
+ case value
223
+ when String then 'String'
224
+ when Integer then 'Int'
225
+ when Float then 'Float'
226
+ when TrueClass, FalseClass then 'Boolean'
227
+ when Array
228
+ if value.empty?
229
+ 'List<Any>'
230
+ elsif value.all? { |item| item.is_a?(String) }
231
+ 'List<String>'
232
+ elsif value.all? { |item| item.is_a?(Integer) }
233
+ 'List<Int>'
234
+ elsif value.all? { |item| item.is_a?(Float) }
235
+ 'List<Float>'
236
+ elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
237
+ 'List<Boolean>'
238
+ elsif value.all? { |item| item.is_a?(Hash) }
239
+ "List<#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item>"
240
+ else
241
+ 'List<Any>'
242
+ end
243
+ when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}"
244
+ else 'Any'
245
+ end
246
+ end
247
+
248
+ def color_for(value)
249
+ "Color.parseColor(\"#{value}\")"
250
+ end
251
+
252
+ def language_specific_null
253
+ 'null'
254
+ end
255
+ end
256
+
257
+ class SwiftCodeGenerator < BaseCodeGenerator
258
+ def language_specific_imports
259
+ "import UIKit\n\n"
260
+ end
261
+
262
+ def class_declaration(class_name)
263
+ "struct #{class_name}: Codable {\n"
264
+ end
265
+
266
+ def property_declaration(key, type, json)
267
+ if key == "asJson"
268
+ json_string = json.to_json.gsub('"', '\\"')
269
+ " let #{key}: String = \"#{json_string}\"\n"
270
+ else
271
+ " let #{key}: #{type}\n"
272
+ end
273
+ end
274
+
275
+ def constructor_parameter(key, type)
276
+ "#{key}: #{type}"
277
+ end
278
+
279
+ def constructor_declaration(class_name, params)
280
+ "\n init(\n#{params.map { |p| " #{p}"}.join(",\n")}) {\n#{params.map { |p| " self.#{p.split(':').first} = #{p.split(':').first}" }.join("\n")}\n }\n\n"
281
+ end
282
+
283
+ def instance_declaration(class_name, json)
284
+ " static let shared = #{class_name}(\n#{json.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n"
285
+ end
286
+
287
+ def json_methods(json, class_name)
288
+ " func toJson() -> String? {\n" +
289
+ " let encoder = JSONEncoder()\n" +
290
+ " if let jsonData = try? encoder.encode(self) {\n" +
291
+ " return String(data: jsonData, encoding: .utf8)\n" +
292
+ " }\n" +
293
+ " return nil\n" +
294
+ " }\n\n" +
295
+ " static func fromJson(_ json: String) -> #{class_name}? {\n" +
296
+ " let decoder = JSONDecoder()\n" +
297
+ " if let jsonData = json.data(using: .utf8),\n" +
298
+ " let result = try? decoder.decode(#{class_name}.self, from: jsonData) {\n" +
299
+ " return result\n" +
300
+ " }\n" +
301
+ " return nil\n" +
302
+ " }\n"
303
+ end
304
+
305
+ def class_closing
306
+ "}\n"
307
+ end
308
+
309
+ def value_for(value, class_prefix, indent)
310
+ case value
311
+ when String
312
+ if value.start_with?('#') && value.length == 7 # Assume it's a color
313
+ color_for(value)
314
+ else
315
+ "\"#{value}\""
316
+ end
317
+ when Integer, Float
318
+ value.to_s
319
+ when TrueClass, FalseClass
320
+ value.to_s
321
+ when Array
322
+ if value.empty?
323
+ "[]"
324
+ elsif value.all? { |item| item.is_a?(Hash) }
325
+ array_items = value.map do |item|
326
+ item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
327
+ "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )"
328
+ end.join(",\n#{indent} ")
329
+ "[\n#{indent} #{array_items}\n#{indent}]"
330
+ else
331
+ array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
332
+ "[\n#{indent} #{array_items}\n#{indent}]"
333
+ end
334
+ when Hash
335
+ "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.shared"
336
+ else
337
+ language_specific_null
338
+ end
339
+ end
340
+
341
+ def value_type(value, class_prefix)
342
+ case value
343
+ when String then 'String'
344
+ when Integer then 'Int'
345
+ when Float then 'Double'
346
+ when TrueClass, FalseClass then 'Bool'
347
+ when Array
348
+ if value.empty?
349
+ '[Any]'
350
+ elsif value.all? { |item| item.is_a?(String) }
351
+ '[String]'
352
+ elsif value.all? { |item| item.is_a?(Integer) }
353
+ '[Int]'
354
+ elsif value.all? { |item| item.is_a?(Float) }
355
+ '[Double]'
356
+ elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
357
+ '[Bool]'
358
+ elsif value.all? { |item| item.is_a?(Hash) }
359
+ "[#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item]"
360
+ else
361
+ '[Any]'
362
+ end
363
+ when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}"
364
+ else 'Any'
365
+ end
366
+ end
367
+
368
+ def color_for(value)
369
+ r, g, b = value[1..2].to_i(16), value[3..4].to_i(16), value[5..6].to_i(16)
370
+ "UIColor(red: #{r}/255.0, green: #{g}/255.0, blue: #{b}/255.0, alpha: 1.0)"
371
+ end
372
+
373
+ def language_specific_null
374
+ 'nil'
375
+ end
376
+ end
377
+
378
+ class DartCodeGenerator < BaseCodeGenerator
379
+ def language_specific_imports
380
+ "import 'package:flutter/material.dart';\n" +
381
+ "import 'dart:convert';\n\n"
382
+ end
383
+
384
+ def class_declaration(class_name)
385
+ "class #{class_name} {\n"
386
+ end
387
+
388
+ def property_declaration(key, type, json)
389
+ if key == "asJson"
390
+ json_string = json.to_json.gsub('"', '\\"')
391
+ " final String #{key} = \"#{json_string}\";\n"
392
+ else
393
+ " final #{type} #{key};\n"
394
+ end
395
+ end
396
+
397
+ def constructor_parameter(key, type)
398
+ "required this.#{key}"
399
+ end
400
+
401
+ def constructor_declaration(class_name, params)
402
+ params.empty? ? "\n const #{class_name}();\n\n" : "\n const #{class_name}({\n#{params.map { |p| " #{p}"}.join(",\n")}});\n\n"
403
+ end
404
+
405
+ def instance_declaration(class_name, json)
406
+ " static const #{class_name} instance = #{class_name}(\n#{json.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n );\n"
407
+ end
408
+
409
+ def json_methods(json, class_name)
410
+ " Map<String, dynamic> toJson() => {\n" +
411
+ " #{json.keys.map { |k| "'#{k}': #{k}" }.join(",\n ")}\n" +
412
+ " };\n\n" +
413
+ " factory #{class_name}.fromJson(Map<String, dynamic> json) => #{class_name}(\n" +
414
+ " #{json.keys.map { |k| "#{k}: json['#{k}']" }.join(",\n ")}\n" +
415
+ " );\n\n" +
416
+ " String toJsonString() => jsonEncode(toJson());\n\n" +
417
+ " factory #{class_name}.fromJsonString(String jsonString) =>\n" +
418
+ " #{class_name}.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);\n"
419
+ end
420
+
421
+ def class_closing
422
+ "}\n"
423
+ end
424
+
425
+ def value_for(value, class_prefix, indent)
426
+ case value
427
+ when String
428
+ if value.start_with?('#') && value.length == 7 # Assume it's a color
429
+ color_for(value)
430
+ else
431
+ "\"#{value}\""
432
+ end
433
+ when Integer, Float
434
+ value.to_s
435
+ when TrueClass, FalseClass
436
+ value.to_s
437
+ when Array
438
+ if value.empty?
439
+ "[]"
440
+ elsif value.all? { |item| item.is_a?(Hash) }
441
+ array_items = value.map do |item|
442
+ item_values = item.map { |k, v| "#{k}: #{value_for(v, k, indent + ' ')}" }.join(",\n#{indent} ")
443
+ "#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item(\n#{indent} #{item_values}\n#{indent} )"
444
+ end.join(",\n#{indent} ")
445
+ "[\n#{indent} #{array_items}\n#{indent}]"
446
+ else
447
+ array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(", ")
448
+ "[\n#{indent} #{array_items}\n#{indent}]"
449
+ end
450
+ when Hash
451
+ "#{class_prefix[0].upcase}#{class_prefix[1..-1]}.instance"
452
+ else
453
+ language_specific_null
454
+ end
455
+ end
456
+
457
+ def value_type(value, class_prefix)
458
+ case value
459
+ when String then 'String'
460
+ when Integer then 'int'
461
+ when Float then 'double'
462
+ when TrueClass, FalseClass then 'bool'
463
+ when Array
464
+ if value.empty?
465
+ 'List<dynamic>'
466
+ elsif value.all? { |item| item.is_a?(String) }
467
+ 'List<String>'
468
+ elsif value.all? { |item| item.is_a?(Integer) }
469
+ 'List<int>'
470
+ elsif value.all? { |item| item.is_a?(Float) }
471
+ 'List<double>'
472
+ elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
473
+ 'List<bool>'
474
+ elsif value.all? { |item| item.is_a?(Hash) }
475
+ "List<#{class_prefix[0].upcase}#{class_prefix[1..-1]}Item>"
476
+ else
477
+ 'List<dynamic>'
478
+ end
479
+ when Hash then "#{class_prefix[0].upcase}#{class_prefix[1..-1]}"
480
+ else 'dynamic'
481
+ end
482
+ end
483
+
484
+ def color_for(value)
485
+ "Color(0xFF#{value[1..-1]})"
486
+ end
487
+
488
+ def language_specific_null
489
+ 'null'
490
+ end
491
+ end
@@ -1,3 +1,3 @@
1
1
  module Solara
2
- VERSION = "0.2.4"
2
+ VERSION = "0.3.0"
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.2.4
4
+ version: 0.3.0
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-09-27 00:00:00.000000000 Z
11
+ date: 2024-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -236,12 +236,12 @@ files:
236
236
  - solara/lib/core/doctor/validator/template/ios_template_validation_config.yml
237
237
  - solara/lib/core/doctor/validator/template/template_validator.rb
238
238
  - solara/lib/core/doctor/validator/validation_strategy.rb
239
- - solara/lib/core/scripts/brand_config_generator.rb
240
239
  - solara/lib/core/scripts/brand_config_manager.rb
241
240
  - solara/lib/core/scripts/brand_exporter.rb
242
241
  - solara/lib/core/scripts/brand_importer.rb
243
242
  - solara/lib/core/scripts/brand_offboarder.rb
244
243
  - solara/lib/core/scripts/brand_resources_manager.rb
244
+ - solara/lib/core/scripts/code_generator.rb
245
245
  - solara/lib/core/scripts/directory_creator.rb
246
246
  - solara/lib/core/scripts/file_manager.rb
247
247
  - solara/lib/core/scripts/file_path.rb
@@ -356,7 +356,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
356
356
  - !ruby/object:Gem::Version
357
357
  version: '0'
358
358
  requirements: []
359
- rubygems_version: 3.5.18
359
+ rubygems_version: 3.5.21
360
360
  signing_key:
361
361
  specification_version: 4
362
362
  summary: Solara is a Ruby library that simplifies the management of white label apps
@@ -1,246 +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
- end
10
-
11
- def init(language)
12
- if Language.all.include?(language)
13
- # Do something with the valid language
14
- else
15
- raise ArgumentError, "Invalid language. Please use one of: #{Language.all.join(', ')}"
16
- end
17
- end
18
-
19
- class BrandConfigGenerator
20
- def initialize(config, output_dir, language, platform)
21
- @output_dir = output_dir
22
- @language = language
23
- @platform = platform
24
- @config = config
25
- end
26
-
27
- def generate
28
- content = generate_config_content
29
- FileManager.create_file_if_not_exist(@output_dir)
30
- File.write(@output_dir, content)
31
- Solara.logger.debug("Generated brand config #{@output_dir} for: #{@language}")
32
- end
33
-
34
- private
35
-
36
- def generate_config_content
37
- content = "// Generated by Solara\n"
38
- content += language_specific_imports
39
-
40
- classes = []
41
- generate_classes("BrandConfig", @config, classes)
42
-
43
- classes.reverse_each do |class_content|
44
- content += class_content
45
- content += "\n"
46
- end
47
-
48
- content
49
- end
50
-
51
- def language_specific_imports
52
- case @language
53
- when 'dart'
54
- "import 'package:flutter/material.dart';\n\n"
55
- when 'kotlin'
56
- "import android.graphics.Color\n\n"
57
- when 'swift'
58
- "import UIKit\n\n"
59
- end
60
- end
61
-
62
- def generate_classes(class_name, config, classes)
63
- content = class_declaration(class_name)
64
- constructor_params = []
65
-
66
- config.each do |key, value|
67
- type = value_type(value, key)
68
- content += property_declaration(key, type)
69
- constructor_params << constructor_parameter(key, type)
70
- end
71
-
72
- content += constructor_declaration(class_name, constructor_params)
73
- content += instance_declaration(class_name, config)
74
- content += class_closing
75
-
76
- classes << content
77
-
78
- config.each do |key, value|
79
- if value.is_a?(Hash)
80
- nested_class_name = "#{key.capitalize}"
81
- generate_classes(nested_class_name, value, classes)
82
- end
83
- end
84
- end
85
-
86
- def class_declaration(class_name)
87
- case @language
88
- when 'dart'
89
- "class #{class_name} {\n"
90
- when 'kotlin'
91
- "data class #{class_name}(\n"
92
- when 'swift'
93
- "struct #{class_name} {\n"
94
- end
95
- end
96
-
97
- def property_declaration(key, type)
98
- case @language
99
- when 'dart'
100
- " final #{type} #{key};\n"
101
- when 'kotlin'
102
- " val #{key}: #{type},\n"
103
- when 'swift'
104
- " let #{key}: #{type}\n"
105
- end
106
- end
107
-
108
- def constructor_parameter(key, type)
109
- case @language
110
- when 'dart'
111
- "required this.#{key}"
112
- when 'kotlin'
113
- "val #{key}: #{type}"
114
- when 'swift'
115
- "#{key}: #{type}"
116
- end
117
- end
118
-
119
- def constructor_declaration(class_name, params)
120
- case @language
121
- when 'dart'
122
- params.empty? ? "\n const #{class_name}();\n\n" : "\n const #{class_name}({#{params.join(', ')}});\n\n"
123
- when 'kotlin'
124
- ") {\n"
125
- when 'swift'
126
- "\n init(#{params.join(', ')}) {\n#{params.map { |p| " self.#{p.split(':').first} = #{p.split(':').first}" }.join("\n")}\n }\n\n"
127
- end
128
- end
129
-
130
- def instance_declaration(class_name, config)
131
- case @language
132
- when 'dart'
133
- " static const #{class_name} instance = #{class_name}(\n#{config.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n );\n"
134
- when 'kotlin'
135
- "companion object {\n val instance = #{class_name}(\n#{config.map { |k, v| " #{k} = #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n}\n"
136
- when 'swift'
137
- " static let shared = #{class_name}(\n#{config.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n"
138
- end
139
- end
140
-
141
- def class_closing
142
- @language == 'swift' ? "}\n" : "}\n"
143
- end
144
-
145
- def value_type(value, class_prefix)
146
- case value
147
- when String then language_specific_type('String')
148
- when Integer then language_specific_type('Int')
149
- when Float then language_specific_type('Double')
150
- when TrueClass, FalseClass then language_specific_type('Bool')
151
- when Array
152
- if value.all? { |item| item.is_a?(String) }
153
- language_specific_type('Array<String>')
154
- elsif value.all? { |item| item.is_a?(Integer) }
155
- language_specific_type('Array<Int>')
156
- elsif value.all? { |item| item.is_a?(Float) }
157
- language_specific_type('Array<Double>')
158
- elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
159
- language_specific_type('Array<Bool>')
160
- else
161
- language_specific_type('Array<Any>')
162
- end
163
- when Hash then "#{class_prefix.capitalize}"
164
- else
165
- language_specific_type('Any')
166
- end
167
- end
168
-
169
- def language_specific_type(type)
170
- case @language
171
- when 'dart'
172
- case type
173
- when 'String' then 'String'
174
- when 'Int' then 'int'
175
- when 'Double' then 'double'
176
- when 'Bool' then 'bool'
177
- when 'Array<String>' then 'List<String>'
178
- when 'Array<Int>' then 'List<int>'
179
- when 'Array<Double>' then 'List<double>'
180
- when 'Array<Bool>' then 'List<bool>'
181
- when 'Array<Any>' then 'List<dynamic>'
182
- when 'Any' then 'dynamic'
183
- else type
184
- end
185
- when 'kotlin'
186
- case type
187
- when 'Bool' then 'Boolean'
188
- when 'Array<String>' then 'List<String>'
189
- when 'Array<Int>' then 'List<Int>'
190
- when 'Array<Double>' then 'List<Double>'
191
- when 'Array<Bool>' then 'List<Boolean>'
192
- when 'Array<Any>' then 'List<Any>'
193
- when 'Any' then 'Any'
194
- else type
195
- end
196
- when 'swift'
197
- type
198
- end
199
- end
200
-
201
- def value_for(value, class_prefix, indent)
202
- case value
203
- when String
204
- if value.start_with?('#') && value.length == 7 # Assume it's a color
205
- color_for(value)
206
- else
207
- value.inspect
208
- end
209
- when Integer, Float
210
- value.to_s
211
- when TrueClass, FalseClass
212
- value.to_s
213
- when Array
214
- array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(', ')
215
- "[\n#{indent} #{array_items}\n#{indent}]"
216
- when Hash
217
- "#{class_prefix.capitalize}.instance"
218
- else
219
- language_specific_null
220
- end
221
- end
222
-
223
- def color_for(value)
224
- case @language
225
- when 'dart'
226
- "Color(0xFF#{value[1..-1]})"
227
- when 'kotlin'
228
- "Color.parseColor(\"#{value}\")"
229
- when 'swift'
230
- "UIColor(red: #{hex_to_rgb(value[:red])}, green: #{hex_to_rgb(value[:green])}, blue: #{hex_to_rgb(value[:blue])}, alpha: 1.0)"
231
- end
232
- end
233
-
234
- def hex_to_rgb(hex)
235
- hex.to_i(16) / 255.0
236
- end
237
-
238
- def language_specific_null
239
- case @language
240
- when 'dart', 'swift'
241
- 'null'
242
- when 'kotlin'
243
- 'null'
244
- end
245
- end
246
- end