moduleXcodeprojclassProjectmoduleObject# This class represents an attribute of {AbstractObject} subclasses.# Attributes are created by the {AbstractObject} DSL methods and allow to# mirror the underlying attributes of the xcodeproj document model.## Attributes provide support for runtime type checking. They also allow# {AbstractObject} initialization and serialization to plist.## @todo Add support for a list of required values so objects can be# validated before serialization ?#classAbstractObjectAttributerequire'active_support/inflector'# @return [Symbol] the type of the attribute. It can be `:simple`,# `:to_one`, `:to_many`.#attr_reader:type# @return [Symbol] the name of the attribute.#attr_reader:name# @return [Class] the class that owns the attribute.#attr_accessor:owner# Creates a new attribute with the given type and name.## Attributes are expected to be instantiated only by the# {AbstractObject} DSL methods.## @param [Symbol] type# the type of the attribute.## @param [Symbol] name# the name of the attribute.## @param [Class] owner# the class that owns the attribute.#definitialize(type,name,owner)@type=type@name=name@owner=ownerend# @return [String] The name of the attribute in camel case.## @example# attribute.new(:simple, :project_root)# attribute.plist_name #=> projectRoot#defplist_name@plist_name||=CaseConverter.convert_to_plist(name,:lower)end# @return [Array<Class>] the list of the classes accepted by the# attribute.#attr_accessor:classes# @return [{Symbol, Array<Class>}] the list of the classes accepted by# each key for attributes which store a dictionary.#attr_accessor:classes_by_key# @return [String, Array, Hash] the default value, if any, for simple# attributes.#attr_accessor:default_value# Convenience method that returns the value of this attribute for a# given object.## @param [AbstractObject] object# the object for which the value of this attribute is requested.## @return [String, Array, Hash, AbstractObject, ObjectList]# the value.#defget_value(object)object.send(name)end# Convenience method that sets the value of this attribute for a# given object. It makes sense only for `:simple` or `:to_one`# attributes.## @raise It the type of this attribute is `:to_many`.## @param [AbstractObject] object# the object for which to set the value.## @param [String, Hash, Array, AbstractObject] new_value# the value to set for the attribute.## @return [void]#defset_value(object,new_value)iftype==:to_manyraise'[Xcodeproj] Set value called for a to-many attribute'endobject.send("#{name}=",new_value)end# Convenience method that sets the value of this attribute for a given# object to the default (if any). It makes sense only for `:simple`# attributes.## @param [AbstractObject] object# the object for which to set the default value.## @note It is extremely important to duplicate the default values# otherwise kittens cry!## @return [void]#defset_default(object)unlesstype==:simpleraise"[Xcodeproj] Set value called for a #{type} attribute"endset_value(object,default_value.dup)ifdefault_valueend# Checks that a given value is compatible with the attribute.## This method powers the runtime type checking of the {AbstractObject}# and is used its by synthesised methods.## @raise If the class of the value is not compatible with the attribute.## @return [void]#defvalidate_value(object)returnunlessobjectacceptable=classes.find{|klass|object.class==klass||object.class<klass}iftype==:simpleraise"[Xcodeproj] Type checking error: got `#{object.class}` "\"for attribute: #{inspect}"unlessacceptableelseraise"[Xcodeproj] Type checking error: got `#{object.isa}` for "\"attribute: #{inspect}"unlessacceptableendend# Checks that a given value is compatible with a key for attributes# which store references by key.## This method is used by the #{ObjectDictionary} class.## @raise If the class of the value is not compatible with the given# key.#defvalidate_value_for_key(object,key)unlesstype==:references_by_keysraise'[Xcodeproj] This method should be called only for '\'attributes of type `references_by_keys`'endunlessclasses_by_key.keys.include?(key)raise"[Xcodeproj] unsupported key `#{key}` "\"(accepted `#{classes_by_key.keys}`) for attribute `#{inspect}`"endreturnunlessobjectclasses=Array(classes_by_key[key])acceptable=classes.find{|klass|object.class==klass||object.class<klass}unlessacceptableraise"[Xcodeproj] Type checking error: got `#{object.isa}` "\"for key `#{key}` (which accepts `#{classes}`) of "\"attribute: `#{inspect}`"endend# @return [String] A string suitable for debugging the object.#definspectiftype==:simple"Attribute `#{plist_name}` (type: `#{type}`, classes: "\"`#{classes}`, owner class: `#{owner.isa}`)"else"Attribute `#{plist_name}` (type: `#{type}`, classes: "\"`#{classes.map(&:isa)}`, owner class: `#{owner.isa}`)"endendendclassAbstractObject# The {AbstractObject} DSL methods allow to specify with fidelity the# underlying model of the xcodeproj document format. {AbstractObject}# subclasses should specify their attributes through the following# methods:## - `{AbstractObject.attribute}`# - `{AbstractObject.has_one}`# - `{AbstractObject.has_many}`## @note The subclasses should not interfere with the methods# synthesised by the DSL and should only implement helpers in top# of them.## @note Attributes are typed and are validated at runtime.#class<<self# @return [Array<AbstractObjectAttribute>] the attributes associated# with the class.## @note It includes the attributes defined in the superclass and the# list is cleaned for duplicates. Subclasses should not duplicate# an attribute of the superclass but for the method implementation# they will duplicate them.## @visibility private#defattributesunless@full_attributesattributes=@attributes||[]ifsuperclass.respond_to?(:attributes)super_attributes=superclass.attributeselsesuper_attributes=[]end# The uniqueness of the attributes is very important because the# initialization from plist deletes the values from the# dictionary.@full_attributes=attributes.concat(super_attributes).uniqend@full_attributesend# @return [Array<AbstractObjectAttribute>] the simple attributes# associated with with the class.## @visibility private#defsimple_attributes@simple_attributes||=attributes.select{|a|a.type==:simple}end# @return [Array<AbstractObjectAttribute>] the attributes# representing a to one relationship associated with with the# class.## @visibility private#defto_one_attributes@to_one_attributes||=attributes.select{|a|a.type==:to_one}end# @return [Array<AbstractObjectAttribute>] the attributes# representing a to many relationship associated with with the# class.## @visibility private#defto_many_attributes@to_many_attributes||=attributes.select{|a|a.type==:to_many}end# @visibility private#defreferences_by_keys_attributes@references_by_keys_attributes||=attributes.select{|a|a.type==:references_by_keys}endprivate# Defines a new simple attribute and synthesises the corresponding# methods.## @note Simple attributes are directly stored in a hash. They can# contain only a string, array of strings or a hash containing# strings and thus they are not affected by reference counting.# Clients can access the hash directly through the# {AbstractObject#simple_attributes_hash} method.## @param [Symbol] name# the name of the attribute.## @param [Class] klass# the accepted {Class} for the values of the attribute.## @param [String, Array<String>, Hash{String=>String}] default_value# the default value for new objects.## @example# attribute :project_root# #=> leads to the creation of the following methods## def project_root# @simple_attributes_hash[projectRoot]# end## def project_root=(value)# attribute.validate_value(value)# @simple_attributes_hash[projectRoot] = value# end## @macro [attach] attribute# @!attribute [rw] $1#defattribute(name,klass,default_value=nil)attrb=AbstractObjectAttribute.new(:simple,name,self)attrb.classes=[klass]attrb.default_value=default_valueadd_attribute(attrb)define_method(attrb.name)do@simple_attributes_hash||={}@simple_attributes_hash[attrb.plist_name]enddefine_method("#{attrb.name}=")do|value|@simple_attributes_hash||={}attrb.validate_value(value)@simple_attributes_hash[attrb.plist_name]=valueendend# rubocop:disable Style/PredicateName# Defines a new relationship to a single and synthesises the# corresponding methods.## @note The synthesised setter takes care of handling reference# counting directly.## @param [String] singular_name# the name of the relationship.## @param [Class, Array<Class>] isas# the list of the classes corresponding to the accepted isas for# this relationship.## @macro [attach] has_one# @!attribute [rw] $1#defhas_one(singular_name,isas)isas=[isas]unlessisas.is_a?(Array)attrb=AbstractObjectAttribute.new(:to_one,singular_name,self)attrb.classes=isasadd_attribute(attrb)attr_reader(attrb.name)# 1.9.2 fix, see https://github.com/CocoaPods/Xcodeproj/issues/40.public(attrb.name)define_method("#{attrb.name}=")do|value|attrb.validate_value(value)previous_value=send(attrb.name)previous_value.remove_referrer(self)ifprevious_valueinstance_variable_set("@#{attrb.name}",value)value.add_referrer(self)ifvalueendend# Defines a new ordered relationship to many.## @note This attribute only generates the reader method. Clients are# not supposed to create {ObjectList} objects which are created# by the methods synthesised by this attribute on demand.# Clients, however can mutate the list according to its# interface. The list is responsible to manage the reference# counting for its values.## @param [String] plural_name# the name of the relationship.## @param [Class, Array<Class>] isas# the list of the classes corresponding to the accepted isas for# this relationship.## @macro [attach] has_many# @!attribute [r] $1#defhas_many(plural_name,isas)isas=[isas]unlessisas.is_a?(Array)attrb=AbstractObjectAttribute.new(:to_many,plural_name,self)attrb.classes=isasadd_attribute(attrb)define_method(attrb.name)do# Here we are in the context of the instancelist=instance_variable_get("@#{attrb.name}")unlesslistlist=ObjectList.new(attrb,self)instance_variable_set("@#{attrb.name}",list)endlistendend# Defines a new ordered relationship to many.## @note This attribute only generates the reader method. Clients are# not supposed to create {ObjectList} objects which are created# by the methods synthesised by this attribute on demand.# Clients, however can mutate the list according to its# interface. The list is responsible to manage the reference# counting for its values.## @param [String] plural_name# the name of the relationship.## @param [{Symbol, Array<Class>}] classes_by_key# the list of the classes corresponding to the accepted isas for# this relationship.## @macro [attach] has_many# @!attribute [r] $1#defhas_many_references_by_keys(plural_name,classes_by_key)attrb=AbstractObjectAttribute.new(:references_by_keys,plural_name,self)attrb.classes=classes_by_key.valuesattrb.classes_by_key=classes_by_keyadd_attribute(attrb)define_method(attrb.name)do# Here we are in the context of the instancelist=instance_variable_get("@#{attrb.name}")unlesslistlist=ObjectList.new(attrb,self)instance_variable_set("@#{attrb.name}",list)endlistendend# rubocop:enable Style/PredicateNameprotected# Adds an attribute to the list of attributes of the class.## @note This method is intended to be invoked only by the# {AbstractObject} meta programming methods## @return [void]#defadd_attribute(attribute)unlessattribute.classesraise"[Xcodeproj] BUG - missing classes for #{attribute.inspect}"endunlessattribute.classes.all?{|klass|klass.is_a?(Class)}raise"[Xcodeproj] BUG - classes:#{attribute.classes} for #{attribute.inspect}"end@attributes||=[]@attributes<<attributeendend# AbstractObject << selfprivate# @return [Hash] the simple attributes hash.#attr_reader:simple_attributes_hashpublic# @!group xcodeproj format attributes# @return (see AbstractObject.attributes)## @visibility private#defattributesself.class.attributesend# @return (see AbstractObject.simple_attributes)## @visibility private#defsimple_attributesself.class.simple_attributesend# @return (see AbstractObject.to_one_attributes)## @visibility private#defto_one_attributesself.class.to_one_attributesend# @return (see AbstractObject.to_many_attributes)## @visibility private#defto_many_attributesself.class.to_many_attributesend# @return (see AbstractObject.to_many_attributes)## @visibility private#defreferences_by_keys_attributesself.class.references_by_keys_attributesendendendendend