lib/xcodeproj/config.rb



module Xcodeproj
  # This class holds the data for a Xcode build settings file (xcconfig) and
  # serializes it.
  class Config
    # Returns a new instance of Config
    #
    # @param [Hash, File, String] xcconfig_hash_or_file  Initial data.
    require 'set'

    attr_accessor :attributes, :frameworks ,:libraries

    def initialize(xcconfig_hash_or_file = {})
      @attributes = {}
      @includes = []
      @frameworks, @libraries = Set.new, Set.new
      merge!(extract_hash(xcconfig_hash_or_file))
    end

    # @return [Hash] The internal data.
    def to_hash
      hash = @attributes.dup
      flags = hash['OTHER_LDFLAGS'] || ''
      flags = flags.dup.strip
      flags << libraries.to_a.sort.reduce('')  {| memo, l | memo << " -l#{l}" }
      flags << frameworks.to_a.sort.reduce('') {| memo, f | memo << " -framework #{f}" }
      hash['OTHER_LDFLAGS'] = flags.strip
      hash.delete('OTHER_LDFLAGS') if flags.strip.empty?
      hash
    end

    def ==(other)
      other.respond_to?(:to_hash) && other.to_hash == self.to_hash
    end

    # @return [Array] Config's include file list
    # @example
    #
    # Consider following xcconfig file:
    #
    #   #include "SomeConfig"
    #   Key1 = Value1
    #   Key2 = Value2
    #
    #   config.includes # => [ "SomeConfig" ]
    def includes
      @includes
    end

    # Merges the given xcconfig hash or Config into the internal data.
    #
    # If a key in the given hash already exists, in the internal data, then its
    # value is appended to the value in the internal data.
    #
    # @example
    #
    #   config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
    #   config.merge!('OTHER_LDFLAGS' => '-lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"')
    #   config.to_hash # => { 'PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2 -lz', 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers"' }
    #
    # @param [Hash, Config] xcconfig  The data to merge into the internal data.
    def merge!(xcconfig)
      if xcconfig.is_a? Config
        @attributes.merge!(xcconfig.attributes) { |key, v1, v2| "#{v1} #{v2}" }
        @libraries.merge   xcconfig.libraries
        @frameworks.merge  xcconfig.frameworks
      else
      @attributes.merge!(xcconfig.to_hash) { |key, v1, v2| "#{v1} #{v2}" }
      # Parse frameworks and libraries. Then remove the from the linker flags
      flags = @attributes['OTHER_LDFLAGS']
      return unless flags

      frameworks = flags.scan(/-framework\s+([^\s]+)/).map { |m| m[0] }
      libraries  = flags.scan(/-l ?([^\s]+)/).map { |m| m[0] }
      @frameworks.merge frameworks
      @libraries.merge libraries

      new_flags = flags.dup
      frameworks.each {|f| new_flags.gsub!("-framework #{f}", "") }
      libraries.each  {|l| new_flags.gsub!("-l#{l}", ""); new_flags.gsub!("-l #{l}", "") }
      @attributes['OTHER_LDFLAGS'] = new_flags.gsub("\w*", ' ').strip
      end
    end
    alias_method :<<, :merge!

    def merge(config)
      self.dup.tap { |x|x.merge!(config) }
    end

    def dup
      Xcodeproj::Config.new(self.to_hash.dup)
    end

    # Serializes the internal data in the xcconfig format.
    #
    # @example
    #
    #   config = Config.new('PODS_ROOT' => '"$(SRCROOT)/Pods"', 'OTHER_LDFLAGS' => '-lxml2')
    #   config.to_s # => "PODS_ROOT = \"$(SRCROOT)/Pods\"\nOTHER_LDFLAGS = -lxml2"
    #
    # @return [String]  The serialized internal data.
    def to_s
      to_hash.map { |key, value| "#{key} = #{value}" }.join("\n")
    end

    def inspect
      to_hash.inspect
    end

    # Writes the serialized representation of the internal data to the given
    # path.
    #
    # @param [Pathname] pathname  The file that the data should be written to.
    def save_as(pathname)
      pathname.open('w') { |file| file << to_s }
    end

    private

    def extract_hash(argument)
      if argument.respond_to? :read
        hash_from_file_content(argument.read)
      elsif File.readable? argument.to_s
        hash_from_file_content(File.read(argument))
      else
        argument
      end
    end

    def hash_from_file_content(raw_string)
      hash = {}
      raw_string.split("\n").each do |line|
        uncommented_line = strip_comment(line)
        if include = extract_include(uncommented_line)
          @includes.push include
        else
          key, value = extract_key_value(uncommented_line)
          hash[key] = value if key
        end
      end
      hash
    end

    def strip_comment(line)
      line.partition('//').first
    end

    def extract_include(line)
      regexp = /#include\s*"(.+)"/
      match = line.match(regexp)
      match[1] if match
    end

    def extract_key_value(line)
      key, value = line.split('=', 2)
      if key && value
        [key.strip, value.strip]
      else
        []
      end
    end

  end
end