immutabler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 082cae15547802583d920c85a676e4c5cf93302d
4
+ data.tar.gz: 0c607875a8e602d62a7d68b747279f86175f48fd
5
+ SHA512:
6
+ metadata.gz: a98dccfbf117a51d37156630bb5346bdf79d302a2b7fbc43d3d0991ef8aa52709f0aea5470ecc41753212d58b7a07daac70c8fe034366033491eb77d04a65d93
7
+ data.tar.gz: 2da0f6044560cc729c7466b0767d43e2cfbdee32fcdcbbc2f7d3cb725f0f6126d0b5c48483d657bebc240e811f26dc143bcdfb4e7673731b1adcf3b59320f022
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /.idea/
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.3
4
+ before_install: gem install bundler -v 1.10.6
@@ -0,0 +1,194 @@
1
+ Getting Started
2
+ ====================
3
+
4
+ # Syntax
5
+
6
+ ##Groups
7
+
8
+ Groups are on the root level
9
+
10
+ ```ruby
11
+ Immutabler.group :GroupName do
12
+ output_dir('./project/output')
13
+ prefix('GroupPrefix')
14
+ base_model('NSObject')
15
+ end
16
+ ```
17
+
18
+ Inside of immutabler you can describe enums, fields and mappings.
19
+
20
+ ## Models
21
+
22
+ Model is describing in `model` block:
23
+
24
+ ```ruby
25
+ model :ModelName do
26
+
27
+ end
28
+ ```
29
+
30
+ You can specify base class for model using `base` option:
31
+
32
+ ```ruby
33
+ model :User, base: MyModel do
34
+
35
+ end
36
+ ```
37
+
38
+ ## Enums
39
+
40
+ Enum is describing in `enum` block:
41
+
42
+ ```ruby
43
+ enum :EnumName do
44
+
45
+ end
46
+ ```
47
+
48
+ Generated enum will have group prefix.
49
+
50
+ Generated enum members will have a group prefix and the enum name.
51
+
52
+ For example: `GroupPrefixEnumNameMember1`
53
+
54
+ Example:
55
+ ```ruby
56
+ enum :EnumName do
57
+ attr :Member1
58
+ attr :Member2
59
+ end
60
+ ```
61
+
62
+ ## Fields
63
+
64
+ Fields are defing in `fields` block:
65
+
66
+ ```ruby
67
+ fields do
68
+
69
+ end
70
+ ```
71
+
72
+ Each field is defining by
73
+
74
+ ```ruby
75
+ prop :fieldName, :fieldType
76
+ ```
77
+
78
+ There are predefined types:
79
+ * int
80
+ * float
81
+ * bool
82
+ * string
83
+ * array
84
+ * dict
85
+ * id
86
+
87
+ When you use custom type and it will be transformed into the same.
88
+
89
+ You can pass additional parameters for prop:
90
+
91
+ + prefix: prefix used in Objective-C code (e.g. `*` for referential types).
92
+ + ref: reference type in Objective-C code (e.g. `weak`)
93
+
94
+ Example:
95
+ ```ruby
96
+ fields do
97
+ prop :modelId, :int
98
+ prop :address, :string
99
+ prop :phone, :string
100
+ prop :walkIns, :array
101
+ prop :workingDays, :dict
102
+ prop :state, :TEState, prefix: '', ref: :weak
103
+ end
104
+ ```
105
+
106
+ ## Mappings
107
+
108
+ All mappings are describing in `mapping` block
109
+ ```ruby
110
+ mapping do
111
+
112
+ end
113
+ ```
114
+
115
+ ### Simple mapping
116
+
117
+ Simple mappings are describing wih `map`:
118
+
119
+ ```ruby
120
+ map :json_field_name, :classFieldName
121
+ ```
122
+
123
+ You can specify custom transformer class using `with` option:
124
+
125
+ ```ruby
126
+ map :phone, :phoneNumber, with: :CustomPhoneTransformer
127
+ ```
128
+
129
+ ### Array mapping
130
+
131
+ You can describe mapping array of records using `array`:
132
+
133
+ ```ruby
134
+ array :json_field_name, :classFieldName, :ClassName
135
+ ```
136
+
137
+ ### Dict mapping
138
+
139
+ You can describe mapping json field to custom value (e.g. into enum) with `dict`:
140
+
141
+ ```ruby
142
+ dict :json_state, :modelState do
143
+ map :active, :TEServiceStateActive
144
+ map :archived, :TEServiceStateArchived
145
+ end
146
+ ```
147
+
148
+ # Full example
149
+ ```ruby
150
+ require 'immutabler'
151
+
152
+ gen_path = './MasterApp/gen'
153
+
154
+ Immutabler.group :TESalonModels do
155
+
156
+ output_dir(gen_path)
157
+ prefix('TE')
158
+ base_model('MTLModel<MTLJSONSerializing>') # Defaut base model for group
159
+
160
+ enum :ServiceState do
161
+ attr :Archived
162
+ attr :Active
163
+ end
164
+
165
+ model :Salon, base: 'CustomModel' do
166
+ fields do
167
+ prop :modelId, :int
168
+ prop :address, :string
169
+ prop :phone, :string
170
+ prop :walkIns, :array
171
+ prop :workingDays, :dict
172
+ prop :state, :TEState, ref: :weak, prefix: '*'
173
+ end
174
+
175
+ mapping do
176
+ map :id, :modelId
177
+ map :phone, :phone, with: :CustomPhoneTransformer
178
+ array :walk_ins, :walkIns, :TEWalkIn
179
+ dict :state, :state do
180
+ map :active, :TEServiceStateActive
181
+ map :archived, :TEServiceStateArchived
182
+ end
183
+ end
184
+ end
185
+ ```
186
+
187
+ # How to run
188
+
189
+ Run in command line from project root directory
190
+ ```shell
191
+ ruby MasterApp/models.rb
192
+ ```
193
+
194
+ Output files will be generated in the specified `output_dir`
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in immutabler.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Techery http://techery.io
4
+ Serhij Korochanskyj <serge.k@techery.io>
5
+ Sergey Zenchenko <serge.z@techery.io>
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ Immutabler
2
+ =========
3
+
4
+ Generator of Objective-C immutable models
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'immutabler'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install immutabler
21
+
22
+ ## Getting started
23
+
24
+ [Getting started is here](https://github.com/techery/immutabler/blob/master/GETTING_STARTED.md)
25
+
26
+ ## Development
27
+
28
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
29
+
30
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
31
+
32
+ ## Contributing
33
+
34
+ Bug reports and pull requests are welcome on GitHub at https://github.com/techery/immutabler. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
35
+
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
40
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'immutabler'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'immutabler/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'immutabler'
8
+ spec.version = Immutabler::VERSION
9
+ spec.authors = ['Serhij Korochanskyj', 'Sergey Zenchenko']
10
+ spec.email = ['serge.k@techery.io', 'serge.z@techery.io']
11
+
12
+ spec.summary = 'Generator of Objective-C immutable models'
13
+ spec.description = 'Generator of Objective-C immutable models'
14
+ spec.homepage = 'https://github.com/techery/immutabler'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_runtime_dependency 'handlebars', '~> 0.7'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.10'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec'
27
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'type_mapper'
2
+ require_relative 'template/builder'
3
+ require_relative 'dsl/group'
4
+
5
+ module Immutabler
6
+ def self.group(group_name, &block)
7
+ group = DSL::Group.new(group_name)
8
+ group.instance_eval(&block)
9
+
10
+ builder = Template::Builder.new(group)
11
+ builder.build
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Immutabler
2
+ module DSL
3
+ class DictMappingsBuilder
4
+ def initialize(dict_mappings, &block)
5
+ @dict_mappings = dict_mappings
6
+ instance_eval(&block)
7
+ end
8
+
9
+ def map(origin_name, destination_name)
10
+ dict_mapping =
11
+ {
12
+ origin_name: origin_name.to_s,
13
+ destination_name: destination_name.to_s
14
+ }
15
+ @dict_mappings << dict_mapping
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module Immutabler
2
+ module DSL
3
+ class Enum
4
+ attr_accessor :name, :attributes
5
+
6
+ def initialize(name, attributes)
7
+ @name = name
8
+ @attributes = attributes
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module Immutabler
2
+ module DSL
3
+ class EnumAttribute
4
+ attr_accessor :name
5
+
6
+ def initialize(name)
7
+ @name = name
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'enum_attribute'
2
+
3
+ module Immutabler
4
+ module DSL
5
+ class EnumAttributesBuilder
6
+ def initialize(attributes, prefix, &block)
7
+ @attributes = attributes
8
+ @prefix = prefix
9
+ instance_eval(&block)
10
+ end
11
+
12
+ def attr(name)
13
+ @attributes << EnumAttribute.new("#{@prefix}#{name}")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,65 @@
1
+ require_relative 'enum'
2
+ require_relative 'model'
3
+ require_relative 'model_attributes_builder'
4
+ require_relative 'enum_attributes_builder'
5
+
6
+ module Immutabler
7
+ module DSL
8
+ class Group
9
+ attr_accessor :name, :models, :enums, :prefix, :links, :output_directory
10
+
11
+ def initialize(name)
12
+ @prefix = ''
13
+ @base_model = 'NSObject'
14
+ @name = name
15
+ @models = []
16
+ @enums = []
17
+ @links = []
18
+ end
19
+
20
+ def prefix(prefix)
21
+ self.prefix = prefix
22
+ end
23
+
24
+ def link_to(model_group_name)
25
+ links << model_group_name
26
+ end
27
+
28
+ def output_dir(dir)
29
+ self.output_directory = dir
30
+ end
31
+
32
+ def base_model(name)
33
+ @base_model = name
34
+ end
35
+
36
+ def enum(name, &block)
37
+ attributes = []
38
+ prefix = "#{@prefix}#{name}"
39
+ EnumAttributesBuilder.new(attributes, prefix, &block)
40
+ @enums << Enum.new(prefix, attributes)
41
+ end
42
+
43
+ def model(name, options = {}, &block)
44
+ prefix = @prefix + name.to_s
45
+ base = options.fetch(:base, @base_model).to_s
46
+ props = []
47
+ mappings = []
48
+ ModelAttributesBuilder.new(props, mappings, &block)
49
+
50
+ model = Model.new(prefix, base, props, mappings)
51
+
52
+ models << model
53
+ end
54
+
55
+ def build
56
+ {
57
+ name: name,
58
+ models: models,
59
+ links: links,
60
+ enums: enums
61
+ }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ module Immutabler
2
+ module DSL
3
+ class Mapping
4
+ attr_accessor :origin_name, :destination_name, :type, :dict_mappings, :custom_transformer
5
+
6
+ def initialize(origin_name, destination_name, options = {})
7
+ @origin_name = origin_name
8
+ @destination_name = destination_name
9
+ @type = options[:type]
10
+ @is_array = options.fetch(:array, false)
11
+ @is_dict = options.fetch(:dict, false)
12
+ @custom_transformer = options[:with]
13
+ @dict_mappings = []
14
+ end
15
+
16
+ def array?
17
+ @is_array
18
+ end
19
+
20
+ def dict?
21
+ @is_dict
22
+ end
23
+
24
+ def custom?
25
+ !custom_transformer.nil?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'mapping'
2
+ require_relative 'dict_mappings_builder'
3
+
4
+ module Immutabler
5
+ module DSL
6
+ class MappingsBuilder
7
+ def initialize(mappings, &block)
8
+ @mappings = mappings
9
+ instance_eval(&block)
10
+ end
11
+
12
+ def map(origin_name, destination_name, options = {})
13
+ @mappings << Mapping.new(origin_name.to_s, destination_name.to_s, options)
14
+ end
15
+
16
+ def array(origin_name, destination_name, type)
17
+ options = {
18
+ type: type.to_s,
19
+ array: true
20
+ }
21
+ @mappings << Mapping.new(origin_name.to_s, destination_name.to_s, options)
22
+ end
23
+
24
+ def dict(origin_name, destination_name, &block)
25
+ mapping = Mapping.new(origin_name.to_s, destination_name.to_s, dict: true)
26
+ DictMappingsBuilder.new(mapping.dict_mappings, &block)
27
+ @mappings << mapping
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module Immutabler
2
+ module DSL
3
+ class Model
4
+ attr_accessor :name, :base, :props, :mappings
5
+
6
+ def initialize(name, base, props, mappings)
7
+ @name = name
8
+ @base = base
9
+ @props = props
10
+ @mappings = mappings
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'props_builder'
2
+ require_relative 'mappings_builder'
3
+
4
+ module Immutabler
5
+ module DSL
6
+ class ModelAttributesBuilder
7
+ def initialize(props, mappings, &block)
8
+ @props = props
9
+ @mappings = mappings
10
+ instance_eval(&block)
11
+ end
12
+
13
+ def fields(&block)
14
+ PropsBuilder.new(@props, &block)
15
+ end
16
+
17
+ def mapping(&block)
18
+ MappingsBuilder.new(@mappings, &block)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Immutabler
2
+ module DSL
3
+ class Prop
4
+ attr_accessor :name, :type, :options
5
+
6
+ def initialize(name, type, options)
7
+ @name = name
8
+ @type = type
9
+ @options = options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'prop'
2
+
3
+ module Immutabler
4
+ module DSL
5
+ class PropsBuilder
6
+ def initialize(props, &block)
7
+ @props = props
8
+ instance_eval(&block)
9
+ end
10
+
11
+ def prop(name, type, options={})
12
+ prop_options = {}
13
+ prop_options[:is_ref] = !!options[:ref] if options.key?(:ref)
14
+ prop_options[:ref_type] = options[:ref] if options.key?(:ref)
15
+ prop_options[:name_prefix] = options[:prefix] if options.key?(:prefix)
16
+ @props << Prop.new(name.to_s, type.to_s, prop_options)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'template'
2
+
3
+ module Immutabler
4
+ module Template
5
+ class BodyTemplate < BaseTemplate
6
+ attr_accessor :name, :models
7
+
8
+ def initialize(name, models)
9
+ self.template_file = File.expand_path("#{__dir__}/../../../templates/body_template.hbs")
10
+ self.name = name
11
+ self.models = models
12
+
13
+ helper(:dict) do |context, arg, block|
14
+ body = "@{\n"
15
+
16
+ if arg
17
+ body << arg.map{|m|
18
+ "@\"#{m.destination_name}\" : @\"#{m.origin_name}\""
19
+ }.join(",\n")
20
+ end
21
+
22
+ body << "\n};"
23
+ end
24
+
25
+ helper(:enum_mapping_dict) do |context, arg, block|
26
+ body = "@{\n"
27
+
28
+ if arg
29
+ body << arg.map{|m|
30
+ "@\"#{m.origin_name}\" : @(#{m.destination_name})"
31
+ }.join(",\n")
32
+ end
33
+
34
+ body << "\n};"
35
+ end
36
+
37
+ end
38
+
39
+ def render
40
+ template.call(models: models, name: name)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,99 @@
1
+ require 'fileutils'
2
+
3
+ require_relative '../type_mapper'
4
+ require_relative 'header_template'
5
+ require_relative 'body_template'
6
+
7
+ module Immutabler
8
+ module Template
9
+ class Builder
10
+ attr_accessor :group
11
+
12
+ def initialize(group)
13
+ @group = group
14
+ end
15
+
16
+ def build
17
+ defs = group.build
18
+
19
+ group_name = defs[:name]
20
+ models = defs[:models].map do |model|
21
+ {
22
+ name: model.name,
23
+ base_class: model.base,
24
+ props: build_props(model.props),
25
+ any_mappings: model.mappings.any?,
26
+ array_mappings: model.mappings.select(&:array?),
27
+ dict_mappings: build_dict_mappings(model.mappings),
28
+ custom_mappers: build_custom_mappers(model.mappings),
29
+ mappings: build_mappings(model.mappings)
30
+ }
31
+ end
32
+
33
+ links = defs[:links]
34
+ enums = defs[:enums]
35
+
36
+ FileUtils.mkdir_p(group.output_directory)
37
+
38
+ build_head(group_name, models, links, enums)
39
+ build_body(group_name, models)
40
+ end
41
+
42
+ private
43
+
44
+ def build_props(props)
45
+ props.map do |prop|
46
+ Immutabler::TypeMapper.map(prop.type, prop.options).merge(name: prop.name)
47
+ end
48
+ end
49
+
50
+ def build_dict_mappings(mappings)
51
+ mappings.select(&:dict?).map do |mapping|
52
+ attributes = mapping.dict_mappings.map do |dict_mapping|
53
+ {
54
+ origin_name: dict_mapping[:origin_name],
55
+ destination_name: dict_mapping[:destination_name]
56
+ }
57
+ end
58
+
59
+ {
60
+ name: mapping.destination_name,
61
+ attributes: attributes
62
+ }
63
+ end
64
+ end
65
+
66
+ def build_mappings(mappings)
67
+ mappings.map do |mapping|
68
+ {
69
+ origin_name: mapping.origin_name,
70
+ destination_name: mapping.destination_name
71
+ }
72
+ end
73
+ end
74
+
75
+ def build_custom_mappers(mappings)
76
+ mappings.select(&:custom?).map do |mapping|
77
+ {
78
+ origin_name: mapping.origin_name,
79
+ transformer_name: mapping.custom_transformer
80
+ }
81
+ end
82
+ end
83
+
84
+ def build_body(group_name, models)
85
+ body_template = Immutabler::Template::BodyTemplate.new(group_name, models)
86
+ body_content = body_template.render
87
+ body_path = "#{group.output_directory}/#{group_name}.m"
88
+ File.write(body_path, body_content)
89
+ end
90
+
91
+ def build_head(group_name, models, links, enums)
92
+ header_template = Immutabler::Template::HeaderTemplate.new(models, links, enums)
93
+ header_content = header_template.render
94
+ header_path = "#{group.output_directory}/#{group_name}.h"
95
+ File.write(header_path, header_content)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'template'
2
+
3
+ module Immutabler
4
+ module Template
5
+ class HeaderTemplate < BaseTemplate
6
+ attr_accessor :models, :links, :enums
7
+
8
+ def initialize(models, links, enums)
9
+ self.template_file = File.expand_path("#{__dir__}/../../../templates/header_template.hbs")
10
+ self.links = links
11
+ self.models = models
12
+ self.enums = build_enums(enums)
13
+
14
+ helper(:interface) do |context, arg, block|
15
+ [
16
+ "@interface #{arg[:name]} : #{arg[:base_class]}",
17
+ '',
18
+ "#{block.fn(context)}",
19
+ '@end',
20
+ ''
21
+ ].join("\n")
22
+ end
23
+
24
+ helper(:declare_props) do |context, arg, block|
25
+ arg.map do |prop|
26
+ options = block[:hash]
27
+ access_type = options[:readOnly] ? 'readonly' : 'readwrite'
28
+ prop_arguments = "@property(nonatomic, #{prop[:ref_type]}, #{access_type})"
29
+ prop_field = "#{prop[:type]} #{prop[:name_prefix]}#{prop[:name]}"
30
+
31
+ "#{prop_arguments} #{prop_field};"
32
+ end.join("\n")
33
+ end
34
+
35
+ helper(:enum) do |context, arg, block|
36
+ [
37
+ 'typedef enum {',
38
+ arg[:attributes].map(&:name).join(",\n"),
39
+ "} #{arg[:name]};",
40
+ ].join("\n")
41
+ end
42
+ end
43
+
44
+ def build_enums(enums)
45
+ enums.map do |enum|
46
+ attributes = enum.attributes.map { |attr| { name: attr.name } }
47
+
48
+ {
49
+ name: enum.name,
50
+ attributes: attributes
51
+ }
52
+ end
53
+ end
54
+
55
+ def render
56
+ template.call(models: models, links: links, enums: enums)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ require 'handlebars'
2
+
3
+ class String
4
+ def unindent
5
+ gsub(/^#{match(/^\s+/)}/, '')
6
+ end
7
+ end
8
+
9
+ module Immutabler
10
+ module Template
11
+ class BaseTemplate
12
+ attr_accessor :template_file
13
+
14
+ def raw_template
15
+ @raw_template ||= File.read(template_file)
16
+ end
17
+
18
+ def handlebars
19
+ @handlebars ||= Handlebars::Context.new
20
+ end
21
+
22
+ def template
23
+ @template ||= handlebars.compile(raw_template, {noEscape: true})
24
+ end
25
+
26
+ def helper(name, &helper_body)
27
+ handlebars.register_helper(name) do |context, arg, block|
28
+ helper_body.call(context, arg, block).unindent
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,63 @@
1
+ module Immutabler
2
+ class TypeMapper
3
+ TYPE_MAPPING = {
4
+ 'int' => {
5
+ type: 'NSInteger',
6
+ is_ref: false,
7
+ ref_type: 'assign',
8
+ name_prefix: ''
9
+ },
10
+ 'float' => {
11
+ type: 'CGFloat',
12
+ is_ref: false,
13
+ ref_type: 'assign',
14
+ name_prefix: ''
15
+ },
16
+ 'bool' => {
17
+ type: 'BOOL',
18
+ is_ref: false,
19
+ ref_type: 'assign',
20
+ name_prefix: ''
21
+ },
22
+ 'array' => {
23
+ type: 'NSArray',
24
+ is_ref: true,
25
+ ref_type: 'strong',
26
+ name_prefix: '*'
27
+ },
28
+ 'string' => {
29
+ type: 'NSString',
30
+ is_ref: true,
31
+ ref_type: 'strong',
32
+ name_prefix: '*'
33
+ },
34
+ 'dict' => {
35
+ type: 'NSDictionary',
36
+ is_ref: true,
37
+ ref_type: 'strong',
38
+ name_prefix: '*',
39
+ },
40
+ 'id' => {
41
+ type: 'id',
42
+ is_ref: true,
43
+ ref_type: 'strong',
44
+ name_prefix: ''
45
+ }
46
+ }
47
+
48
+ def self.map(type, options)
49
+ TYPE_MAPPING.fetch(type, default_mapping(type)).merge(options)
50
+ end
51
+
52
+ private
53
+
54
+ def self.default_mapping(type)
55
+ {
56
+ type: type,
57
+ is_ref: true,
58
+ ref_type: 'strong',
59
+ name_prefix: '*'
60
+ }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module Immutabler
2
+ VERSION = '0.1.0'
3
+ end
data/lib/immutabler.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'immutabler/version'
2
+ require 'immutabler/core'
@@ -0,0 +1,187 @@
1
+ #import "{{name}}.h"
2
+
3
+ {{#models}}
4
+ @class {{name}};
5
+ {{/models}}
6
+
7
+ {{#models}}
8
+
9
+ @implementation {{name}} {
10
+ int __modelVersion;
11
+ }
12
+
13
+ {{#any_mappings}}
14
+ + (NSDictionary *)JSONKeyPathsByPropertyKey {
15
+ return {{#dict mappings}}{{/dict}}
16
+ }
17
+ {{/any_mappings}}
18
+
19
+ {{#custom_mappers}}
20
+ + (NSValueTransformer *){{origin_name}}JSONTransformer {
21
+ return [ [{{transformer_name}} alloc] init];
22
+ }
23
+ {{/custom_mappers}}
24
+
25
+ {{#array_mappings}}
26
+ + (NSValueTransformer *){{destination_name}}JSONTransformer {
27
+ return [MTLJSONAdapter arrayTransformerWithModelClass:[{{type}} class]];
28
+ }
29
+ {{/array_mappings}}
30
+
31
+ {{#dict_mappings}}
32
+ + (NSValueTransformer *){{name}}JSONTransformer {
33
+ NSDictionary *dict = {{#enum_mapping_dict attributes}}{{/enum_mapping_dict}}
34
+ return [MTLValueTransformer transformerUsingForwardBlock:^NSNumber *(NSString *string, BOOL *success, NSError *__autoreleasing *error) {
35
+ return dict[string];
36
+ } reverseBlock:^NSString *(NSNumber *number, BOOL *success, NSError *__autoreleasing *error) {
37
+ return [dict allKeysForObject:number].firstObject;
38
+ }];
39
+ }
40
+ {{/dict_mappings}}
41
+
42
+ + ({{name}}*)create:(void(^)({{name}}Builder *builder))builderBlock
43
+ {
44
+ {{name}}Builder *builder = [{{name}}Builder new];
45
+ if(builderBlock)
46
+ {
47
+ builderBlock(builder);
48
+ }
49
+ return [[self alloc] initWithBuilder:builder modelVersion:1];
50
+ }
51
+
52
+ - (instancetype)init
53
+ {
54
+ {{name}}Builder *builder = [{{name}}Builder new];
55
+ return [[[self class] alloc] initWithBuilder:builder modelVersion:1];
56
+ }
57
+
58
+ - (instancetype)initWithBuilder:({{name}}Builder*)builder modelVersion:(int)modelVersion
59
+ {
60
+ self = [super init];
61
+
62
+ if (self) {
63
+ __modelVersion = modelVersion;
64
+ {{#props}}
65
+ _{{name}} = builder.{{name}};
66
+ {{/props}}
67
+ }
68
+
69
+ return self;
70
+ }
71
+
72
+ - ({{name}}*)mutate:(void(^)({{name}}Builder *builder))builderBlock
73
+ {
74
+ {{name}}Builder *builder = [{{name}}Builder new];
75
+
76
+ {{#props}}
77
+ builder.{{name}} = self.{{name}};
78
+ {{/props}}
79
+
80
+ builderBlock(builder);
81
+
82
+ return [[[self class] alloc] initWithBuilder:builder modelVersion:__modelVersion + 1];
83
+ }
84
+
85
+ - (BOOL)isEqual:(id)other {
86
+ if (other == self)
87
+ return YES;
88
+ if (!other || ![[other class] isEqual:[self class]])
89
+ return NO;
90
+
91
+ return [self isEqualToOther:other];
92
+ }
93
+
94
+ - (BOOL)isEqualToOther:({{name}} *)object {
95
+ if (self == object)
96
+ return YES;
97
+ if (object == nil)
98
+ return NO;
99
+ {{#props}}
100
+ if (self.{{name}} != object.{{name}})
101
+ return NO;
102
+ {{/props}}
103
+ return YES;
104
+ }
105
+
106
+ - (NSUInteger)hash {
107
+ NSUInteger hash = 0;
108
+ {{#props}}
109
+ {{#is_ref}}
110
+ hash = hash * 31u + [self.{{name}} hash];
111
+ {{/is_ref}}
112
+ {{^is_ref}}
113
+ hash = hash * 31u + self.{{name}};
114
+ {{/is_ref}}
115
+ {{/props}}
116
+ return hash;
117
+ }
118
+
119
+ - (NSString *)description {
120
+ NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])];
121
+ [description appendFormat:@"__modelVersion=%d", __modelVersion];
122
+ {{#props}}
123
+ {{#is_ref}}
124
+ [description appendFormat:@", {{name}}=%@", self.{{name}}];
125
+ {{/is_ref}}
126
+ {{^is_ref}}
127
+ [description appendFormat:@", {{name}}=%@", @(self.{{name}})];
128
+ {{/is_ref}}
129
+ {{/props}}
130
+ [description appendString:@">"];
131
+ return description;
132
+ }
133
+
134
+ - (id)initWithCoder:(NSCoder *)coder {
135
+ self = [super init];
136
+ if (self) {
137
+ {{#props}}
138
+ {{#is_ref}}
139
+ _{{name}} = [coder decodeObjectForKey:@"_{{name}}"];
140
+ {{/is_ref}}
141
+ {{^is_ref}}
142
+ _{{name}} = [coder decodeIntForKey:@"_{{name}}"];
143
+ {{/is_ref}}
144
+ {{/props}}
145
+ __modelVersion = [coder decodeIntForKey:@"__modelVersion"];
146
+ }
147
+
148
+ return self;
149
+ }
150
+
151
+ - (void)encodeWithCoder:(NSCoder *)coder {
152
+ {{#props}}
153
+ {{#is_ref}}
154
+ [coder encodeObject:self.{{name}} forKey:@"_{{name}}"];
155
+ {{/is_ref}}
156
+ {{^is_ref}}
157
+ [coder encodeInteger:self.{{name}} forKey:@"_{{name}}"];
158
+ {{/is_ref}}
159
+ {{/props}}
160
+ [coder encodeInt:__modelVersion forKey:@"__modelVersion"];
161
+ }
162
+
163
+ - (id)copyWithZone:(NSZone *)zone {
164
+ {{name}} *copy = [[[self class] allocWithZone:zone] init];
165
+
166
+ if (copy != nil) {
167
+ copy->__modelVersion = __modelVersion + 1;
168
+ {{#props}}
169
+ copy->_{{name}} = _{{name}};
170
+ {{/props}}
171
+ }
172
+
173
+ return copy;
174
+ }
175
+
176
+
177
+ @end
178
+
179
+ @implementation {{name}}Builder
180
+
181
+ + (NSDictionary *)JSONKeyPathsByPropertyKey {
182
+ return @{};
183
+ }
184
+
185
+ @end
186
+
187
+ {{/models}}
@@ -0,0 +1,37 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <Mantle/Mantle.h>
3
+
4
+ {{#links}}
5
+ #import "{{.}}.h"
6
+ {{/links}}
7
+
8
+ {{#enums}}
9
+ {{#enum .}}
10
+ {{/enum}}
11
+ {{/enums}}
12
+
13
+ {{#models}}
14
+
15
+ @class {{name}}Builder;
16
+
17
+ {{#interface .}}
18
+
19
+ {{#declare_props props readOnly=true}}
20
+ {{/declare_props}}
21
+
22
+
23
+ + ({{name}}*)create:(void(^)({{name}}Builder *builder))builder;
24
+ - ({{name}}*)mutate:(void(^)({{name}}Builder *builder))builder;
25
+
26
+
27
+ {{/interface}}
28
+
29
+ @interface {{name}}Builder : {{base_class}}
30
+
31
+ {{#declare_props props readOnly=false}}
32
+ {{/declare_props}}
33
+
34
+
35
+ @end
36
+
37
+ {{/models}}
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: immutabler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Serhij Korochanskyj
8
+ - Sergey Zenchenko
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2015-09-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: handlebars
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.7'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.7'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.10'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.10'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '10.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '10.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description: Generator of Objective-C immutable models
71
+ email:
72
+ - serge.k@techery.io
73
+ - serge.z@techery.io
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".rspec"
80
+ - ".travis.yml"
81
+ - GETTING_STARTED.md
82
+ - Gemfile
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - bin/console
87
+ - bin/setup
88
+ - immutabler.gemspec
89
+ - lib/immutabler.rb
90
+ - lib/immutabler/core.rb
91
+ - lib/immutabler/dsl/dict_mappings_builder.rb
92
+ - lib/immutabler/dsl/enum.rb
93
+ - lib/immutabler/dsl/enum_attribute.rb
94
+ - lib/immutabler/dsl/enum_attributes_builder.rb
95
+ - lib/immutabler/dsl/group.rb
96
+ - lib/immutabler/dsl/mapping.rb
97
+ - lib/immutabler/dsl/mappings_builder.rb
98
+ - lib/immutabler/dsl/model.rb
99
+ - lib/immutabler/dsl/model_attributes_builder.rb
100
+ - lib/immutabler/dsl/prop.rb
101
+ - lib/immutabler/dsl/props_builder.rb
102
+ - lib/immutabler/template/body_template.rb
103
+ - lib/immutabler/template/builder.rb
104
+ - lib/immutabler/template/header_template.rb
105
+ - lib/immutabler/template/template.rb
106
+ - lib/immutabler/type_mapper.rb
107
+ - lib/immutabler/version.rb
108
+ - templates/body_template.hbs
109
+ - templates/header_template.hbs
110
+ homepage: https://github.com/techery/immutabler
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.2.2
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Generator of Objective-C immutable models
134
+ test_files: []