rambler-digital-solutions/Generamba

View on GitHub
lib/generamba/helpers/xcodeproj_helper.rb

Summary

Maintainability
C
7 hrs
Test Coverage
module Generamba
  # Provides a number of helper methods for working with xcodeproj gem
  class XcodeprojHelper
    # Returns a PBXProject class for a given name
    # @param project_name [String] The name of the project file
    #
    # @return [Xcodeproj::Project]
    def self.obtain_project(project_name)
      Xcodeproj::Project.open(project_name)
    end

    # Adds a provided file to a specific Project and Target
    # @param project [Xcodeproj::Project] The target xcodeproj file
    # @param targets_name [String] Array of targets name
    # @param group_path [Pathname] The Xcode group path for current file
    # @param dir_path [Pathname] The directory path for current file
    # @param file_group_path [String] Directory path
    # @param file_name [String] Current file name
    # @param file_is_resource [TrueClass or FalseClass] If true then file is resource
    #
    # @return [void]
    def self.add_file_to_project_and_targets(project, targets_name, group_path, dir_path, file_group_path, file_name, root_path, file_is_resource = false)
      
      if root_path
          file_path = root_path
      else
          file_path = dir_path
          file_path = file_path.join(file_group_path) if file_group_path
      end

      file_path = file_path.join(file_name) if file_name

      module_group = self.retrieve_group_or_create_if_needed(group_path, dir_path, file_group_path, project, true, root_path)
      xcode_file = module_group.new_file(File.absolute_path(file_path))

      targets_name.each do |target|
        xcode_target = obtain_target(target, project)

        if file_is_resource || self.is_bundle_resource?(file_name)
          xcode_target.add_resources([xcode_file])
        elsif self.is_compile_source?(file_name)
          xcode_target.add_file_references([xcode_file])
        end
      end
    end

    # Adds a provided directory to a specific Project
    # @param project [Xcodeproj::Project] The target xcodeproj file
    # @param group_path [Pathname] The Xcode group path for current directory
    # @param dir_path [Pathname] The directory path for current directory
    # @param directory_name [String] Current directory name
    #
    # @return [void]
    def self.add_group_to_project(project, group_path, dir_path, directory_name, group_is_logical)
      self.retrieve_group_or_create_if_needed(group_path, dir_path, directory_name, project, true, group_is_logical)
    end

    # File is a compiled source
    # @param file_name [String] String of file name
    #
    # @return [TrueClass or FalseClass]
    def self.is_compile_source?(file_name)
      File.extname(file_name) == '.m' || File.extname(file_name) == '.swift' || File.extname(file_name) == '.mm'
    end

    # File is a resource
    # @param resource_name [String] String of resource name
    #
    # @return [TrueClass or FalseClass]
    def self.is_bundle_resource?(resource_name)
      File.extname(resource_name) == '.xib' || File.extname(resource_name) == '.storyboard'
    end

    # Recursively clears children of the given group
    # @param project [Xcodeproj::Project] The working Xcode project file
    # @param group_path [Pathname] The full group path
    #
    # @return [Void]
    def self.clear_group(project, targets_name, group_path, group_is_logical)
      module_group = self.retrieve_group_or_create_if_needed(group_path, nil, nil, project, false, group_is_logical)
      return unless module_group

      files_path = self.files_path_from_group(module_group, project)
      return unless files_path

      files_path.each do |file_path|
        self.remove_file_by_file_path(file_path, targets_name, project)
      end

      module_group.clear
    end

    # Finds a group in a xcodeproj file with a given path
    # @param project [Xcodeproj::Project] The working Xcode project file
    # @param group_path [Pathname] The full group path
    #
    # @return [TrueClass or FalseClass]
    def self.module_with_group_path_already_exists(project, group_path, group_is_logical)
      module_group = self.retrieve_group_or_create_if_needed(group_path, nil, nil, project, false, group_is_logical)
      module_group.nil? ? false : true
    end

    private

    # Finds or creates a group in a xcodeproj file with a given path
    # @param group_path [Pathname] The Xcode group path for module
    # @param dir_path [Pathname] The directory path for module
    # @param file_group_path [String] Directory path
    # @param project [Xcodeproj::Project] The working Xcode project file
    # @param create_group_if_not_exists [TrueClass or FalseClass] If true nonexistent group will be created
    #
    # @return [PBXGroup]
    def self.retrieve_group_or_create_if_needed(group_path, dir_path, file_group_path, project, create_group_if_not_exists, group_is_logical = false)
      group_names = path_names_from_path(group_path)
      group_components_count = group_names.count
      group_names += path_names_from_path(file_group_path) if file_group_path

      final_group = project

      group_names.each_with_index do |group_name, index|
        next_group = final_group[group_name]

        unless next_group
          return nil unless create_group_if_not_exists

          if group_path != dir_path && index == group_components_count-1
              next_group = group_is_logical ? final_group.new_group(group_name) : final_group.new_group(group_name, dir_path, :project)
          else
          next_group = group_is_logical ?  final_group.new_group(group_name) : final_group.new_group(group_name, group_name)
          end
        end

        final_group = next_group
      end

      final_group
    end

    # Returns an AbstractTarget class for a given name
    # @param target_name [String] The name of the target
    # @param project [Xcodeproj::Project] The target xcodeproj file
    #
    # @return [Xcodeproj::AbstractTarget]
    def self.obtain_target(target_name, project)
      project.targets.each do |target|
        return target if target.name == target_name
      end

      error_description = "Cannot find a target with name #{target_name} in Xcode project".red
      raise StandardError, error_description
    end

    # Splits the provided Xcode path to an array of separate paths
    # @param path The full group or file path
    #
    # @return [[String]]
    def self.path_names_from_path(path)
      path.to_s.split('/')
    end

    # Remove build file from target build phase
    # @param file_path [String] The path of the file
    # @param targets_name [String] Array of targets
    # @param project [Xcodeproj::Project] The target xcodeproj file
    #
    # @return [Void]
    def self.remove_file_by_file_path(file_path, targets_name, project)
      file_names = path_names_from_path(file_path)

      build_phases = nil

      if self.is_compile_source?(file_names.last)
        build_phases = self.build_phases_from_targets(targets_name, project)
      elsif self.is_bundle_resource?(file_names.last)
        build_phases = self.resources_build_phase_from_targets(targets_name, project)
      end

      self.remove_file_from_build_phases(file_path, build_phases)
    end

    def self.remove_file_from_build_phases(file_path, build_phases)
      return if build_phases.nil?

      build_phases.each do |build_phase|
        build_phase.files.each do |build_file|
          next if build_file.nil? || build_file.file_ref.nil?

          build_file_path = self.configure_file_ref_path(build_file.file_ref)

          if build_file_path == file_path
            build_phase.remove_build_file(build_file)
          end
        end
      end
    end

    # Find and return target build phases
    # @param targets_name [String] Array of targets
    # @param project [Xcodeproj::Project] The target xcodeproj file
    #
    # @return [[PBXSourcesBuildPhase]]
    def self.build_phases_from_targets(targets_name, project)
      build_phases = []

      targets_name.each do |target_name|
        xcode_target = self.obtain_target(target_name, project)
        xcode_target.build_phases.each do |build_phase|
          if build_phase.isa == 'PBXSourcesBuildPhase'
            build_phases.push(build_phase)
          end
        end
      end

      build_phases
    end

    # Find and return target resources build phase
    # @param targets_name [String] Array of targets
    # @param project [Xcodeproj::Project] The target xcodeproj file
    #
    # @return [[PBXResourcesBuildPhase]]
    def self.resources_build_phase_from_targets(targets_name, project)
      resource_build_phase = []

      targets_name.each do |target_name|
        xcode_target = self.obtain_target(target_name, project)
        resource_build_phase.push(xcode_target.resources_build_phase)
      end

      resource_build_phase
    end

    # Get configure file full path
    # @param file_ref [PBXFileReference] Build file
    #
    # @return [String]
    def self.configure_file_ref_path(file_ref)
      build_file_ref_path = file_ref.hierarchy_path.to_s
      build_file_ref_path[0] = ''

      build_file_ref_path
    end

    # Get all files path from group path
    # @param module_group [PBXGroup] The module group
    # @param project [Xcodeproj::Project] The target xcodeproj file
    #
    # @return [[String]]
    def self.files_path_from_group(module_group, _project)
      files_path = []

      module_group.recursive_children.each do |file_ref|
        if file_ref.isa == 'PBXFileReference'
          file_ref_path = configure_file_ref_path(file_ref)
          files_path.push(file_ref_path)
        end
      end

      files_path
    end
  end
end