xcodeproj 0.1.0 → 0.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ require 'xcodeproj/project/association/has_many'
2
+ require 'xcodeproj/project/association/has_one'
3
+ require 'xcodeproj/project/association/reflection'
4
+
5
+ module Xcodeproj
6
+ class Project
7
+ module Object
8
+
9
+ class AbstractPBXObject
10
+ class << self
11
+ def has_many(plural_attr_name, options = {}, &block)
12
+ create_association(:has_many, plural_attr_name, options, &block)
13
+ end
14
+
15
+ def has_one(singular_attr_name, options = {})
16
+ create_association(:has_one, singular_attr_name, options)
17
+ end
18
+
19
+ private
20
+
21
+ def create_association(type, name, options, &block)
22
+ reflection = create_reflection(type, name, options)
23
+ unless reflection.inverse?
24
+ attribute(reflection.attribute_name, :as => reflection.attribute_getter)
25
+ end
26
+ define_method(reflection.getter) do
27
+ reflection.association_for(self, &block).get
28
+ end
29
+ define_method(reflection.setter) do |new_value|
30
+ reflection.association_for(self, &block).set(new_value)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class Association
37
+ attr_reader :owner, :reflection
38
+
39
+ def initialize(owner, reflection, &block)
40
+ @owner, @reflection, @block = owner, reflection, block
41
+ end
42
+
43
+ def get
44
+ @reflection.inverse? ? inverse_get : direct_get
45
+ end
46
+
47
+ def set(value)
48
+ @reflection.inverse? ? inverse_set(value) : direct_set(value)
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,51 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+ class Association
5
+
6
+ class HasMany < Association
7
+ def direct_get
8
+ uuids = @owner.send(@reflection.attribute_getter)
9
+ if @block
10
+ # Evaluate the block, which was specified at the class level, in
11
+ # the instance’s context.
12
+ @owner.list_by_class(uuids, @reflection.klass) do |list|
13
+ list.let(:push) do |new_object|
14
+ @owner.instance_exec(new_object, &@block)
15
+ end
16
+ end
17
+ else
18
+ @owner.list_by_class(uuids, @reflection.klass)
19
+ end
20
+ end
21
+
22
+ def inverse_get
23
+ PBXObjectList.new(@reflection.klass, @owner.project) do |list|
24
+ list.let(:uuid_scope) do
25
+ @owner.project.objects.list_by_class(@reflection.klass).select do |object|
26
+ object.send(@reflection.inverse.attribute_getter) == @owner.uuid
27
+ end.map(&:uuid)
28
+ end
29
+ list.let(:push) do |new_object|
30
+ new_object.send(@reflection.inverse.attribute_setter, @owner.uuid)
31
+ end
32
+ end
33
+ end
34
+
35
+ # @todo Currently this does not call the @block, which means that
36
+ # in theory (like with a group's children) the object stays
37
+ # asociated with its old group.
38
+ def direct_set(list)
39
+ @owner.send(@reflection.attribute_setter, list.map(&:uuid))
40
+ end
41
+
42
+ def inverse_set(list)
43
+ raise NotImplementedError
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,39 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+ class Association
5
+
6
+ # @todo Does this need 'new object'-callback support too?
7
+ class HasOne < Association
8
+ def direct_get
9
+ @owner.project.objects[@owner.send(@reflection.attribute_getter)]
10
+ end
11
+
12
+ def inverse_get
13
+ # Loop over all objects of the class and find the one that includes
14
+ # this object in the specified uuid list.
15
+ @owner.project.objects.list_by_class(@reflection.klass).find do |object|
16
+ object.send(@reflection.inverse.attribute_getter).include?(@owner.uuid)
17
+ end
18
+ end
19
+
20
+ def direct_set(object)
21
+ @owner.send(@reflection.attribute_setter, object.uuid)
22
+ end
23
+
24
+ def inverse_set(object)
25
+ # Remove this object from the uuid list of the target
26
+ # that this object was associated to.
27
+ if previous = @owner.send(@reflection.name)
28
+ previous.send(@reflection.inverse.attribute_getter).delete(@owner.uuid)
29
+ end
30
+ # Now assign this object to the new object
31
+ object.send(@reflection.inverse.attribute_getter) << @owner.uuid if object
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,88 @@
1
+ require 'xcodeproj/inflector'
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+
7
+ class AbstractPBXObject
8
+ def self.reflections
9
+ @reflections ||= []
10
+ end
11
+
12
+ def self.create_reflection(type, name, options)
13
+ (reflections << Association::Reflection.new(type, name, options)).last
14
+ end
15
+
16
+ def self.reflection(name)
17
+ reflections.find { |r| r.name.to_s == name.to_s }
18
+ end
19
+ end
20
+
21
+ class Association
22
+ class Reflection
23
+ def initialize(type, name, options)
24
+ @type, @name, @options = type, name.to_s, options
25
+ end
26
+
27
+ attr_reader :type, :name, :options
28
+
29
+ def klass
30
+ @options[:class] ||= begin
31
+ name = "PBX#{@name.classify}"
32
+ name = "XC#{@name.classify}" unless Xcodeproj::Project::Object.const_defined?(name)
33
+ Xcodeproj::Project::Object.const_get(name)
34
+ end
35
+ end
36
+
37
+ def inverse
38
+ klass.reflection(@options[:inverse_of])
39
+ end
40
+
41
+ def inverse?
42
+ !!@options[:inverse_of]
43
+ end
44
+
45
+ def attribute_name
46
+ (@options[:uuid] || @options[:uuids] || @name).to_sym
47
+ end
48
+
49
+ def attribute_getter
50
+ case type
51
+ when :has_many
52
+ uuid_method_name.pluralize
53
+ when :has_one
54
+ uuid_method_name
55
+ end.to_sym
56
+ end
57
+
58
+ def attribute_setter
59
+ "#{attribute_getter}=".to_sym
60
+ end
61
+
62
+ def getter
63
+ @name.to_sym
64
+ end
65
+
66
+ def setter
67
+ "#{@name}=".to_sym
68
+ end
69
+
70
+ def association_for(owner, &block)
71
+ case type
72
+ when :has_many then Association::HasMany
73
+ when :has_one then Association::HasOne
74
+ end.new(owner, self, &block)
75
+ end
76
+
77
+ private
78
+
79
+ def uuid_method_name
80
+ (@options[:uuids_as] || @options[:uuid] || @options[:uuids] || "#{@name.singularize}_reference").to_s.singularize
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,207 @@
1
+ require 'xcodeproj/inflector'
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ # This is the namespace in which all the classes that wrap the objects in
6
+ # a Xcode project reside.
7
+ #
8
+ # The base class from which all classes inherit is AbstractPBXObject.
9
+ #
10
+ # If you need to deal with these classes directly, it's possible to include
11
+ # this namespace into yours, making it unnecessary to prefix them with
12
+ # Xcodeproj::Project::Object.
13
+ #
14
+ # @example
15
+ #
16
+ # class SourceFileSorter
17
+ # include Xcodeproj::Project::Object
18
+ # end
19
+ module Object
20
+
21
+ # Missing constants that begin with either `PBX' or `XC' are assumed to
22
+ # be valid classes in a Xcode project. A new AbstractPBXObject subclass is
23
+ # created for the constant and returned.
24
+ #
25
+ # @return [Class] The generated class inhertiting from AbstractPBXObject.
26
+ def self.const_missing(name)
27
+ if name.to_s =~ /^(PBX|XC)/
28
+ klass = Class.new(AbstractPBXObject)
29
+ const_set(name, klass)
30
+ klass
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ # This is the base class of all object types that can exist in a Xcode
37
+ # project. As such it provides common behavior, but you can only use
38
+ # instances of subclasses of AbstractPBXObject, because this class does
39
+ # not exist in actual Xcode projects.
40
+ class AbstractPBXObject
41
+
42
+ # This defines accessor methods for a key-value pair which occurs in the
43
+ # attributes hash that the object wraps.
44
+ #
45
+ #
46
+ # @example
47
+ #
48
+ # # Create getter and setter methods named after the key it corresponds to
49
+ # # in the attributes hash:
50
+ #
51
+ # class PBXBuildPhase < AbstractPBXObject
52
+ # attribute :settings
53
+ # end
54
+ #
55
+ # build_phase.attributes # => { 'settings' => { 'COMPILER_FLAGS' => '-fobjc-arc' }, ... }
56
+ # build_phase.settings # => { 'COMPILER_FLAGS' => '-fobjc-arc' }
57
+ #
58
+ # build_phase.settings = { 'COMPILER_FLAGS' => '-fobjc-no-arc' }
59
+ # build_phase.attributes # => { 'settings' => { 'COMPILER_FLAGS' => '-fobjc-no-arc' }, ... }
60
+ #
61
+ # # Or with a custom getter and setter methods:
62
+ #
63
+ # class PBXCopyFilesBuildPhase < AbstractPBXObject
64
+ # attribute :dst_path, :as => :destination_path
65
+ # end
66
+ #
67
+ # build_phase.attributes # => { 'dstPath' => 'some/path', ... }
68
+ # build_phase.destination_path # => 'some/path'
69
+ #
70
+ # build_phase.destination_path = 'another/path'
71
+ # build_phase.attributes # => { 'dstPath' => 'another/path', ... }
72
+ #
73
+ #
74
+ # @param [Symbol, String] attribute_name The key in snake case.
75
+ # @option options [Symbol String] :as An optional custom name for
76
+ # the getter and setter methods.
77
+ def self.attribute(name, options = {})
78
+ accessor_name = (options[:as] || name).to_s
79
+ attribute_name = name.to_s.camelize(:lower) # change `foo_bar' to `fooBar'
80
+ define_method(accessor_name) { @attributes[attribute_name] }
81
+ define_method("#{accessor_name}=") { |value| @attributes[attribute_name] = value }
82
+ end
83
+
84
+ def self.isa
85
+ @isa ||= name.split('::').last
86
+ end
87
+
88
+ attr_reader :uuid, :attributes, :project
89
+
90
+ # [String] the object's class name
91
+ attribute :isa
92
+
93
+ # [String] the object's name
94
+ attribute :name
95
+
96
+ # It is not recommended that you instantiate objects through this
97
+ # constructor. It is much easier to use associations to create them.
98
+ #
99
+ # @example
100
+ #
101
+ # file_reference = project.files.new('path' => 'path/to/file')
102
+ #
103
+ # @return [AbstractPBXObject]
104
+ def initialize(project, uuid, attributes)
105
+ @project, @attributes = project, attributes
106
+ self.isa ||= self.class.isa
107
+ unless uuid
108
+ # Add new objects to the main hash with a unique UUID
109
+ uuid = generate_uuid
110
+ @project.add_object_hash(uuid, @attributes)
111
+ end
112
+ @uuid = uuid
113
+ end
114
+
115
+ def destroy
116
+ @project.objects_hash.delete(uuid)
117
+ end
118
+
119
+ def ==(other)
120
+ other.is_a?(AbstractPBXObject) && self.uuid == other.uuid
121
+ end
122
+
123
+ def <=>(other)
124
+ self.uuid <=> other.uuid
125
+ end
126
+
127
+ def inspect
128
+ "#<#{isa} UUID: `#{uuid}', name: `#{name}'>"
129
+ end
130
+
131
+ def matches_attributes?(attributes)
132
+ attributes.all? do |attribute, expected_value|
133
+ return nil unless respond_to?(attribute)
134
+
135
+ if expected_value.is_a?(Hash)
136
+ send(attribute).matches_attributes?(expected_value)
137
+ else
138
+ send(attribute) == expected_value
139
+ end
140
+ end
141
+ end
142
+
143
+ # Returns a PBXObjectList instance of objects in the `uuid_list`.
144
+ #
145
+ # By default this list will scope the list by objects matching the
146
+ # specified class and add objects, pushed onto the list, to the given
147
+ # `uuid_list` array.
148
+ #
149
+ # If a block is given the list instance is yielded so that the default
150
+ # callbacks can be overridden.
151
+ #
152
+ # @param [Array] uuid_list The UUID array instance which is
153
+ # part of the internal data. If this
154
+ # would be an arbitrary array and an
155
+ # object is added, then it doesn't
156
+ # actually modify the internal data,
157
+ # meaning the change is lost.
158
+ #
159
+ # @param [AbstractPBXObject] klass The AbstractPBXObject subclass to
160
+ # which the list should be scoped.
161
+ #
162
+ # @yield [PBXObjectList] The list instance, allowing you to
163
+ # easily override the callbacks.
164
+ #
165
+ # @return [PBXObjectList<klass>] The list of matching objects.
166
+ def list_by_class(uuid_list, klass)
167
+ PBXObjectList.new(klass, @project) do |list|
168
+ list.let(:uuid_scope) do
169
+ # TODO why does this not work? should be more efficient.
170
+ #uuid_list.select do |uuid|
171
+ #@project.objects_hash[uuid]['isa'] == klass.isa
172
+ #end
173
+ uuid_list.map { |uuid| @project.objects[uuid] }.select { |o| o.is_a?(klass) }.map(&:uuid)
174
+ end
175
+ list.let(:push) do |new_object|
176
+ # Add the uuid of a newly created object to the uuids list
177
+ uuid_list << new_object.uuid
178
+ end
179
+ yield list if block_given?
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ # Generate a truly unique UUID. This is to ensure that cutting up the
186
+ # UUID in the xcodeproj extension doesn't cause a collision.
187
+ def generate_uuid
188
+ begin
189
+ uuid = Xcodeproj.generate_uuid
190
+ end while @project.objects_hash.has_key?(uuid)
191
+ uuid
192
+ end
193
+ end
194
+
195
+ end
196
+ end
197
+ end
198
+
199
+ require 'xcodeproj/project/association'
200
+ require 'xcodeproj/project/object_list'
201
+
202
+ # Now load the rest of the classes which inherit from AbstractPBXObject.
203
+ require 'xcodeproj/project/object/build_phase'
204
+ require 'xcodeproj/project/object/configuration'
205
+ require 'xcodeproj/project/object/file_reference'
206
+ require 'xcodeproj/project/object/group'
207
+ require 'xcodeproj/project/object/native_target'
@@ -0,0 +1,89 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+
5
+ class PBXBuildFile < AbstractPBXObject
6
+ # [Hash] the list of build settings for this file
7
+ attribute :settings
8
+
9
+ has_one :file, :uuid => :file_ref
10
+ end
11
+
12
+ class PBXBuildPhase < AbstractPBXObject
13
+ has_many :build_files, :uuids => :files, :uuids_as => :build_file_references
14
+
15
+ # [String] some kind of magic number which seems to always be '2147483647'
16
+ attribute :build_action_mask
17
+
18
+ # [String] wether or not this should only be processed before deployment
19
+ # (I guess). This cane be either '0', or '1'
20
+ attribute :run_only_for_deployment_postprocessing
21
+
22
+ def initialize(*)
23
+ super
24
+ self.build_file_references ||= []
25
+ # These are always the same, no idea what they are.
26
+ self.build_action_mask ||= "2147483647"
27
+ self.run_only_for_deployment_postprocessing ||= "0"
28
+ end
29
+
30
+ def files
31
+ PBXObjectList.new(PBXFileReference, @project) do |list|
32
+ list.let(:uuid_scope) { self.build_files.map(&:file_ref) }
33
+ list.let(:push) do |file|
34
+ self.build_files << file.build_files.new
35
+ end
36
+ end
37
+ end
38
+
39
+ def <<(file)
40
+ files << file
41
+ end
42
+ end
43
+
44
+ class PBXCopyFilesBuildPhase < PBXBuildPhase
45
+ # [String] the path where this file should be copied to
46
+ attribute :dst_path
47
+
48
+ # [String] a magic number which always seems to be "16"
49
+ attribute :dst_subfolder_spec
50
+
51
+ def initialize(*)
52
+ super
53
+ self.dst_path ||= '$(PRODUCT_NAME)'
54
+ self.dst_subfolder_spec ||= "16"
55
+ end
56
+ end
57
+
58
+ class PBXSourcesBuildPhase < PBXBuildPhase; end
59
+ class PBXFrameworksBuildPhase < PBXBuildPhase; end
60
+
61
+ # @todo Should `files`, `input_paths`, and `output_paths` be has_many
62
+ # associations with file references?
63
+ class PBXShellScriptBuildPhase < PBXBuildPhase
64
+ attribute :name
65
+
66
+ attribute :files
67
+ attribute :input_paths
68
+ attribute :output_paths
69
+
70
+ # [String] The path to the script interpreter. Defaults to `/bin/sh`.
71
+ attribute :shell_path
72
+
73
+ # [String] The actual script to perform.
74
+ attribute :shell_script
75
+
76
+ def initialize(*)
77
+ super
78
+ self.files ||= []
79
+ self.input_paths ||= []
80
+ self.output_paths ||= []
81
+
82
+ self.shell_path ||= '/bin/sh'
83
+ self.shell_script ||= ''
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+ end