diff --git a/.github/workflows/push_gem.yml b/.github/workflows/push_gem.yml new file mode 100644 index 0000000..633377c --- /dev/null +++ b/.github/workflows/push_gem.yml @@ -0,0 +1,46 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'ruby/set' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/set + + permissions: + contents: write + id-token: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up Ruby + uses: ruby/setup-ruby@e34163cd15f4bb403dcd72d98e295997e6a55798 # v1.238.0 + with: + bundler-cache: true + ruby-version: ruby + + - name: Publish to RubyGems + uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ece2aae..9e55565 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,15 +3,22 @@ name: test on: [push, pull_request] jobs: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + engine: cruby + min_version: 3.0 + build: + needs: ruby-versions name: build (${{ matrix.ruby }} / ${{ matrix.os }}) strategy: matrix: - ruby: [ 3.2, 3.1, "3.0", head ] + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} os: [ ubuntu-latest, macos-latest ] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index a74b2c4..3035be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Set Changelog +# 1.1.1 (2024-11-29) + +* Enhancements + * Fix Set#^ to respect subclasses [#38][] ([@kyanagi][]) + * Speed up Set#flatten [#39][] ([@kyanagi][]) + # 1.1.0 (2023-12-23) * Optimize for and require Ruby >=3 @@ -48,6 +54,8 @@ This is the first release of set as a gem. Here lists the changes since the ver [#20]: https://github.com/ruby/set/pull/20 [#29]: https://github.com/ruby/set/pull/29 [#30]: https://github.com/ruby/set/pull/30 +[#38]: https://github.com/ruby/set/pull/38 +[#39]: https://github.com/ruby/set/pull/39 [Feature #17838]: https://bugs.ruby-lang.org/issues/17838 [Feature #16989]: https://bugs.ruby-lang.org/issues/16989 @@ -56,4 +64,5 @@ This is the first release of set as a gem. Here lists the changes since the ver [@jeremyevans]: https://github.com/jeremyevans [@k-tsj]: https://github.com/k-tsj [@knu]: https://github.com/knu +[@kyanagi]: https://github.com/kyanagi [@marcandre]: https://github.com/marcandre diff --git a/LICENSE.txt b/LICENSE.txt index 6ffacb6..4d4df5b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2002-2020 Akinori MUSHA +Copyright (c) 2002-2024 Akinori MUSHA All rights reserved. diff --git a/lib/set.rb b/lib/set.rb index 4b706b7..295750f 100644 --- a/lib/set.rb +++ b/lib/set.rb @@ -1,9 +1,17 @@ # frozen_string_literal: true + +if RUBY_VERSION >= '3.5' + if defined?(Set) && defined?(Set.[]) && Set.method(:[]).source_location.nil? + # Remove defined? ... conditional after Ruby 3.5.0-preview2 + return + end +end + # :markup: markdown # # set.rb - defines the Set class # -# Copyright (c) 2002-2023 Akinori MUSHA +# Copyright (c) 2002-2024 Akinori MUSHA # # Documentation by Akinori MUSHA and Gavin Sinclair. # @@ -216,7 +224,7 @@ # has been modified while an element in the set. # class Set - VERSION = "1.1.0" + VERSION = "1.1.2" include Enumerable @@ -335,7 +343,7 @@ def replace(enum) end end - # Converts the set to an array. The order of elements is uncertain. + # Returns an array containing all elements in the set. # # Set[1, 2].to_a #=> [1, 2] # Set[1, 'c', :s].to_a #=> [1, "c", :s] @@ -353,16 +361,19 @@ def to_set(klass = Set, *args, &block) klass.new(self, *args, &block) end - def flatten_merge(set, seen = Set.new) # :nodoc: + def flatten_merge(set, seen = {}) # :nodoc: set.each { |e| if e.is_a?(Set) - if seen.include?(e_id = e.object_id) + case seen[e_id = e.object_id] + when true raise ArgumentError, "tried to flatten recursive Set" + when false + next end - seen.add(e_id) + seen[e_id] = true flatten_merge(e, seen) - seen.delete(e_id) + seen[e_id] = false else add(e) end @@ -540,22 +551,22 @@ def delete?(o) # Deletes every element of the set for which block evaluates to # true, and returns self. Returns an enumerator if no block is # given. - def delete_if + def delete_if(&block) block_given? or return enum_for(__method__) { size } - # @hash.delete_if should be faster, but using it breaks the order - # of enumeration in subclasses. - select { |o| yield o }.each { |o| @hash.delete(o) } + # Instead of directly using @hash.delete_if, perform enumeration + # using self.each that subclasses may override. + select(&block).each { |o| @hash.delete(o) } self end # Deletes every element of the set for which block evaluates to # false, and returns self. Returns an enumerator if no block is # given. - def keep_if + def keep_if(&block) block_given? or return enum_for(__method__) { size } - # @hash.keep_if should be faster, but using it breaks the order of - # enumeration in subclasses. - reject { |o| yield o }.each { |o| @hash.delete(o) } + # Instead of directly using @hash.keep_if, perform enumeration + # using self.each that subclasses may override. + reject(&block).each { |o| @hash.delete(o) } self end @@ -659,7 +670,7 @@ def &(enum) # Set[1, 2] ^ Set[2, 3] #=> # # Set[1, 'b', 'c'] ^ ['b', 'd'] #=> # def ^(enum) - n = Set.new(enum) + n = self.class.new(enum) each { |o| n.add(o) unless n.delete?(o) } n end diff --git a/test/test_set.rb b/test/test_set.rb index 49dc58e..453ace3 100644 --- a/test/test_set.rb +++ b/test/test_set.rb @@ -643,6 +643,11 @@ def test_xor ret = set ^ [2,4,5,5] assert_not_same(set, ret) assert_equal(Set[1,3,5], ret) + + set2 = Set2[1,2,3,4] + ret2 = set2 ^ [2,4,5,5] + assert_instance_of(Set2, ret2) + assert_equal(Set2[1,3,5], ret2) end def test_eq @@ -780,26 +785,54 @@ def test_join assert_equal('1 & 2 & 3', Set[1, 2, 3].join(' & ')) end - def test_inspect - set1 = Set[1, 2] - assert_equal('#', set1.inspect) + if RUBY_VERSION >= '3.5' + def test_inspect + set1 = Set[1, 2] + assert_equal('Set[1, 2]', set1.inspect) - set2 = Set[Set[0], 1, 2, set1] - assert_equal('#, 1, 2, #}>', set2.inspect) + set2 = Set[Set[0], 1, 2, set1] + assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.inspect) - set1.add(set2) - assert_equal('#, 1, 2, #}>}>', set2.inspect) - end + set1.add(set2) + assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect) - def test_to_s - set1 = Set[1, 2] - assert_equal('#', set1.to_s) + c = Class.new(Set) + c.set_temporary_name("_MySet") + assert_equal('_MySet[1, 2]', c[1, 2].inspect) + end + + def test_to_s + set1 = Set[1, 2] + assert_equal('Set[1, 2]', set1.to_s) + + set2 = Set[Set[0], 1, 2, set1] + assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.to_s) - set2 = Set[Set[0], 1, 2, set1] - assert_equal('#, 1, 2, #}>', set2.to_s) + set1.add(set2) + assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.to_s) + end + else + def test_inspect + set1 = Set[1, 2] + assert_equal('#', set1.inspect) + + set2 = Set[Set[0], 1, 2, set1] + assert_equal('#, 1, 2, #}>', set2.inspect) + + set1.add(set2) + assert_equal('#, 1, 2, #}>}>', set2.inspect) + end - set1.add(set2) - assert_equal('#, 1, 2, #}>}>', set2.to_s) + def test_to_s + set1 = Set[1, 2] + assert_equal('#', set1.to_s) + + set2 = Set[Set[0], 1, 2, set1] + assert_equal('#, 1, 2, #}>', set2.to_s) + + set1.add(set2) + assert_equal('#, 1, 2, #}>}>', set2.to_s) + end end def test_compare_by_identity diff --git a/test/test_sorted_set.rb b/test/test_sorted_set.rb index f7ad7af..09a1e7a 100644 --- a/test/test_sorted_set.rb +++ b/test/test_sorted_set.rb @@ -42,4 +42,4 @@ def test_ok_require var = SortedSet.new.to_s RUBY end -end +end if defined?(SortedSet)