diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b6bc4b5e..0ea98475e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ * Updated latest SDK versions for the release of Xcode 10.2. [Samuel Giddins](https://github.com/segiddins) +* Sort by name like Xcode does. + [Antoine Cœur](https://github.com/Coeur) + [#677](https://github.com/CocoaPods/Xcodeproj/pull/677) + ##### Bug Fixes * Use modern localization identifier 'en' for the development region. diff --git a/lib/xcodeproj/project/object/group.rb b/lib/xcodeproj/project/object/group.rb index b403080c7..8c97c7b8a 100644 --- a/lib/xcodeproj/project/object/group.rb +++ b/lib/xcodeproj/project/object/group.rb @@ -1,5 +1,6 @@ require 'xcodeproj/project/object/helpers/groupable_helper' require 'xcodeproj/project/object/helpers/file_references_factory' +require 'xcodeproj/project/object/helpers/sort_helper' module Xcodeproj class Project @@ -437,10 +438,9 @@ def sort(options = nil) next groups_position == :above ? 1 : -1 end end - - result = File.basename(x.display_name.downcase, '.*') <=> File.basename(y.display_name.downcase, '.*') + result = XcodeSortString.new(File.basename(x.display_name, '.*')) <=> XcodeSortString.new(File.basename(y.display_name, '.*')) if result.zero? - File.extname(x.display_name.downcase) <=> File.extname(y.display_name.downcase) + XcodeSortString.new(File.extname(x.display_name)) <=> XcodeSortString.new(File.extname(y.display_name)) else result end diff --git a/lib/xcodeproj/project/object/helpers/sort_helper.rb b/lib/xcodeproj/project/object/helpers/sort_helper.rb new file mode 100644 index 000000000..7ef4b499c --- /dev/null +++ b/lib/xcodeproj/project/object/helpers/sort_helper.rb @@ -0,0 +1,34 @@ +module Xcodeproj + # Wrapper for a string that sorts by name like Xcode does. + # @example + # arrayOfFilenames.sort_by { |s| XcodeSortString.new(s) } + class XcodeSortString + include Comparable + attr_reader :str, :scrubbed, :ints_and_strings, :i_s_pattern + + def initialize(str) + @str = str + @scrubbed = str.downcase + # `Integer`+`rescue`: credit to https://stackoverflow.com/a/39025521 + @ints_and_strings = @scrubbed.scan(/\d+|\D+/).map { |s| begin Integer(s, 10); rescue ArgumentError; s end } + # comparing patterns: credit to https://rosettacode.org/wiki/Natural_sorting#Ruby + @i_s_pattern = @ints_and_strings.map { |el| el.is_a?(Integer) ? :i : :s }.join + end + + def <=>(other) + if i_s_pattern.start_with?(other.i_s_pattern) || other.i_s_pattern.start_with?(i_s_pattern) + compare = ints_and_strings <=> other.ints_and_strings + if compare != 0 + # we sort naturally + compare + else + # equality, we sort reverse raw + -(str <=> other.str) + end + else + # type mismatch, we sort alphabetically + scrubbed <=> other.scrubbed + end + end + end +end diff --git a/spec/project/object/helpers/sort_helper_spec.rb b/spec/project/object/helpers/sort_helper_spec.rb new file mode 100644 index 000000000..93d55b1c1 --- /dev/null +++ b/spec/project/object/helpers/sort_helper_spec.rb @@ -0,0 +1,52 @@ +require File.expand_path('../../../../spec_helper', __FILE__) + +module ProjectSpecs + describe Xcodeproj::XcodeSortString do + before do + @helper = XcodeSortString + end + + #-------------------------------------------------------------------------# + + describe 'In general' do + it 'sorts like Xcode' do + unsorted_names = [ + # basic values + 'a', 'a1', '1a', + # spaces comparison (test disabled: not properly supported) + #' a', 'a ', + # case comparison + 'A', + # pure integers + '1', '2', '10', '01', + # multi integers + '0.1.1', '0.1.2', '0.1.10', '0.1.01', + # equal integers + 'A1B001', 'A01B1', + ] + sorted = unsorted_names.sort_by { |s| @helper.new(s) } + # order given by Xcode 10.2 "Sort by Name" on macOS 10.14.4, English as primary language + sorted.should == [ + #' a', + '0.1.1', + '0.1.01', + '0.1.2', + '0.1.10', + '1', + '01', + '1a', + '2', + '10', + 'a', + 'A', + #'a ', + 'a1', + 'A1B001', + 'A01B1', + ] + end + end + + #-------------------------------------------------------------------------# + end +end