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.
- data/README.md +3 -9
- data/ext/xcodeproj/extconf.rb +1 -0
- data/ext/xcodeproj/xcodeproj_ext.c +81 -13
- data/lib/xcodeproj/config.rb +142 -10
- data/lib/xcodeproj/inflector.rb +1 -1
- data/lib/xcodeproj/project/association/has_many.rb +51 -0
- data/lib/xcodeproj/project/association/has_one.rb +39 -0
- data/lib/xcodeproj/project/association/reflection.rb +88 -0
- data/lib/xcodeproj/project/association.rb +54 -0
- data/lib/xcodeproj/project/object/build_phase.rb +89 -0
- data/lib/xcodeproj/project/object/configuration.rb +100 -0
- data/lib/xcodeproj/project/object/file_reference.rb +71 -0
- data/lib/xcodeproj/project/object/group.rb +102 -0
- data/lib/xcodeproj/project/object/native_target.rb +158 -0
- data/lib/xcodeproj/project/object.rb +207 -0
- data/lib/xcodeproj/project/object_list.rb +146 -0
- data/lib/xcodeproj/project.rb +154 -566
- data/lib/xcodeproj.rb +1 -1
- metadata +17 -5
|
@@ -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
|