metamodel 0.2.0 → 0.3.1

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
  SHA1:
3
- metadata.gz: 93f4a8e08c9a3c0248f100f4e43409e63f085756
4
- data.tar.gz: f33f3f2f7dc7a0239dc01c7b8b6dd4d48d851d7a
3
+ metadata.gz: a027c62103e0c1e22308807cbb65e60921e2cfc3
4
+ data.tar.gz: ef725ae4f7bdfbbfd06b306523f127346ac1b44f
5
5
  SHA512:
6
- metadata.gz: 9ebff208564ec7f994210b17b8d63cf2b8a9a81a21474094391312cc070338b694b41cb108a70f7330f51fee857f297152fd71322ea664554a3ae9cdca7379fb
7
- data.tar.gz: c3e358b76d40442fdc2854ddb352f79ed86f61d0d62c032556b494327d1115059b34a1ef7fb37a23a5a973c195ca21d1ec1005b5594743f31edef8f93a432bec
6
+ metadata.gz: 0d68afdc7a74ed5c62a66f5ba85a071c1ffdee8a45e30b2d23fe581e4c2b4212c5db55a3de07e2f799ab8a08d18b036f490379da9926faaf6abb74c3d17cfa61
7
+ data.tar.gz: 83c413f52a8c1fe141424395862d9640311fc0cbc114b943f02f5463a134da0ddef7661217bc0a087201a62d240ba020cb3e635961039f418f71d4a5f3f7db57
data/lib/metamodel.rb CHANGED
@@ -25,4 +25,6 @@ module MetaModel
25
25
 
26
26
  autoload :Command, 'metamodel/command'
27
27
  autoload :Parser, 'metamodel/parser'
28
+ autoload :Installer, 'metamodel/installer'
29
+ autoload :Metafile, 'metamodel/metafile'
28
30
  end
@@ -5,7 +5,7 @@ module MetaModel
5
5
  class Command < CLAide::Command
6
6
  require 'metamodel/command/init'
7
7
  require 'metamodel/command/generate'
8
- require 'metamodel/command/build'
8
+ require 'metamodel/command/install'
9
9
  require 'metamodel/command/clean'
10
10
 
11
11
  include Config::Mixin
@@ -18,7 +18,7 @@ module MetaModel
18
18
 
19
19
  METAMODEL_COMMAND_ALIAS = {
20
20
  "g" => "generate",
21
- "i" => "init",
21
+ "i" => "install",
22
22
  "b" => "build",
23
23
  "c" => "clean"
24
24
  }
@@ -48,6 +48,10 @@ module MetaModel
48
48
  super
49
49
  end
50
50
 
51
+ def installer_for_config
52
+ Installer.new(config.metafile)
53
+ end
54
+
51
55
  #-------------------------------------------------------------------------#
52
56
 
53
57
  private
@@ -0,0 +1,52 @@
1
+ require 'git'
2
+
3
+ module MetaModel
4
+ class Command
5
+ class Install < Command
6
+ include Config::Mixin
7
+ self.summary = "Build a MetaModel.framework from Metafile"
8
+ self.description = <<-DESC
9
+ Clone a metamodel project template from GitHub, parsing Metafile, validating models,
10
+ generate model swift file and build MetaModel.framework.
11
+ DESC
12
+
13
+ attr_accessor :models
14
+
15
+ def initialize(argv)
16
+ validate!
17
+ super
18
+ end
19
+
20
+ def run
21
+ UI.section "Building MetaModel.framework in project" do
22
+ prepare
23
+ installer = installer_for_config
24
+ installer.install!
25
+ end
26
+ UI.notice "Please drag MetaModel.framework into Embedded Binaries phrase.\n"
27
+ end
28
+
29
+ def prepare
30
+ clone_project
31
+ end
32
+
33
+ def clone_project
34
+ if File.exist? config.metamodel_xcode_project
35
+ UI.message "Existing project `#{config.metamodel_xcode_project}`"
36
+ else
37
+ UI.section "Cloning MetaModel project into `./metamodel` folder" do
38
+ Git.clone(config.metamodel_template_uri, 'metamodel', :depth => 1)
39
+ UI.message "Using `#{config.metamodel_xcode_project}` to build module"
40
+ end
41
+ end
42
+ end
43
+
44
+ def validate!
45
+ # super
46
+ raise Informative, 'No Metafile in current directory' unless config.metafile_in_dir(Pathname.pwd)
47
+ end
48
+
49
+ private
50
+ end
51
+ end
52
+ end
@@ -115,6 +115,10 @@ module MetaModel
115
115
  nil
116
116
  end
117
117
 
118
+ def metafile
119
+ @metafile ||= Metafile.from_file(metafile_path) if metafile_path
120
+ end
121
+
118
122
  public
119
123
 
120
124
  #-------------------------------------------------------------------------#
@@ -0,0 +1,92 @@
1
+ module MetaModel
2
+ class Installer
3
+ require 'metamodel/installer/renderer'
4
+ require 'metamodel/installer/validator'
5
+
6
+ include Config::Mixin
7
+
8
+ attr_reader :metafile
9
+
10
+ attr_accessor :models
11
+ attr_accessor :associations
12
+
13
+ attr_accessor :current_model
14
+
15
+ def initialize(metafile)
16
+ @metafile = metafile
17
+ end
18
+
19
+ def install!
20
+ @models = metafile.models
21
+ @associations = metafile.associations
22
+ Renderer.new(@models, @associations).tap do |renderer|
23
+ renderer.render!
24
+ end
25
+
26
+ update_initialize_method
27
+ build_metamodel_framework unless config.skip_build?
28
+ end
29
+
30
+ def update_initialize_method
31
+ template = File.read File.expand_path(File.join(File.dirname(__FILE__), "./template/metamodel.swift"))
32
+ result = ErbalTemplate::render_from_hash(template, { :models => @models, :associations => @associations })
33
+ model_path = Pathname.new("./metamodel/MetaModel/MetaModel.swift")
34
+ File.write model_path, result
35
+ end
36
+
37
+ def build_metamodel_framework
38
+ UI.section "Generating MetaModel.framework" do
39
+ build_framework_on_iphoneos
40
+ build_framework_on_iphonesimulator
41
+ copy_framework_swiftmodule_files
42
+ lipo_frameworks_on_different_archs
43
+ end
44
+ UI.message "-> ".green + "MetaModel.framework located in current folder"
45
+ end
46
+
47
+ def build_framework_on_iphoneos
48
+ build_iphoneos = "xcodebuild -scheme MetaModel \
49
+ -project MetaModel/MetaModel.xcodeproj \
50
+ -configuration Release -sdk iphoneos \
51
+ -derivedDataPath './metamodel' \
52
+ BITCODE_GENERATION_MODE=bitcode \
53
+ ONLY_ACTIVE_ARCH=NO \
54
+ CODE_SIGNING_REQUIRED=NO \
55
+ CODE_SIGN_IDENTITY= \
56
+ clean build"
57
+ result = system "#{build_iphoneos} > /dev/null"
58
+ raise Informative, 'Building framework on iphoneos failed.' unless result
59
+ end
60
+
61
+ def build_framework_on_iphonesimulator
62
+ build_iphonesimulator = "xcodebuild -scheme MetaModel \
63
+ -project MetaModel/MetaModel.xcodeproj \
64
+ -configuration Release -sdk iphonesimulator \
65
+ -derivedDataPath './metamodel' \
66
+ ONLY_ACTIVE_ARCH=NO \
67
+ CODE_SIGNING_REQUIRED=NO \
68
+ CODE_SIGN_IDENTITY= \
69
+ clean build"
70
+ result = system "#{build_iphonesimulator} > /dev/null"
71
+ raise Informative, 'Building framework on iphonesimulator failed.' unless result
72
+ end
73
+
74
+ BUILD_PRODUCTS_FOLDER = "./metamodel/Build/Products"
75
+
76
+ def copy_framework_swiftmodule_files
77
+ copy_command = "cp -rf #{BUILD_PRODUCTS_FOLDER}/Release-iphoneos/MetaModel.framework . && \
78
+ cp -rf #{BUILD_PRODUCTS_FOLDER}/Release-iphonesimulator/MetaModel.framework/Modules/MetaModel.swiftmodule/* \
79
+ MetaModel.framework/Modules/MetaModel.swiftmodule/"
80
+ system copy_command
81
+ end
82
+
83
+ def lipo_frameworks_on_different_archs
84
+ lipo_command = "lipo -create -output MetaModel.framework/MetaModel \
85
+ #{BUILD_PRODUCTS_FOLDER}/Release-iphonesimulator/MetaModel.framework/MetaModel \
86
+ #{BUILD_PRODUCTS_FOLDER}/Release-iphoneos/MetaModel.framework/MetaModel"
87
+ result = system "#{lipo_command}"
88
+ raise Informative, 'Copy framework to current folder failed.' unless result
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,116 @@
1
+ require 'xcodeproj'
2
+
3
+ module MetaModel
4
+ class Installer
5
+ class Renderer
6
+ include Config::Mixin
7
+
8
+ attr_reader :project
9
+
10
+ attr_reader :models
11
+ attr_reader :associations
12
+
13
+ def initialize(models, associations)
14
+ @models = models
15
+ @associations = associations
16
+ @project = Xcodeproj::Project.open(Config.instance.metamodel_xcode_project)
17
+ end
18
+
19
+ SWIFT_TEMPLATES_FILES = %w(
20
+ file_header
21
+ table_initialize
22
+ model_initialize
23
+ model_update
24
+ model_query
25
+ model_delete
26
+ static_methods
27
+ helper
28
+ )
29
+
30
+ def model_swift_templates
31
+ [].tap do |templates|
32
+ SWIFT_TEMPLATES_FILES.each do |file_path|
33
+ template = File.read File.expand_path(File.join(File.dirname(__FILE__), "../template/#{file_path}.swift"))
34
+ templates << template
35
+ end
36
+ end
37
+ end
38
+
39
+ def render!
40
+ remove_previous_files_refereneces
41
+ UI.section "Generating model files" do
42
+ render_model_files
43
+ end
44
+ UI.section "Generating association files" do
45
+ render_association_files
46
+ end
47
+ @project.save
48
+ end
49
+
50
+ def remove_previous_files_refereneces
51
+ target = @project.targets.first
52
+
53
+ @models.each do |model|
54
+ target.source_build_phase.files_references.each do |file_ref|
55
+ target.source_build_phase.remove_file_reference(file_ref) if file_ref && "#{model.name}.swift" == file_ref.name
56
+ end
57
+ end
58
+
59
+ @associations.each do |association|
60
+ target.source_build_phase.files_references.each do |file_ref|
61
+ target.source_build_phase.remove_file_reference(file_ref) if file_ref && "#{association.class_name}.swift" == file_ref.name
62
+ end
63
+ end
64
+ end
65
+
66
+ def render_model_files
67
+ target = @project.targets.first
68
+
69
+ models_group = @project.main_group.find_subpath('MetaModel/Models', true)
70
+ models_group.clear
71
+ models_group.set_source_tree('SOURCE_ROOT')
72
+
73
+ file_refs = []
74
+ @models.each do |model|
75
+ result = model_swift_templates.map { |template|
76
+ ErbalTemplate::render_from_hash(template, { :model => model })
77
+ }.join("\n")
78
+ model_path = Pathname.new("./metamodel/MetaModel/#{model.name}.swift")
79
+ File.write model_path, result
80
+
81
+ file_refs << models_group.new_reference(Pathname.new("MetaModel/#{model.name}.swift"))
82
+
83
+ UI.message '-> '.green + "Using #{model.name}.swift file"
84
+ end
85
+ target.add_file_references file_refs
86
+ end
87
+
88
+ def render_association_files
89
+ target = @project.targets.first
90
+
91
+ association_group = @project.main_group.find_subpath('MetaModel/Associations', true)
92
+ association_group.clear
93
+ association_group.set_source_tree('SOURCE_ROOT')
94
+
95
+ has_many_association_template = File.read File.expand_path(File.join(File.dirname(__FILE__), "../template/has_many_association.swift"))
96
+ belongs_to_association_template = File.read File.expand_path(File.join(File.dirname(__FILE__), "../template/belongs_to_association.swift"))
97
+
98
+ file_refs = []
99
+ @associations.each do |association|
100
+ template = association.relation == :has_many ? has_many_association_template : belongs_to_association_template
101
+ result = ErbalTemplate::render_from_hash(template, { :association => association })
102
+ file_name = "#{association.class_name}.swift"
103
+ File.write Pathname.new("./metamodel/MetaModel/#{file_name}"), result
104
+
105
+ file_refs << association_group.new_reference(Pathname.new("MetaModel/#{file_name}"))
106
+
107
+ UI.message '-> '.green + "Using #{file_name} file"
108
+ end
109
+ target.add_file_references file_refs
110
+ end
111
+
112
+ private
113
+
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,55 @@
1
+ module MetaModel
2
+ class Installer
3
+ class Validator
4
+ require 'metamodel/record/model'
5
+ require 'metamodel/record/property'
6
+ require 'metamodel/record/association'
7
+
8
+ def translate!
9
+ satisfy_constraint = @associations.reduce([]) do |remain, association|
10
+ expect = remain.select { |assoc| assoc.expect_constraint? association }
11
+ if expect.empty?
12
+ remain << association
13
+ else
14
+ remain.delete expect.first
15
+ end
16
+ remain
17
+ end
18
+ raise Informative, "Unsatisfied constraints in #{satisfy_constraint.map \
19
+ { |x| x.debug_description }}" if satisfy_constraint.size > 0
20
+
21
+ end
22
+
23
+ def validate_models
24
+ existing_types = @models.map { |m| m.properties.map { |property| property.type } }.flatten.uniq
25
+ unsupported_types = existing_types - supported_types
26
+ raise Informative, "Unsupported types #{unsupported_types}" unless unsupported_types == []
27
+ end
28
+
29
+ CURRENT_SUPPORTED_BUILT_IN_TYPES = %w[
30
+ Int
31
+ Double
32
+ Float
33
+ String
34
+ Bool
35
+ NSDate
36
+ ]
37
+
38
+ def built_in_types
39
+ CURRENT_SUPPORTED_BUILT_IN_TYPES.map do |t|
40
+ [t, "#{t}?"]
41
+ end.flatten
42
+ end
43
+
44
+ def supported_types
45
+ @models.reduce(CURRENT_SUPPORTED_BUILT_IN_TYPES) { |types, model|
46
+ types << model.name.to_s
47
+ }.map { |type|
48
+ [type, "#{type}?"]
49
+ }.flatten
50
+ end
51
+
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ require "metamodel/metafile/dsl"
2
+
3
+ module MetaModel
4
+ class Metafile
5
+ include MetaModel::Metafile::DSL
6
+
7
+ attr_accessor :defined_in_file
8
+ attr_accessor :current_model
9
+
10
+ attr_accessor :models
11
+ attr_accessor :associations
12
+
13
+ def initialize(defined_in_file = nil, internal_hash = {})
14
+ @defined_in_file = defined_in_file
15
+ @models = []
16
+ @associations = []
17
+
18
+ evaluate_model_definition(defined_in_file)
19
+ amend_association
20
+ end
21
+
22
+ def evaluate_model_definition(path)
23
+ UI.section "Analyzing Metafile" do
24
+ contents ||= File.open(path, 'r:utf-8', &:read)
25
+
26
+ if contents.respond_to?(:encoding) && contents.encoding.name != 'UTF-8'
27
+ contents.encode!('UTF-8')
28
+ end
29
+
30
+ eval(contents, nil, path.to_s)
31
+ end
32
+ end
33
+
34
+ def self.from_file(path)
35
+ path = Pathname.new(path)
36
+ unless path.exist?
37
+ raise Informative, "No Metafile exists at path `#{path}`."
38
+ end
39
+
40
+ case path.extname
41
+ when '', '.metafile'
42
+ Metafile.new(path)
43
+ else
44
+ raise Informative, "Unsupported Metafile format `#{path}`."
45
+ end
46
+ end
47
+
48
+ def amend_association
49
+ name_model_hash = Hash[@models.collect { |model| [model.name, model] }]
50
+ @associations.map! do |association|
51
+ major_model = name_model_hash[association.major_model]
52
+ major_model.associations << association
53
+ association.major_model = major_model
54
+ association.secondary_model = name_model_hash[association.secondary_model]
55
+ raise Informative, "Associations not satisfied in `Metafile`" unless [association.major_model, association.secondary_model].compact.size == 2
56
+ association
57
+ end
58
+ self
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,44 @@
1
+ module MetaModel
2
+ class Metafile
3
+ module DSL
4
+ require 'metamodel/record/model'
5
+ require 'metamodel/record/property'
6
+ require 'metamodel/record/association'
7
+
8
+ def metamodel_version(version)
9
+ raise Informative,
10
+ "Meta file #{version} not matched with current metamodel version #{VERSION}" if version != VERSION
11
+ end
12
+
13
+ def define(model_name)
14
+ UI.message '-> '.green + "Resolving `#{model_name.to_s.camelize}`"
15
+ @current_model = Record::Model.new(model_name)
16
+ yield if block_given?
17
+ @models << @current_model
18
+ end
19
+
20
+ def attr(key, type = :string, **args)
21
+ @current_model.properties << Record::Property.new(key, type, args)
22
+ end
23
+
24
+ def has_one(name, model_name = nil, **args)
25
+ model_name = name.to_s.singularize.camelize if model_name.nil?
26
+ association = Record::Association.new(name, current_model.name, model_name, :has_one, args)
27
+ @associations << association
28
+ end
29
+
30
+ def has_many(name, model_name = nil, **args)
31
+ model_name = name.to_s.singularize.camelize if model_name.nil?
32
+ raise Informative, "has_many relation can't be created with optional model name" if model_name.end_with? "?"
33
+ association = Record::Association.new(name, current_model.name, model_name, :has_many, args)
34
+ @associations << association
35
+ end
36
+
37
+ def belongs_to(name, model_name = nil, **args)
38
+ model_name = name.to_s.singularize.camelize if model_name.nil?
39
+ association = Record::Association.new(name, current_model.name, model_name, :belongs_to, args)
40
+ @associations << association
41
+ end
42
+ end
43
+ end
44
+ end