metamodel 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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