gyro 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,265 @@
1
+ =begin
2
+ Copyright 2016 - Niji
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ =end
16
+
17
+ require File.expand_path('../../utils/log', File.dirname(__FILE__))
18
+ require File.expand_path('../../utils/string_xcdatamodel', File.dirname(__FILE__))
19
+ require File.expand_path('../../utils/file_utils', File.dirname(__FILE__))
20
+ require File.expand_path('../../xcdatamodel/parser/relationship', File.dirname(__FILE__))
21
+ require File.expand_path('converter', File.dirname(__FILE__))
22
+ require File.expand_path('enum_generator', File.dirname(__FILE__))
23
+ require File.expand_path('templates', File.dirname(__FILE__))
24
+ require File.expand_path('object_mapper_generator', File.dirname(__FILE__))
25
+
26
+ module Gyro
27
+ module Realm
28
+ module Swift
29
+
30
+ class Generator
31
+
32
+ # INCLUDES #############################################################
33
+
34
+ include Gyro::XCDataModel::Parser
35
+ include Converter
36
+ include EnumGenerator
37
+ include Templates
38
+ include ObjectMapperGenerator
39
+
40
+ # PUBLIC METHODS #######################################################
41
+
42
+ def initialize(path, xcdatamodel, json = false)
43
+ puts "\n"
44
+ Gyro::Log::title('Swift Realm')
45
+ xcdatamodel.entities.each do |_, entity|
46
+ unless entity.abstract?
47
+ Gyro::Log::success("Generating entity #{entity.name}...")
48
+ generate_class(path, entity, json)
49
+ generate_object_mapper_categories(path, xcdatamodel) if json
50
+ end
51
+ end
52
+ end
53
+
54
+ private ################################################################
55
+
56
+ def generate_class(path, entity, json)
57
+ class_file = String.new
58
+ entity.name = entity.name.delete_objc_prefix
59
+ class_file << generate_header(entity)
60
+ class_file << generate_attributes(entity.attributes, entity.relationships, json)
61
+ class_file << generate_inverse_properties(entity)
62
+ class_file << generate_primary_key(entity)
63
+ class_file << generate_indexed_properties(entity)
64
+ class_file << generate_ignored_properties(entity)
65
+ class_file << '}' + "\n"
66
+ Gyro.write_file_with_name(path, SWIFT_FILE_TEMPLATE%[entity.name], class_file)
67
+ generate_enums(path, entity.attributes)
68
+ end
69
+
70
+ def generate_header(entity)
71
+ class_file = String.new
72
+ class_file << GENERATED_MESSAGE + "\n\n"
73
+ class_file << IMPORT_REALM + "\n\n"
74
+ class_file << CLASS_COMMENT_TEMPLATE%[entity.comment] + "\n" unless entity.comment.empty?
75
+ class_file << CLASS_TEMPLATE%[entity.name] + "\n\n"
76
+ class_file << generate_constants(entity)
77
+ end
78
+
79
+ def generate_constants(entity)
80
+ attribute_constants = String.new
81
+ unless entity.attributes.empty?
82
+ attribute_constants << ' ' + ENUM_STRING_DEF_TEMPLATE%['Attributes'] + "\n"
83
+ entity.attributes.each do |_, attribute|
84
+ unless attribute.realm_ignored? or attribute.read_only?
85
+ attribute_constants << ' ' + ATTRIBUTE_COMMENT_TEMPLATE%[attribute.comment] + "\n" unless attribute.comment.empty?
86
+ attribute_constants << ' ' + ENUM_STRING_CASE_TEMPLATE%[attribute.name.capitalize_first_letter, attribute.name] + "\n"
87
+ end
88
+ end
89
+ attribute_constants << ' ' + '}'+ "\n\n"
90
+ end
91
+ relationship_constants = String.new
92
+ if not entity.relationships.empty? and not entity.has_only_inverse?
93
+ relationship_constants << ' ' + ENUM_STRING_DEF_TEMPLATE%['Relationships'] + "\n"
94
+ entity.relationships.each do |_, relationship|
95
+ relationship_constants << ' ' + ENUM_STRING_CASE_TEMPLATE%[relationship.name.capitalize_first_letter, relationship.name] + "\n" if not relationship.inverse?
96
+ end
97
+ relationship_constants << ' ' + '}'+ "\n\n"
98
+ end
99
+ attribute_constants + relationship_constants
100
+ end
101
+
102
+ def generate_attributes(attributes, relationships, json)
103
+ # "NORMAL" ATTRIBUTES
104
+ attributes_string = write_attributes(attributes, json)
105
+ # "RELATIONSHIP" ATTRIBUTES
106
+ relationships.each do |_, relationship|
107
+ unless relationship.inverse?
108
+ is_list = relationship.type == :to_many
109
+ name = relationship.name
110
+ type = relationship.inverse_type.delete_objc_prefix
111
+ if is_list
112
+ if json
113
+ attributes_string << ' ' + PROPERTY_LIST_VAR_TEMPLATE%[name, type] + "\n"
114
+ else
115
+ attributes_string << ' ' + PROPERTY_LIST_TEMPLATE%[name, type] + "\n"
116
+ end
117
+ else
118
+ attributes_string << ' ' + PROPERTY_OBJECT_TEMPLATE%[name, type] + "\n"
119
+ end
120
+ end
121
+ end
122
+ attributes_string + "\n"
123
+ end
124
+
125
+ def write_attributes(attributes, json)
126
+ attributes_string = String.new
127
+ attributes.each_with_index do |(_, attribute)|
128
+ unless attribute.read_only?
129
+ if attribute.enum?
130
+ attributes_string << write_enum_attribute(attribute, json)
131
+ else
132
+ if attribute.optional?
133
+ attributes_string << write_optional_attribute(attribute, json) + "\n"
134
+ else
135
+ default_value = convert_default(attribute.type, attribute.has_default? ? attribute.default : nil)
136
+ attributes_string << ' ' + PROPERTY_DEFAULT_TEMPLATE%[attribute.name, convert_type(attribute.type), default_value] + "\n"
137
+ end
138
+ end
139
+ end
140
+ end
141
+ attributes_string
142
+ end
143
+
144
+ def write_optional_attribute(attribute, json)
145
+ optional_string = String.new
146
+ type = convert_type(attribute.type)
147
+ if attribute.is_number? or attribute.is_bool?
148
+ if json
149
+ optional_string << ' ' + PROPERTY_OPTIONAL_NUMBER_VAR_TEMPLATE%[attribute.name, type]
150
+ else
151
+ optional_string << ' ' + PROPERTY_OPTIONAL_NUMBER_TEMPLATE%[attribute.name, type]
152
+ end
153
+ else
154
+ optional_string << ' ' + PROPERTY_OPTIONAL_NON_NUMBER_TEMPLATE%[attribute.name, type]
155
+ end
156
+ optional_string
157
+ end
158
+
159
+ def write_enum_attribute(attribute, json)
160
+ enum_string = String.new
161
+ if attribute.optional?
162
+ enum_string << ' ' + PROPERTY_OPTIONAL_ENUM_TEMPLATE%[attribute.name] + "\n"
163
+ else
164
+ enum_string << ' ' + PROPERTY_ENUM_TEMPLATE%[attribute.name] + "\n"
165
+ end
166
+ enum_type = attribute.enum_type.delete_objc_prefix
167
+ enum_name = attribute.name + 'Enum'
168
+
169
+ enum_string << ' ' + PROPERTY_OPTIONAL_COMPUTED_TEMPLATE%[enum_name, enum_type] + "\n"
170
+ enum_string << ' ' + 'get {' + "\n"
171
+ if attribute.optional?
172
+ enum_string << ' ' + "guard let #{attribute.name} = #{attribute.name},\n"
173
+ enum_string << ' ' + " let enumValue = #{enum_type}(rawValue: #{attribute.name})\n"
174
+ enum_string << ' ' + " else { return nil }" + "\n"
175
+ else
176
+ enum_string << ' ' + "guard let enumValue = #{enum_type}(rawValue: #{attribute.name}) else { return nil }" + "\n"
177
+ end
178
+ enum_string << ' ' + "return enumValue" + "\n"
179
+ enum_string << ' ' + '}' + "\n"
180
+
181
+ if attribute.optional?
182
+ enum_string << ' ' + "set { #{attribute.name} = newValue?.rawValue ?? nil }" + "\n"
183
+ else
184
+ enum_string << ' ' + "set { #{attribute.name} = newValue?.rawValue ?? \"\" }" + "\n"
185
+ end
186
+ enum_string << ' ' + '}' + "\n\n"
187
+ end
188
+
189
+ def generate_primary_key(entity)
190
+ primary_key = String.new
191
+ if entity.has_primary_key?
192
+ primary_key << ' ' + 'override static func primaryKey() -> String? {' + "\n"
193
+ primary_key << ' ' + "return #{entity.identity_attribute.add_quotes}" + "\n"
194
+ primary_key << ' ' + '}' + "\n\n"
195
+ end
196
+ primary_key
197
+ end
198
+
199
+ def generate_ignored_properties(entity)
200
+ ignored_properties = String.new
201
+ if entity.has_ignored?
202
+ ignored_properties << ' ' + "// Specify properties to ignore (Realm won't persist these)" + "\n"
203
+ ignored_properties << ' ' +'override static func ignoredProperties() -> [String] {' + "\n"
204
+ ignored_properties << ' ' + 'return ['
205
+ entity.attributes.each do |_, attribute|
206
+ ignored_properties << ARRAY_TEMPLATE%[attribute.name.add_quotes] if attribute.realm_ignored?
207
+ end
208
+ entity.relationships.each do |_, relationship|
209
+ ignored_properties << ARRAY_TEMPLATE%[relationship.name.add_quotes] if relationship.realm_ignored?
210
+ end
211
+ ignored_properties = ignored_properties[0..ignored_properties.length - 3] # delete last coma
212
+ ignored_properties << ']' + "\n"
213
+ ignored_properties << ' ' + '}' + "\n\n"
214
+ end
215
+ ignored_properties
216
+ end
217
+
218
+ def generate_inverse_properties(entity)
219
+ inverse_properties = []
220
+ entity.relationships.each do |_, relationship|
221
+ if relationship.inverse?
222
+ if relationship.type == :to_many
223
+ inverse_properties << PROPERTY_INVERSE_MANY_TEMPLATE%[
224
+ relationship.name.delete_inverse_suffix,
225
+ relationship.inverse_type.delete_objc_prefix,
226
+ relationship.inverse_name
227
+ ]
228
+ else
229
+ inverse_properties << PROPERTY_INVERSE_MANY_TEMPLATE%[
230
+ relationship.name.delete_inverse_suffix + 's', # 's' for plural form
231
+ relationship.inverse_type.delete_objc_prefix,
232
+ relationship.inverse_name
233
+ ]
234
+ inverse_properties << PROPERTY_INVERSE_ONE_TEMPLATE%[
235
+ relationship.name.delete_inverse_suffix,
236
+ relationship.inverse_type.delete_objc_prefix,
237
+ relationship.name.delete_inverse_suffix + 's' # 's' for plural form
238
+ ]
239
+ end
240
+ end
241
+ end
242
+ return '' if inverse_properties.empty?
243
+ inverse_properties.map { |value| " #{value}\n" }.join + "\n"
244
+ end
245
+
246
+ def generate_indexed_properties(entity)
247
+ indexed_properties = String.new
248
+ if entity.has_indexed_attributes?
249
+ indexed_properties << ' ' + '// Specify properties to index' + "\n"
250
+ indexed_properties << ' ' +'override static func indexedProperties() -> [String] {' + "\n"
251
+ indexed_properties << ' ' + 'return ['
252
+ entity.attributes.each do |_, attribute|
253
+ indexed_properties << ARRAY_TEMPLATE%[attribute.name.add_quotes] if attribute.indexed?
254
+ end
255
+ indexed_properties = indexed_properties[0..indexed_properties.length - 3] # delete last coma
256
+ indexed_properties << ']' + "\n"
257
+ indexed_properties << '}' + "\n\n"
258
+ end
259
+ indexed_properties
260
+ end
261
+
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,124 @@
1
+ =begin
2
+ Copyright 2016 - Niji
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ =end
16
+
17
+ require File.expand_path('templates', File.dirname(__FILE__))
18
+ require File.expand_path('../../utils/file_utils', File.dirname(__FILE__))
19
+ require File.expand_path('../../utils/string_xcdatamodel', File.dirname(__FILE__))
20
+
21
+ module Gyro
22
+ module Realm
23
+ module Swift
24
+ module ObjectMapperGenerator
25
+
26
+ # INCLUDES #############################################################
27
+
28
+ include Templates
29
+
30
+ # PUBLIC METHODS #######################################################
31
+
32
+ def generate_object_mapper_categories(path, xcdatamodel, framework = false)
33
+ json_path = File.join(path, 'ObjectMapper')
34
+ Dir.mkdir(json_path) unless Dir.exists?(json_path)
35
+ xcdatamodel.entities.each do |_, entity|
36
+ generate_json_category_file(json_path, entity, framework)
37
+ end
38
+ end
39
+
40
+ # PRIVATE METHODS #######################################################
41
+
42
+ def generate_json_category_file(path, entity, framework)
43
+ if !entity.attributes.empty? || !entity.relationships.empty?
44
+ source_file = generate_source_extension_file(entity)
45
+ file_name = EXTENSION_NAME%[entity.name]
46
+ Gyro.write_file_with_name(path, SOURCE_TEMPLATE%[file_name], source_file) unless source_file.empty?
47
+ end
48
+ end
49
+
50
+ def generate_source_extension_file(entity)
51
+ source_file = String.new
52
+ source_file << GENERATED_MESSAGE + "\n\n"
53
+ source_file << IMPORT_OBJECT_MAPPER + "\n\n"
54
+ source_file << EXTENSION_TEMPLATE%[entity.name, "Mappable"] + "\n\n"
55
+ source_file << generate_init
56
+ source_file << generate_mapping(entity)
57
+ source_file << "}\n"
58
+ source_file
59
+ end
60
+
61
+ def generate_mapping(entity)
62
+ source_file = String.new
63
+ source_file << " // MARK: Mappable\n\n"
64
+ source_file << " func mapping(map: Map) {\n"
65
+ source_file << generate_mapper_attributes(entity) if !entity.attributes.empty?
66
+ source_file << generate_mapper_relationships(entity) if !entity.relationships.empty?
67
+ source_file << " }\n"
68
+ source_file
69
+ end
70
+
71
+ def generate_init()
72
+ source_file = String.new
73
+ source_file << " // MARK: Initializers\n\n"
74
+ source_file << " convenience init?(_ map: Map) {\n"
75
+ source_file << " self.init()\n"
76
+ source_file << " }\n\n"
77
+ source_file
78
+ end
79
+
80
+ def generate_mapper_attributes(entity)
81
+ attributes = String.new
82
+ attributes << "\n // MARK: Attributes\n"
83
+ entity.attributes.each do |_, attribute|
84
+ attrKey = attribute.json_key_path.empty? ? attribute.name : attribute.json_key_path
85
+ case
86
+ when attribute.type == :date
87
+ transformer = attribute.transformer.empty? ? "ISO8601DateTransform" : attribute.transformer
88
+ attributes << " self." + attribute.name + " <- (map[" + attrKey.add_quotes + "], " + transformer + "())\n"
89
+ when attribute.type == :integer_16 && attribute.optional && attribute.enum_type == ""
90
+ attributes << " self." + attribute.name + " <- (map[" + attrKey.add_quotes + "], RealmOptionalInt16Transform())\n"
91
+ when attribute.type == :integer_32 && attribute.optional
92
+ attributes << " self." + attribute.name + " <- (map[" + attrKey.add_quotes + "], RealmOptionalInt32Transform())\n"
93
+ when attribute.type == :integer_64 && attribute.optional
94
+ attributes << " self." + attribute.name + " <- (map[" + attrKey.add_quotes + "], RealmOptionalInt64Transform())\n"
95
+ when attribute.type == :float && attribute.optional
96
+ attributes << " self." + attribute.name + " <- (map[" + attrKey.add_quotes + "], RealmOptionalFloatTransform())\n"
97
+ when attribute.type == :double && attribute.optional
98
+ attributes << " self." + attribute.name + " <- (map[" + attrKey.add_quotes + "], RealmOptionalDoubleTransform())\n"
99
+ when
100
+ attributes << " self." + attribute.name + " <- map[" + attrKey.add_quotes + "]\n"
101
+ end
102
+ end
103
+ attributes
104
+ end
105
+
106
+ def generate_mapper_relationships(entity)
107
+ relationships = String.new
108
+ relationships << "\n // MARK: Relationships\n"
109
+ entity.relationships.each do |_, relationship|
110
+ next if relationship.inverse?
111
+ relationKey = relationship.json_key_path.empty? ? relationship.name : relationship.json_key_path
112
+ if relationship.type == :to_many
113
+ relationships << " self." + relationship.name + " <- (map[" + relationKey.add_quotes + "], ListTransform<" + relationship.inverse_type + ">())\n"
114
+ else
115
+ relationships << " self." + relationship.name + " <- map[" + relationKey.add_quotes + "]\n"
116
+ end
117
+ end
118
+ relationships
119
+ end
120
+
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,64 @@
1
+ =begin
2
+ Copyright 2016 - Niji
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ =end
16
+
17
+ module Gyro
18
+ module Realm
19
+ module Swift
20
+ module Templates
21
+
22
+ # COMMONS
23
+ GENERATED_MESSAGE = '/* DO NOT EDIT | Generated by gyro */'
24
+
25
+ # IMPORTS
26
+ IMPORT_REALM = 'import RealmSwift'
27
+ SWIFT_FILE_TEMPLATE = '%s.swift'
28
+ IMPORT_OBJECT_MAPPER = 'import ObjectMapper'
29
+
30
+ # ENUM
31
+ ENUM_STRING_DEF_TEMPLATE = 'enum %s: String {'
32
+ ENUM_STRING_CASE_TEMPLATE = 'case %s = "%s"'
33
+
34
+ # CLASS
35
+ ARRAY_TEMPLATE ='%s, '
36
+ CLASS_TEMPLATE = 'final class %s: Object {'
37
+ PROPERTY_DEFAULT_TEMPLATE = 'dynamic var %s: %s = %s'
38
+ PROPERTY_OBJECT_TEMPLATE = 'dynamic var %s: %s?'
39
+ PROPERTY_LIST_TEMPLATE = 'let %s = List<%s>()'
40
+ PROPERTY_LIST_VAR_TEMPLATE = 'var %s = List<%s>()'
41
+ PROPERTY_PRIVATE_ENUM_TEMPLATE = 'private dynamic var %s: String?'
42
+ PROPERTY_ENUM_TEMPLATE = 'dynamic var %s: String = ""'
43
+ PROPERTY_OPTIONAL_ENUM_TEMPLATE = 'dynamic var %s: String? = nil'
44
+ PROPERTY_COMPUTED_TEMPLATE = 'var %s: %s {'
45
+ PROPERTY_OPTIONAL_COMPUTED_TEMPLATE = 'var %s: %s? {'
46
+ PROPERTY_INVERSE_ONE_TEMPLATE = 'var %s: %s? { return %s.first }'
47
+ PROPERTY_INVERSE_MANY_TEMPLATE = 'let %s = LinkingObjects(fromType: %s.self, property: "%s")'
48
+ PROPERTY_OPTIONAL_NON_NUMBER_TEMPLATE = 'dynamic var %s: %s? = nil'
49
+ PROPERTY_OPTIONAL_NUMBER_TEMPLATE = 'let %s = RealmOptional<%s>()'
50
+ PROPERTY_OPTIONAL_NUMBER_VAR_TEMPLATE = 'var %s = RealmOptional<%s>()'
51
+
52
+ # EXTENSION
53
+ EXTENSION_TEMPLATE = 'extension %s: %s {'
54
+ EXTENSION_NAME = '%sMapper'
55
+ SOURCE_TEMPLATE = '%s.swift'
56
+
57
+ # COMMENTS
58
+ CLASS_COMMENT_TEMPLATE = "/**\n * %s\n */"
59
+ ATTRIBUTE_COMMENT_TEMPLATE = '/** %s */'
60
+
61
+ end
62
+ end
63
+ end
64
+ end