degem 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99c4e8766d6d3f8e6f710e2e13c0e0007190617fff3f83d198b469f0aec2ffb9
4
- data.tar.gz: 4df2c08f44fc1eb1c694a85ae55d36421f4610bb717f3789eb8b5feb93c726e3
3
+ metadata.gz: 4ca5d0f8ec44263d688f664f508c95477f5740aba98449817d1aee6728516c6c
4
+ data.tar.gz: 7dd84507228188120a8ee904e648fedc0116c639f83ba6c57f445a2d5267c69a
5
5
  SHA512:
6
- metadata.gz: 26145f7a46f9b9a3152092ba4fa7065261630b66c5edf443d7e90d1eff1c88264814edefc3048d73c39b24cb7a7e9709114baf5959133de351b841967f17fba0
7
- data.tar.gz: 5f3d721a7bf24c8541f93d5a67672ca71f8bfa7550a11822af4e214d6e338d905b9a4f8a3324457593de276c88cbb18eaf10af910faa8470a9abca9096787eca
6
+ metadata.gz: bee7cac14068642532355b2b88e205b1ebadc35ee8e8f6f31fea257079295e0cae4733d0f59cba95ddea9bc0404bf62cfc641790a98d0ae203630633cfdbeb2b
7
+ data.tar.gz: de01fdf89638fcb8bbbbc267a731e55197442fdaeb26d3b80567811b9f79accfac9a78fe07056b7ea0b993afe025acbbb2a8a80948ee0b78cc51dd89cdd3ed4c
data/lib/degem/cli.rb CHANGED
@@ -9,33 +9,29 @@ module Degem
9
9
  end
10
10
 
11
11
  def initialize(stderr)
12
- @stderr = stderr
12
+ Degem.stderr = stderr
13
13
  end
14
14
 
15
15
  def call
16
16
  unless gemfile_exists?
17
- @stderr.puts "Gemfile not found in the current directory"
17
+ Degem.stderr.puts "Gemfile not found in the current directory"
18
18
  return 1
19
19
  end
20
20
 
21
21
  unused = find_unused.call
22
22
  decorated = decorate_rubygems.call(unused)
23
- Report.new(@stderr).call(decorated)
23
+ Report.new.call(decorated)
24
24
  0
25
25
  end
26
26
 
27
27
  private
28
28
 
29
29
  def find_unused
30
- FindUnused.new(
31
- gemfile_path: GEMFILE,
32
- gem_specification: Gem::Specification,
33
- grep: Grep.new(@stderr)
34
- )
30
+ FindUnused.new(gemfile_path: GEMFILE)
35
31
  end
36
32
 
37
33
  def decorate_rubygems
38
- DecorateRubygems.new(
34
+ DecorateUnusedGems.new(
39
35
  gem_specification: Gem::Specification,
40
36
  git_adapter: GitAdapter.new
41
37
  )
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Degem
4
- class DecorateRubygems
4
+ class DecorateUnusedGems
5
5
  def initialize(gem_specification:, git_adapter:)
6
6
  @gem_specification = gem_specification
7
7
  @git_adapter = git_adapter
@@ -11,7 +11,7 @@ module Degem
11
11
  rubygems.map do |rubygem|
12
12
  gemspec = @gem_specification.find_by_name(rubygem.name)
13
13
  git = @git_adapter.call(rubygem.name)
14
- Rubygem.new(rubygem, gemspec, git)
14
+ UnusedGem.new(rubygem, gemspec, git)
15
15
  end
16
16
  end
17
17
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Degem
4
4
  class FindUnused
5
- def initialize(gemfile_path:, gem_specification:, grep: Grep.new, bundle_paths: GitLsFiles.new)
5
+ def initialize(gemfile_path:, gem_specification: Gem::Specification, bundle_paths: GitLsFiles.new)
6
6
  @gemfile_path = gemfile_path
7
7
  @gem_specification = gem_specification
8
- @grep = grep
9
- @bundle_paths = bundle_paths.call(File.dirname(gemfile_path))
8
+ fallback = Dir.glob(File.join(File.dirname(gemfile_path), "**/*.rb"))
9
+ @bundle_paths = bundle_paths.call(fallback)
10
10
  end
11
11
 
12
12
  def call
@@ -22,160 +22,40 @@ module Degem
22
22
  def reject_railties(rubygems)
23
23
  rubygems
24
24
  .reject { _1.name == "rails" }
25
- .reject do |rubygem|
26
- gem_path = @gem_specification.find_by_name(rubygem.name).full_gem_path
27
- @grep.match?(/(Rails::Railtie|Rails::Engine)/, gem_path)
28
- end
25
+ .reject { _1.consts.grep(/Rails::Railtie|Rails::Engine/).any? }
29
26
  end
30
27
 
31
28
  def reject_used(rubygems)
32
- candidates = rubygems.map { Matcher.new(rubygem: _1, matchers: matchers) }
33
- @grep.inverse_many(candidates, @bundle_paths).map(&:rubygem)
34
- end
35
-
36
- def matchers
37
- [
38
- method(:based_on_top_module),
39
- method(:based_on_top_composite_module_dash),
40
- method(:based_on_top_composite_module_underscore),
41
- method(:based_on_top_call),
42
- method(:based_on_top_composite_call_dash),
43
- method(:based_on_top_composite_call_underscore),
44
- method(:based_on_require),
45
- method(:based_on_require_prefix_path),
46
- method(:based_on_require_path)
47
- ].compact
48
- end
49
-
50
- def gemfile
51
- @gemfile ||= ParseGemfile.new.call(gemfile_path)
52
- end
53
-
54
- def rails?
55
- @rails ||= gemfile.rails?
56
- end
57
-
58
- # gem foo -> Foo:: (but not XFoo:: or X::Foo)
59
- def based_on_top_module(rubygem, line)
60
- return false if rubygem.name.include?("-")
61
-
62
- regex = %r{
63
- (?<!\w::) # Do not match if :: before
64
- (?<!\w) # Do not match if \w before
65
- #{rubygem.name.capitalize}
66
- ::
67
- }x
68
- regex.match?(line)
69
- end
70
-
71
- # gem foo_bar -> FooBar (but not XFooBar or X::FooBar)
72
- def based_on_top_composite_module_underscore(rubygem, line)
73
- return false unless rubygem.name.include?("_")
74
-
75
- regex = %r{
76
- (?<!\w::) # Do not match if :: before
77
- (?<!\w) # Do not match if \w before
78
- #{rubygem.name.split("_").map(&:capitalize).join}
79
- ::
80
- }x
81
- regex.match?(line)
29
+ bundle = ParseRuby.new.call(@bundle_paths)
30
+ rubygems = reject_required(rubygems, bundle.requires)
31
+ reject_consts(rubygems, bundle.consts)
82
32
  end
83
33
 
84
- # gem foo-bar -> Foo::Bar (but not XFoo::Bar or X::Foo::Bar)
85
- def based_on_top_composite_module_dash(rubygem, line)
86
- return false unless rubygem.name.include?("-")
87
-
88
- regex = %r{
89
- (?<!\w::) # Do not match if :: before
90
- (?<!\w) # Do not match if \w before
91
- #{rubygem.name.split("-").map(&:capitalize).join("::")}
92
- }x
93
- regex.match?(line)
94
- end
95
-
96
- # gem foo -> Foo. (but not X::Foo. or XBar.)
97
- def based_on_top_call(rubygem, line)
98
- return false if rubygem.name.include?("-")
99
-
100
- regex = %r{
101
- (?<!\w::) # Do not match if :: before
102
- (?<!\w) # Do not match if \w before
103
- #{rubygem.name.capitalize}
104
- \.
105
- }x
106
- regex.match?(line)
107
- end
108
-
109
- # gem foo-bar -> FooBar. (but not X::FooBar. or XFooBar.)
110
- def based_on_top_composite_call_dash(rubygem, line)
111
- return false unless rubygem.name.include?("-")
112
-
113
- regex = %r{
114
- (?<!\w::) # Do not match if :: before
115
- (?<!\w) # Do not match if \w before
116
- #{rubygem.name.split("-").map(&:capitalize).join}
117
- \.
118
- }x
119
- regex.match?(line)
34
+ def reject_consts(rubygems, bundle_consts)
35
+ rubygems.reject do |rubygem|
36
+ rubygem.own_consts.any? do |own_const|
37
+ bundle_consts.include?(own_const)
38
+ end
39
+ end
120
40
  end
121
41
 
122
- # gem foo_bar -> FooBar. (but not X::FooBar. or XFooBar.)
123
- def based_on_top_composite_call_underscore(rubygem, line)
124
- return false unless rubygem.name.include?("_")
42
+ def reject_required(rubygems, bundle_requires)
43
+ rubygems.reject do |rubygem|
44
+ bundle_requires.any? do |bundle_require|
45
+ next true if bundle_require == rubygem.name
46
+ next true if bundle_require == rubygem.name.tr("-", "/")
125
47
 
126
- regex = %r{
127
- (?<!\w::) # Do not match if :: before
128
- (?<!\w) # Do not match if \w before
129
- #{rubygem.name.split("_").map(&:capitalize).join}
130
- \.
131
- }x
132
- regex.match?(line)
133
- end
134
-
135
- # gem foo-bar -> require 'foo-bar'
136
- def based_on_require(rubygem, line)
137
- regex = %r{
138
- ^
139
- \s*
140
- require
141
- \s+
142
- ['"]
143
- #{rubygem.name}
144
- ['"]
145
- }x
146
- regex.match?(line)
48
+ bundle_require.start_with?("#{rubygem.name}/")
49
+ end
50
+ end
147
51
  end
148
52
 
149
- # gem foo-bar -> require 'foo/bar'
150
- def based_on_require_path(rubygem, line)
151
- return false unless rubygem.name.include?("-")
152
-
153
- regex = %r{
154
- ^
155
- \s*
156
- require
157
- \s+
158
- ['"]
159
- #{rubygem.name.tr("-", "/")} # match foo/bar when rubygem is foo-bar
160
- ['"]
161
- }x
162
- regex.match?(line)
53
+ def gemfile
54
+ @gemfile ||= ParseGemfile.new(@gem_specification).call(gemfile_path)
163
55
  end
164
56
 
165
- # gem foo -> require 'foo/'
166
- def based_on_require_prefix_path(rubygem, line)
167
- return false if rubygem.name.include?("-")
168
-
169
- regex = %r{
170
- ^
171
- \s*
172
- require
173
- \s+
174
- ['"]
175
- #{rubygem.name}
176
- /
177
- }x
178
- regex.match?(line)
57
+ def rails?
58
+ @rails ||= gemfile.rails?
179
59
  end
180
60
  end
181
61
  end
data/lib/degem/gemfile.rb CHANGED
@@ -2,12 +2,16 @@
2
2
 
3
3
  module Degem
4
4
  class Gemfile
5
- def initialize(dsl)
5
+ def initialize(dsl:, gem_specification:)
6
6
  @dsl = dsl
7
+ @gem_specification = gem_specification
7
8
  end
8
9
 
9
10
  def rubygems
10
- @rubygems ||= (gemfile_dependencies + gemspec_dependencies).uniq
11
+ @rubygems ||=
12
+ (gemfile_dependencies + gemspec_dependencies)
13
+ .map { Rubygem.new(rubygem: _1, gem_specification: @gem_specification) }
14
+ .uniq
11
15
  end
12
16
 
13
17
  def rails?
@@ -17,7 +21,9 @@ module Degem
17
21
  private
18
22
 
19
23
  def gemfile_dependencies
20
- @dsl.dependencies.select(&:should_include?)
24
+ @dsl.dependencies.select(&:should_include?).reject do |dependency|
25
+ @dsl.gemspecs.flat_map(&:name).include?(dependency.name)
26
+ end
21
27
  end
22
28
 
23
29
  def gemspec_dependencies
@@ -2,10 +2,14 @@
2
2
 
3
3
  module Degem
4
4
  class ParseGemfile
5
+ def initialize(gem_specification = Gem::Specification)
6
+ @gem_specification = gem_specification
7
+ end
8
+
5
9
  def call(gemfile_path)
6
10
  dsl = Bundler::Dsl.new
7
11
  dsl.eval_gemfile(gemfile_path)
8
- Gemfile.new(dsl)
12
+ Gemfile.new(dsl: dsl, gem_specification: @gem_specification)
9
13
  end
10
14
 
11
15
  private
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Degem
4
+ class ParseRuby
5
+ def initialize(visitor = Visitor)
6
+ @visitor = visitor
7
+ end
8
+
9
+ def call(path)
10
+ visitor = @visitor.new
11
+ Array(path).each do |path|
12
+ visitor.path = path
13
+ Prism.parse_file(path).value.accept(visitor)
14
+ Degem.stderr.putc "."
15
+ end
16
+ visitor
17
+ end
18
+ end
19
+
20
+ require "prism"
21
+
22
+ class Visitor < Prism::Visitor
23
+ def initialize
24
+ @requires = Set.new
25
+ @consts = Set.new
26
+ @path = nil
27
+ @stack = []
28
+ super
29
+ end
30
+
31
+ def requires = @requires.to_a
32
+ def consts = @consts.to_a
33
+ attr_writer :path
34
+
35
+ def visit_call_node(node)
36
+ visit_require_call_node(node)
37
+ super
38
+ end
39
+
40
+ def visit_module_node(node)
41
+ @stack.push(node)
42
+ super
43
+ @consts.add(@stack.map(&:name).join("::"))
44
+ @stack.pop
45
+ end
46
+
47
+ def visit_class_node(node)
48
+ @stack.push(node)
49
+ super
50
+ @consts.add(@stack.map(&:name).join("::"))
51
+ @stack.pop
52
+ end
53
+
54
+ def visit_constant_path_node(node)
55
+ consts_from(node).each { @consts.add(_1) }
56
+ super
57
+ end
58
+
59
+ def visit_constant_read_node(node)
60
+ @consts.add(node.name.to_s) unless @stack.find { _1.constant_path == node }
61
+ super
62
+ end
63
+
64
+ private
65
+
66
+ def visit_require_call_node(node)
67
+ return if node.name.to_s != "require"
68
+ return if node.receiver
69
+ return unless node.arguments
70
+ return unless node.arguments.arguments[0].is_a?(Prism::StringNode)
71
+
72
+ required = node.arguments.arguments[0].unescaped
73
+ @requires.add(required)
74
+ end
75
+
76
+ def from_ancestor_to(node)
77
+ acc = [node]
78
+ node = node.respond_to?(:parent) && node.parent
79
+ while node
80
+ acc.prepend(node)
81
+ node = node.respond_to?(:parent) && node.parent
82
+ end
83
+ acc
84
+ end
85
+
86
+ def consts_from(node)
87
+ from_ancestor_to(node)
88
+ .filter_map { _1.respond_to?(:name) ? _1.name.to_s : nil }
89
+ .tap { _1.singleton_class.include(Scan) }
90
+ .scan { |a, b| [a, b].join("::") }
91
+ end
92
+ end
93
+
94
+ module Scan
95
+ def scan(init = nil)
96
+ if init.nil?
97
+ init = self[0]
98
+ xs = self[1..] || []
99
+ else
100
+ xs = self
101
+ end
102
+
103
+ return self if xs.empty?
104
+
105
+ xs.reduce([init]) do |acc, x|
106
+ acc + [yield(acc.last, x)]
107
+ end
108
+ end
109
+ end
110
+ end
data/lib/degem/report.rb CHANGED
@@ -2,21 +2,17 @@
2
2
 
3
3
  module Degem
4
4
  class Report
5
- def initialize(stderr)
6
- @stderr = stderr
7
- end
8
-
9
5
  def call(rubygems)
10
- @stderr.puts
11
- @stderr.puts
12
- @stderr.puts "The following gems may be unused:"
13
- @stderr.puts
6
+ Degem.stderr.puts
7
+ Degem.stderr.puts
8
+ Degem.stderr.puts "The following gems may be unused (#{rubygems.size}):"
9
+ Degem.stderr.puts
14
10
 
15
11
  rubygems.each do |rubygem|
16
12
  gem_name(rubygem)
17
- @stderr.puts
13
+ Degem.stderr.puts
18
14
  commits(rubygem)
19
- @stderr.puts
15
+ Degem.stderr.puts
20
16
  end
21
17
  end
22
18
 
@@ -30,15 +26,15 @@ module Degem
30
26
  "#{rubygem.name}: #{rubygem.source_code_uri}"
31
27
  end
32
28
 
33
- @stderr.puts(heading)
34
- @stderr.puts("=" * heading.size)
29
+ Degem.stderr.puts(heading)
30
+ Degem.stderr.puts("=" * heading.size)
35
31
  end
36
32
 
37
33
  def commits(rubygem)
38
34
  rubygem.commits.each.with_index do |commit, i|
39
- @stderr.puts("#{commit.hash[0..6]} (#{commit.date}) #{commit.title}")
40
- @stderr.puts(commit.url)
41
- @stderr.puts if i + 1 == rubygem.commits.size
35
+ Degem.stderr.puts("#{commit.hash[0..6]} (#{commit.date}) #{commit.title}")
36
+ Degem.stderr.puts(commit.url)
37
+ Degem.stderr.puts if i + 1 == rubygem.commits.size
42
38
  end
43
39
  end
44
40
  end
data/lib/degem/rubygem.rb CHANGED
@@ -1,16 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
4
+
3
5
  module Degem
4
- class Rubygem < MultiDelegator
5
- attr_reader :commits
6
+ class Rubygem < SimpleDelegator
7
+ def initialize(rubygem:, gem_specification:)
8
+ @gem_specification = gem_specification
9
+ super(rubygem)
10
+ end
11
+
12
+ def consts
13
+ parsed.consts
14
+ end
15
+
16
+ def own_consts
17
+ variations = [
18
+ name,
19
+ name.delete("_-"),
20
+ name.gsub("_", "::"),
21
+ name.gsub("-", "::"),
22
+ *name.split("_").each_cons(2).to_a.map { _1.join("::") },
23
+ *name.split("_").each_cons(2).to_a.map(&:join),
24
+ *name.split("-").each_cons(2).to_a.map { _1.join("::") },
25
+ *name.split("-").each_cons(2).to_a.map(&:join)
26
+ ]
6
27
 
7
- def initialize(_, _, commits)
8
- super
9
- @commits = commits
28
+ consts.filter { |const| variations.any? { |variation| const.downcase == variation.downcase } }
10
29
  end
11
30
 
12
- def source_code_uri
13
- metadata["source_code_uri"] || homepage
31
+ private
32
+
33
+ def parsed
34
+ @parsed ||=
35
+ begin
36
+ gem_path = @gem_specification.find_by_name(name).full_gem_path
37
+ paths = Dir.glob(File.join(gem_path, "**/*.rb"))
38
+ ParseRuby.new.call(paths)
39
+ end
14
40
  end
15
41
  end
16
42
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Degem
4
- class Rubygem < MultiDelegator
4
+ class UnusedGem < MultiDelegator
5
5
  attr_reader :commits
6
6
 
7
7
  def initialize(_, _, commits)
data/lib/degem/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Degem
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/degem.rb CHANGED
@@ -2,18 +2,25 @@
2
2
 
3
3
  require_relative "degem/version"
4
4
  require_relative "degem/gemfile"
5
+ require_relative "degem/rubygem"
5
6
  require_relative "degem/parse_gemfile"
6
- require_relative "degem/grep"
7
7
  require_relative "degem/git_ls_files"
8
- require_relative "degem/matcher"
8
+ require_relative "degem/parse_ruby"
9
9
  require_relative "degem/find_unused"
10
10
  require_relative "degem/multi_delegator"
11
- require_relative "degem/rubygem"
12
- require_relative "degem/decorate_rubygems"
11
+ require_relative "degem/unused_gem"
12
+ require_relative "degem/decorate_unused_gems"
13
13
  require_relative "degem/commit"
14
14
  require_relative "degem/git_adapter"
15
15
  require_relative "degem/report"
16
16
  require_relative "degem/cli"
17
17
 
18
18
  module Degem
19
+ class << self
20
+ attr_writer :stderr
21
+
22
+ def stderr
23
+ @stderr ||= $stderr
24
+ end
25
+ end
19
26
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: degem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 3v0k4
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-05 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-12-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: prism
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
13
27
  description: Degem finds unused gems in the Ruby bundle (ie, an app with a `Gemfile`
14
28
  or a gem with both a `Gemfile` and a gemspec).
15
29
  email:
@@ -23,18 +37,17 @@ files:
23
37
  - lib/degem.rb
24
38
  - lib/degem/cli.rb
25
39
  - lib/degem/commit.rb
26
- - lib/degem/decorate_rubygems.rb
27
- - lib/degem/decorated.rb
40
+ - lib/degem/decorate_unused_gems.rb
28
41
  - lib/degem/find_unused.rb
29
42
  - lib/degem/gemfile.rb
30
43
  - lib/degem/git_adapter.rb
31
44
  - lib/degem/git_ls_files.rb
32
- - lib/degem/grep.rb
33
- - lib/degem/matcher.rb
34
45
  - lib/degem/multi_delegator.rb
35
46
  - lib/degem/parse_gemfile.rb
47
+ - lib/degem/parse_ruby.rb
36
48
  - lib/degem/report.rb
37
49
  - lib/degem/rubygem.rb
50
+ - lib/degem/unused_gem.rb
38
51
  - lib/degem/version.rb
39
52
  homepage: https://github.com/3v0k4/degem
40
53
  licenses:
data/lib/degem/grep.rb DELETED
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "find"
4
-
5
- module Degem
6
- class Grep
7
- def initialize(stderr = StringIO.new)
8
- @stderr = stderr
9
- end
10
-
11
- def match?(matcher, dir)
12
- Find.find(File.expand_path(dir)) do |path|
13
- next unless File.file?(path)
14
- next if File.extname(path) != ".rb"
15
-
16
- @stderr.putc "."
17
- File.foreach(path) do |line|
18
- next unless matcher.match?(line)
19
-
20
- return true
21
- end
22
- end
23
-
24
- false
25
- end
26
-
27
- def inverse_many(matchers, paths)
28
- Find.find(*paths) do |path|
29
- next unless File.file?(path)
30
-
31
- @stderr.putc "."
32
- File.foreach(path) do |line|
33
- matchers = matchers.reject do |matcher|
34
- matcher.match?(line)
35
- end
36
- end
37
- end
38
-
39
- matchers
40
- end
41
- end
42
- end
data/lib/degem/matcher.rb DELETED
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Degem
4
- class Matcher
5
- attr_reader :rubygem
6
-
7
- def initialize(rubygem:, matchers:)
8
- @rubygem = rubygem
9
- @matchers = matchers
10
- end
11
-
12
- def match?(string)
13
- @matchers.any? { _1.call(@rubygem, string) }
14
- end
15
- end
16
- end