xcodeproj 0.1.0 → 0.2.0.rc1

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.
@@ -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