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_namecasenamewhen:remote_global_id_string# `_id_` would become `Id`'remoteGlobalIDString'elsename.to_s.camelize(:lower)endend# @return [Array<Class>] the list of the classes accepted by the# attribute.#attr_accessor:classes# @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)raise"[Xcodeproj] Set value called for to many attribute"iftype==:to_manyobject.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.## @return [void]#defset_default(object)raise"[Xcodeproj] Set value called for to many attribute"unlesstype==:simpleset_value(object,default_value)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: '#{owner.isa}' expected '#{classes.inspect}' got '#{object.class}' for attribute: #{inspect}"unlessacceptableelseraise"[Xcodeproj] Type checking error: '#{owner.isa}' expected #{classes.map(&:isa)} got #{object.isa} for attribute: #{inspect}"unlessacceptableendenddefinspect"#<name: '#{name}', type: '#{type}', classes: '#{classes}', owner class: '#{owner.isa}'>"endendclassAbstractObject# 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}`## The subclasses should not interfere with the methods synthesised by# the DSL and should only implement convenience methods in top of them.## 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.#defattributesunless@full_attributesattributes=@attributes||[]super_attributes=superclass.respond_to?(:attributes)?superclass.attributes:[]# 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.#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.#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.#defto_many_attributes@to_many_attributes||=attributes.select{|a|a.type==:to_many}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# 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)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)endlistendendprotected# 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)raise"[Xcodeproj] BUG - missing classes for #{attribute.inspect}"unlessattribute.classesraise"[Xcodeproj] BUG - classes:#{attribute.classes} for #{attribute.inspect}"unlessattribute.classes.all?{|klass|klass.is_a?(Class)}@attributes||=[]@attributes<<attributeendend# AbstractObject << self# @return [Hash] the simple attributes hash.#attr_reader:simple_attributes_hash# @!group xcodeproj format attributes# @return (see AbstractObject.attributes)#defattributesself.class.attributesend# @return (see AbstractObject.simple_attributes)#defsimple_attributesself.class.simple_attributesend# @return (see AbstractObject.to_one_attributes)#defto_one_attributesself.class.to_one_attributesend# @return (see AbstractObject.to_many_attributes)#defto_many_attributesself.class.to_many_attributesendend# AbstractObjectendendend