solara 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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