xcodeproj 0.1.0 → 0.2.0

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,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,146 @@
1
+ module Xcodeproj
2
+ class Project
3
+
4
+ # In case `scoped` is an Array the list's order is maintained.
5
+ class PBXObjectList
6
+ include Enumerable
7
+
8
+ def initialize(represented_class, project)
9
+ @represented_class = represented_class
10
+ @project = project
11
+ @callbacks = {}
12
+
13
+ yield self if block_given?
14
+ end
15
+
16
+ # Specify callbacks for:
17
+ # * :uuid_scope Returns the list of UUIDs to scope this list to.
18
+ # * :push When an object is added to the list.
19
+ def let(callback_name, &block)
20
+ raise ArgumentError, "Incorrect callback `#{callback_name}'." unless [:uuid_scope, :push].include?(callback_name)
21
+ @callbacks[callback_name] = block
22
+ end
23
+
24
+ def uuid_scope
25
+ @callbacks[:uuid_scope].call
26
+ end
27
+
28
+ def empty?
29
+ uuid_scope.empty?
30
+ end
31
+
32
+ def [](uuid)
33
+ if uuid_scope.include?(uuid) && hash = @project.objects_hash[uuid]
34
+ Object.const_get(hash['isa']).new(@project, uuid, hash)
35
+ end
36
+ end
37
+
38
+ def add(klass, hash = {})
39
+ object = klass.new(@project, nil, hash)
40
+ self << object
41
+ object
42
+ end
43
+
44
+ def new(hash = {})
45
+ add(@represented_class, hash)
46
+ end
47
+
48
+ # Run Ruby in debug mode to receive warnings about calls to :push when a
49
+ # list does not have a callback registered for :push.
50
+ def push(object)
51
+ if @callbacks[:push]
52
+ @callbacks[:push].call(object)
53
+ else
54
+ if $DEBUG
55
+ warn "Pushed object onto a PBXObjectList that does not have a :push callback from: #{caller.first}"
56
+ end
57
+ end
58
+ self
59
+ end
60
+ alias_method :<<, :push
61
+
62
+ def each
63
+ uuid_scope.each do |uuid|
64
+ yield self[uuid]
65
+ end
66
+ end
67
+
68
+ def ==(other)
69
+ self.to_a == other.to_a
70
+ end
71
+
72
+ def size
73
+ uuid_scope.size
74
+ end
75
+
76
+ # Since order can't always be guaranteed, these might need to move to an order specific subclass.
77
+ def first
78
+ to_a.first
79
+ end
80
+ def last
81
+ to_a.last
82
+ end
83
+
84
+ def inspect
85
+ "<PBXObjectList: #{map(&:inspect).join(', ')}>"
86
+ end
87
+
88
+ def where(attributes)
89
+ find { |o| o.matches_attributes?(attributes) }
90
+ end
91
+
92
+ # @todo is it really necessary to have an extra method for this?
93
+ def object_named(name)
94
+ where :name => name
95
+ end
96
+
97
+ # Returns a PBXObjectList instance of objects in the list.
98
+ #
99
+ # By default this list will scope the list by objects matching the
100
+ # specified class and add objects, pushed onto the list, to the parent
101
+ # list
102
+ #
103
+ # If a block is given the list instance is yielded so that the default
104
+ # callbacks can be overridden.
105
+ #
106
+ # @param [AbstractPBXObject] klass The AbstractPBXObject subclass to
107
+ # which the list should be scoped.
108
+ #
109
+ # @yield [PBXObjectList] The list instance, allowing you to
110
+ # easily override the callbacks.
111
+ #
112
+ # @return [PBXObjectList<klass>] The list of matching objects.
113
+ def list_by_class(klass)
114
+ parent = self
115
+ PBXObjectList.new(klass, @project) do |list|
116
+ list.let(:push) do |object|
117
+ # Objects added to the subselection should still use the same
118
+ # callback as this list.
119
+ parent << object
120
+ end
121
+ list.let(:uuid_scope) do
122
+ parent.uuid_scope.select do |uuid|
123
+ @project.objects_hash[uuid]['isa'] == klass.isa
124
+ end
125
+ end
126
+ yield list if block_given?
127
+ end
128
+ end
129
+
130
+ # This only makes sense on those with a specific represented class. Not the main objects list.
131
+ def method_missing(name, *args, &block)
132
+ if @represented_class.respond_to?(name)
133
+ object = @represented_class.send(name, @project, *args)
134
+ # The callbacks are only for AbstractPBXObject instances instantiated
135
+ # from the class method that we forwarded the message to.
136
+ self << object if object.is_a?(Object::AbstractPBXObject)
137
+ object
138
+ else
139
+ super
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+ end