diff --git a/xcode/build_phases/multiple_swiftlint/array_extension.rb b/xcode/build_phases/multiple_swiftlint/array_extension.rb new file mode 100644 index 0000000..e5bebf6 --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/array_extension.rb @@ -0,0 +1,5 @@ +class Array + def nilOrEmpty? + self.nil? or self.empty? + end +end diff --git a/xcode/build_phases/multiple_swiftlint/command_utils.rb b/xcode/build_phases/multiple_swiftlint/command_utils.rb new file mode 100644 index 0000000..cf12d07 --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/command_utils.rb @@ -0,0 +1,6 @@ +class CommandUtils + def self.make_command(command) + command = command.to_s + return `#{command}` + end +end \ No newline at end of file diff --git a/xcode/build_phases/multiple_swiftlint/git_caretaker.rb b/xcode/build_phases/multiple_swiftlint/git_caretaker.rb new file mode 100644 index 0000000..323464a --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/git_caretaker.rb @@ -0,0 +1,35 @@ +require_relative 'array_extension.rb' +require_relative 'command_utils.rb' +require_relative 'string_extension.rb' + +class GitСaretaker < CommandUtils + def self.get_modified_files + non_indexed_files = get_files_from('git diff --name-only | sed s/.*/"&,"/ ') + indexed_files = get_files_from('git diff --cached --name-only | sed s/.*/"&,"/ ') + + modified_files = non_indexed_files + indexed_files + unique_modified_files = modified_files.uniq + + unique_modified_swift_files = [] + if not unique_modified_files.nilOrEmpty? + unique_modified_swift_files = unique_modified_files.select { |file_path| + file_path.to_s.filter_allowed_symbol_into_path + file_path.to_s.include? '.swift' + } + end + + return unique_modified_swift_files + end + + def self.get_creation_date(file_path) + git_command = 'git log --follow --format=%cD --reverse -- ' + file_path + ' | head -1' + return make_command(git_command) + end + + private + + def self.get_files_from(command) + files_as_string = make_command(command) + return files_as_string.split(',') + end +end \ No newline at end of file diff --git a/xcode/build_phases/multiple_swiftlint/setting_option.rb b/xcode/build_phases/multiple_swiftlint/setting_option.rb new file mode 100644 index 0000000..44b8d7e --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/setting_option.rb @@ -0,0 +1,65 @@ +require 'optparse' +require 'ostruct' + +require_relative 'array_extension.rb' + +class SettingOption + def initialize + @options = OpenStruct.new + OptionParser.new do |opt| + opt.on('-p', '--project_root_path STRING', 'The path of project directory and contains *.xcodeproj file always. ' + + 'Example: project_root_path=~/Projects/MyProject/Source/..') { |option| @options.project_root_path = option } + opt.on('-r', '--source_root_path STRING', 'The path of source directory and may not contains *.xcodeproj file in some cases. ' + + 'Example: source_root_path=~/Projects/MyProject/') { |option| @options.source_root_path = option } + opt.on('-s', '--swiftlint_executable_path STRING', 'The executable path of swiftlint') { |option| @options.swiftlint_executable_path = option } + opt.on('-c', '--check_mode MODE', 'The mode of check is "fully" or "simplified"') { |option| @options.check_mode = option } + opt.on('-u', '--use_multiple BOOL', 'The flag indicates the use of multiple yaml swiftlint configurations') { |option| @options.use_multiple = option } + opt.on('-d', '--source_date DATE', 'The date of grouping files according touchin and old swiftlint rules') { |option| @options.source_date = option } + opt.on('-y', '--touchin_swiftlint_yaml_path STRING', 'The path to the touchin swiftlint yaml relative to the source directory') { |option| @options.touchin_swiftlint_yaml_path = option } + end.parse! + + if @options.check_mode.to_s.nilOrEmpty? + @options.check_mode = 'fully' + end + + if @options.use_multiple.to_s.nilOrEmpty? + @options.use_multiple = 'false' + end + + if @options.source_root_path.to_s.nilOrEmpty? + @options.source_root_path = @options.project_root_path + end + + if @options.touchin_swiftlint_yaml_path.to_s.nilOrEmpty? + @options.touchin_swiftlint_yaml_path = '/build-scripts/xcode/.swiftlint.yml' + end + end + + def project_root_path + @options.project_root_path + end + + def source_date + @options.source_date + end + + def swiftlint_executable_path + @options.swiftlint_executable_path + end + + def check_mode + @options.check_mode + end + + def use_multiple + @options.use_multiple + end + + def source_root_path + @options.source_root_path + end + + def touchin_swiftlint_yaml_path + @options.touchin_swiftlint_yaml_path + end +end diff --git a/xcode/build_phases/multiple_swiftlint/strategy_maker.rb b/xcode/build_phases/multiple_swiftlint/strategy_maker.rb new file mode 100644 index 0000000..a677892 --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/strategy_maker.rb @@ -0,0 +1,160 @@ +require 'fileutils' + +require_relative 'array_extension.rb' +require_relative 'git_caretaker.rb' +require_relative 'string_extension.rb' +require_relative 'swift_file_manager.rb' +require_relative 'yaml_manager.rb' + +class StrategyMaker + def initialize(project_root_path, swiftlint_executable_path, touchin_swiftlint_yaml_path) + @project_root_path = project_root_path + @touchin_swiftlint_yaml_path = project_root_path + touchin_swiftlint_yaml_path + @old_swiftlint_yaml_path = project_root_path + '/.swiftlint.yml' + + @temporary_swiftlint_folder_name = project_root_path + '/temporary_swiftlint' + @touchin_swiftlint_yaml_temporary_path = @temporary_swiftlint_folder_name + '/.touchin_swiftlint.yml' + @old_swiftlint_yaml_temporary_path = @temporary_swiftlint_folder_name + '/.old_swiftlint.yml' + + @swiftlint_autocorrect_command = swiftlint_executable_path + ' autocorrect --path ' + @project_root_path + ' --config ' + @swiftlint_lint_command = swiftlint_executable_path + ' --path ' + @project_root_path + ' --config ' + end + + def run_fully_multiple_strategy(source_date) + create_yaml_managers_and_copy_temporary_files + + exclude_files = unique_exclude_files(@touchin_swiftlint_yaml_manager, @old_swiftlint_yaml_manager) + + swift_files = SwiftFileManager.new(exclude_files, source_date) + swift_files.find_list_file_paths(@project_root_path) + + total_touchin_excluded_files = exclude_files + swift_files.old_files + total_old_excluded_files = exclude_files + swift_files.new_files + + @touchin_swiftlint_yaml_manager.update('excluded', total_touchin_excluded_files) + @old_swiftlint_yaml_manager.update('excluded', total_old_excluded_files) + + run_multiple_strategy(@touchin_swiftlint_yaml_temporary_path, @old_swiftlint_yaml_temporary_path) + end + + def run_simplified_multiple_strategy(source_date, source_root_path) + included_files = GitСaretaker.get_modified_files + + if included_files.nilOrEmpty? + puts 'Git did not found swift files to check' + return + end + + create_yaml_managers_and_copy_temporary_files + + exclude_files = unique_exclude_files(@touchin_swiftlint_yaml_manager, @old_swiftlint_yaml_manager) + included_files = included_files.map { |file_path| source_root_path + file_path } + + swift_file_manager = SwiftFileManager.new(exclude_files, source_date) + swift_file_manager.find_list_file_paths_from(included_files) + + total_touchin_included_files = swift_file_manager.new_files + total_old_included_files = swift_file_manager.old_files + + @touchin_swiftlint_yaml_manager.update('excluded', []) + @old_swiftlint_yaml_manager.update('excluded', []) + + @touchin_swiftlint_yaml_manager.update('included', total_touchin_included_files) + @old_swiftlint_yaml_manager.update('included', total_old_included_files) + + is_exist_total_touchin_included_files = (not total_touchin_included_files.nilOrEmpty?) + is_exist_total_old_included_files = (not total_old_included_files.nilOrEmpty?) + + if is_exist_total_touchin_included_files and is_exist_total_old_included_files + run_multiple_strategy(@touchin_swiftlint_yaml_temporary_path, @old_swiftlint_yaml_temporary_path) + elsif is_exist_total_touchin_included_files and not is_exist_total_old_included_files + run_single_strategy(@touchin_swiftlint_yaml_temporary_path) + elsif not is_exist_total_touchin_included_files and is_exist_total_old_included_files + run_single_strategy(@old_swiftlint_yaml_temporary_path) + else + puts 'Git did not found swift files to check' + end + end + + def run_fully_single_strategy + run_single_strategy(@touchin_swiftlint_yaml_path) + end + + def run_simplified_single_strategy(source_root_path) + included_files = GitСaretaker.get_modified_files + + if included_files.nilOrEmpty? + puts 'Git did not found swift files to check' + return + end + + create_copy_temporary_touchin_files + + touchin_swiftlint_yaml_manager = YamlManager.new(@touchin_swiftlint_yaml_temporary_path) + touchin_excluded_files = touchin_swiftlint_yaml_manager.get_configuration('excluded') + swift_files = SwiftFileManager.new(touchin_excluded_files, '') + + included_files = included_files.select { |file_name| not swift_files.is_excluded_file(file_name) } + included_files = included_files.map { |file_path| source_root_path + file_path } + + touchin_swiftlint_yaml_manager.update('excluded', []) + touchin_swiftlint_yaml_manager.update('included', included_files) + + if not included_files.nilOrEmpty? + run_single_strategy(@touchin_swiftlint_yaml_temporary_path) + else + puts 'Git found the swift files to check, but they are excluded in yaml' + end + end + + private + + def run_single_strategy(swiftlint_yaml_path) + result_swiftlint_command = get_swiftlint_command(swiftlint_yaml_path) + puts result_swiftlint_command + run_bash_command(result_swiftlint_command) + end + + def run_multiple_strategy(touchin_swiftlint_yaml_temporary_path, old_swiftlint_yaml_temporary_path) + touchin_swiftlint_command = get_swiftlint_command(touchin_swiftlint_yaml_temporary_path) + old_swiftlint_command = get_swiftlint_command(old_swiftlint_yaml_temporary_path) + result_swiftlint_command = touchin_swiftlint_command + ' && ' + old_swiftlint_command + puts result_swiftlint_command + run_bash_command(result_swiftlint_command) + end + + def get_swiftlint_command(swiftlint_yaml_path) + autocorrect_command = @swiftlint_autocorrect_command + swiftlint_yaml_path + lint_command = @swiftlint_lint_command + swiftlint_yaml_path + return autocorrect_command + ' && ' + lint_command + end + + def run_bash_command(bash_command) + exit (exec bash_command) + end + + def create_yaml_managers_and_copy_temporary_files + create_copy_temporary_files + + @touchin_swiftlint_yaml_manager = YamlManager.new(@touchin_swiftlint_yaml_temporary_path) + @old_swiftlint_yaml_manager = YamlManager.new(@old_swiftlint_yaml_temporary_path) + end + + def create_copy_temporary_files + create_copy_temporary_touchin_files + FileUtils.cp @old_swiftlint_yaml_path, @old_swiftlint_yaml_temporary_path + end + + def create_copy_temporary_touchin_files + Dir.mkdir(@temporary_swiftlint_folder_name) unless Dir.exist?(@temporary_swiftlint_folder_name) + FileUtils.cp @touchin_swiftlint_yaml_path, @touchin_swiftlint_yaml_temporary_path + end + + def unique_exclude_files(touchin_swiftlint_yaml_manager, old_swiftlint_yaml_manager) + touchin_excluded_files = touchin_swiftlint_yaml_manager.get_configuration('excluded') + old_excluded_files = old_swiftlint_yaml_manager.get_configuration('excluded') + common_exclude_files = touchin_excluded_files + old_excluded_files + unique_exclude_files = common_exclude_files.uniq + return unique_exclude_files + end +end \ No newline at end of file diff --git a/xcode/build_phases/multiple_swiftlint/string_extension.rb b/xcode/build_phases/multiple_swiftlint/string_extension.rb new file mode 100644 index 0000000..33a6357 --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/string_extension.rb @@ -0,0 +1,17 @@ +class String + def with_wrapped_whitespace + self.gsub(/\s+/, '\ ') + end + + def filter_allowed_symbol_into_path + self.gsub!(/[^0-9A-Za-z \-+.\/]/, '') + end + + def true? + self.to_s.downcase == "true" + end + + def nilOrEmpty? + self.nil? or self.empty? + end +end diff --git a/xcode/build_phases/multiple_swiftlint/swift_file_manager.rb b/xcode/build_phases/multiple_swiftlint/swift_file_manager.rb new file mode 100644 index 0000000..b8edf6c --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/swift_file_manager.rb @@ -0,0 +1,68 @@ +require 'fileutils' +require 'date' + +require_relative 'git_caretaker.rb' + +class SwiftFileManager + def initialize(excluded_files, source_date) + if not source_date.nilOrEmpty? + @source_date = Date.parse(source_date) + end + @excluded_files = excluded_files + @new_files = [] + @old_files = [] + end + + def old_files + @old_files + end + + def new_files + @new_files + end + + def find_list_file_paths(start_folder) + swift_files = File.join('**', '*.swift') + Dir.glob(swift_files, base: start_folder) { |file_path| + if not is_excluded_file(file_path) + compare_timestamp(file_path) + end + } + end + + def find_list_file_paths_from(files_path) + files_path.each { |file_path| + if not is_excluded_file(file_path) + compare_timestamp(file_path) + end + } + end + + def is_excluded_file(file_path) + @excluded_files.each do |exclude_file_path| + if file_path.include? exclude_file_path + return true + end + end + return false + end + + private + + def compare_timestamp(file_path) + wrapped_whitespace_file_path = file_path.with_wrapped_whitespace + creation_date_string = GitСaretaker.get_creation_date(wrapped_whitespace_file_path) + if creation_date_string.nilOrEmpty? + @old_files.push(file_path) + puts ('Creation date of ' + file_path + ' was not found') + else + creation_date = Date.parse(creation_date_string) + puts ('Creation date of ' + file_path + ' is ' + creation_date.to_s) + if @source_date < creation_date + @new_files.push(file_path) + else + @old_files.push(file_path) + end + end + end +end \ No newline at end of file diff --git a/xcode/build_phases/multiple_swiftlint/swiftlint.rb b/xcode/build_phases/multiple_swiftlint/swiftlint.rb new file mode 100644 index 0000000..17f8383 --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/swiftlint.rb @@ -0,0 +1,16 @@ +#https://github.com/TouchInstinct/Styleguide/blob/multiple_swiftlint/IOS/Guides/BuildScripts/Multiple_Swiftlint_Guide.md +require_relative 'setting_option.rb' +require_relative 'strategy_maker.rb' + +setting = SettingOption.new +strategy_maker = StrategyMaker.new(setting.project_root_path, setting.swiftlint_executable_path, setting.touchin_swiftlint_yaml_path) + +if setting.check_mode.eql? 'fully' and setting.use_multiple.true? + strategy_maker.run_fully_multiple_strategy(setting.source_date) +elsif setting.check_mode.eql? 'fully' and not setting.use_multiple.true? + strategy_maker.run_fully_single_strategy +elsif setting.check_mode.eql? 'simplified' and setting.use_multiple.true? + strategy_maker.run_simplified_multiple_strategy(setting.source_date, setting.source_root_path) +elsif setting.check_mode.eql? 'simplified' and not setting.use_multiple.true? + strategy_maker.run_simplified_single_strategy(setting.source_root_path) +end diff --git a/xcode/build_phases/multiple_swiftlint/yaml_manager.rb b/xcode/build_phases/multiple_swiftlint/yaml_manager.rb new file mode 100644 index 0000000..cb3bbd8 --- /dev/null +++ b/xcode/build_phases/multiple_swiftlint/yaml_manager.rb @@ -0,0 +1,24 @@ +require 'yaml' +require 'fileutils' + +class YamlManager + def initialize(swiftlint_yaml_path) + @swiftlint_yaml_path = swiftlint_yaml_path + @configuration ||= YAML.load(File.read(@swiftlint_yaml_path)) + end + + def get_configuration(key) + @configuration[key] + end + + def update(key, new_configuration_values) + @configuration[key] = new_configuration_values + save_settings(@configuration) + end + + private + + def save_settings(settings) + File.write(@swiftlint_yaml_path, settings.to_yaml) + end +end