leftovers 0.4.3 → 0.5.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/docs/Configuration.md +46 -11
  4. data/lib/config/actioncable.yml +4 -0
  5. data/lib/config/actionmailer.yml +22 -0
  6. data/lib/config/actionpack.yml +190 -0
  7. data/lib/config/actionview.yml +64 -0
  8. data/lib/config/activejob.yml +29 -0
  9. data/lib/config/activemodel.yml +74 -0
  10. data/lib/config/activerecord.yml +179 -0
  11. data/lib/config/activesupport.yml +98 -0
  12. data/lib/config/haml.yml +2 -0
  13. data/lib/config/rails.yml +11 -450
  14. data/lib/config/ruby.yml +6 -0
  15. data/lib/leftovers/ast/node.rb +14 -0
  16. data/lib/leftovers/config.rb +8 -0
  17. data/lib/leftovers/config_validator/schema_hash.rb +50 -5
  18. data/lib/leftovers/file.rb +2 -3
  19. data/lib/leftovers/matcher_builders/node.rb +2 -0
  20. data/lib/leftovers/matcher_builders/node_has_argument.rb +11 -7
  21. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +14 -10
  22. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +17 -13
  23. data/lib/leftovers/matcher_builders/node_has_receiver.rb +15 -0
  24. data/lib/leftovers/matcher_builders/node_type.rb +7 -6
  25. data/lib/leftovers/matcher_builders/node_value.rb +42 -0
  26. data/lib/leftovers/matcher_builders.rb +3 -2
  27. data/lib/leftovers/matchers/node_has_any_positional_argument_with_value.rb +4 -1
  28. data/lib/leftovers/matchers/node_has_positional_argument.rb +0 -4
  29. data/lib/leftovers/matchers/node_has_receiver.rb +20 -0
  30. data/lib/leftovers/matchers/predicate.rb +19 -0
  31. data/lib/leftovers/matchers.rb +2 -0
  32. data/lib/leftovers/merged_config.rb +19 -1
  33. data/lib/leftovers/todo_reporter.rb +9 -6
  34. data/lib/leftovers/version.rb +1 -1
  35. data/lib/leftovers.rb +95 -92
  36. metadata +14 -3
  37. data/lib/leftovers/matcher_builders/argument_node_value.rb +0 -21
@@ -26,6 +26,8 @@ module Leftovers
26
26
 
27
27
  ::Leftovers.each_or_self(at) do |k|
28
28
  case k
29
+ when '*'
30
+ positions << k
29
31
  when ::String, ::Hash
30
32
  keys << k
31
33
  when ::Integer
@@ -35,23 +37,25 @@ module Leftovers
35
37
  # :nocov:
36
38
  end
37
39
  end
40
+
38
41
  keys = nil if keys.empty?
39
42
  positions = nil if positions.empty?
40
43
 
41
44
  [keys, positions]
42
45
  end
43
46
 
44
- def self.build_from_hash(at: nil, has_value: nil, has_value_type: nil, unless_arg: nil) # rubocop:disable Metrics/MethodLength
47
+ def self.build_from_hash( # rubocop:disable Metrics/MethodLength
48
+ at: nil,
49
+ has_value: nil,
50
+ unless_arg: nil
51
+ )
45
52
  keys, positions = separate_argument_types(at)
46
53
 
47
- value_matcher = ::Leftovers::MatcherBuilders::And.build([
48
- ::Leftovers::MatcherBuilders::ArgumentNodeValue.build(has_value),
49
- ::Leftovers::MatcherBuilders::NodeType.build(has_value_type)
50
- ])
54
+ value_matcher = ::Leftovers::MatcherBuilders::NodeValue.build(has_value)
51
55
  matcher = if (keys && positions) || (!keys && !positions)
52
56
  ::Leftovers::MatcherBuilders::Or.build([
53
- ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher),
54
- ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher)
57
+ ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher),
58
+ ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
55
59
  ])
56
60
  elsif keys
57
61
  ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
@@ -3,19 +3,23 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasKeywordArgument
6
- def self.build(keywords, value_matcher)
7
- value_matcher = ::Leftovers::MatcherBuilders::NodePairValue.build(value_matcher)
8
- keyword_matcher = ::Leftovers::MatcherBuilders::NodePairName.build(keywords)
6
+ class << self
7
+ def build(keywords, value_matcher) # rubocop:disable Metrics/MethodLength
8
+ value_matcher = ::Leftovers::MatcherBuilders::NodePairValue.build(value_matcher)
9
+ keyword_matcher = if ::Leftovers.each_or_self(keywords).any? { |x| x == '**' }
10
+ ::Leftovers::Matchers::NodeType.new(:pair)
11
+ else
12
+ ::Leftovers::MatcherBuilders::NodePairName.build(keywords)
13
+ end
9
14
 
10
- pair_matcher = ::Leftovers::MatcherBuilders::And.build([
11
- keyword_matcher, value_matcher
12
- ])
13
- # :nocov:
14
- raise unless pair_matcher
15
+ pair_matcher = ::Leftovers::MatcherBuilders::And.build([
16
+ keyword_matcher, value_matcher
17
+ ])
15
18
 
16
- # :nocov:
19
+ return nil unless pair_matcher
17
20
 
18
- ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
21
+ ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -3,20 +3,24 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasPositionalArgument
6
- def self.build(positions, value_matcher) # rubocop:disable Metrics/MethodLength
7
- if positions && value_matcher
8
- ::Leftovers::MatcherBuilders::Or.each_or_self(positions) do |position|
9
- ::Leftovers::Matchers::NodeHasPositionalArgumentWithValue.new(position, value_matcher)
10
- end
11
- elsif positions
12
- position = positions.is_a?(Array) ? positions.min : positions
6
+ class << self
7
+ def build(positions, value_matcher) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength
8
+ if positions && value_matcher
9
+ ::Leftovers::MatcherBuilders::Or.each_or_self(positions) do |pos|
10
+ if pos == '*'
11
+ ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
12
+ else
13
+ ::Leftovers::Matchers::NodeHasPositionalArgumentWithValue.new(pos, value_matcher)
14
+ end
15
+ end
16
+ elsif positions
17
+ pos = 0 if ::Leftovers.each_or_self(positions).any? { |x| x == '*' }
18
+ pos ||= ::Leftovers.each_or_self(positions).min
13
19
 
14
- ::Leftovers::Matchers::NodeHasPositionalArgument.new(position)
15
- elsif value_matcher
16
- ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
17
- # :nocov:
18
- else raise
19
- # :nocov:
20
+ ::Leftovers::Matchers::NodeHasPositionalArgument.new(pos)
21
+ elsif value_matcher
22
+ ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -0,0 +1,15 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Leftovers
4
+ module MatcherBuilders
5
+ module NodeHasReceiver
6
+ class << self
7
+ def build(pattern)
8
+ matcher = ::Leftovers::MatcherBuilders::NodeValue.build(pattern)
9
+
10
+ ::Leftovers::Matchers::NodeHasReceiver.new(matcher) if matcher
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,18 +5,19 @@ require 'set'
5
5
  module Leftovers
6
6
  module MatcherBuilders
7
7
  module NodeType
8
- def self.build(types_pattern)
8
+ def self.build(types_pattern) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
9
9
  ::Leftovers::MatcherBuilders::Or.each_or_self(types_pattern) do |type|
10
10
  case type
11
11
  when 'Symbol' then ::Leftovers::Matchers::NodeType.new(:sym)
12
12
  when 'String' then ::Leftovers::Matchers::NodeType.new(:str)
13
13
  when 'Integer' then ::Leftovers::Matchers::NodeType.new(:int)
14
14
  when 'Float' then ::Leftovers::Matchers::NodeType.new(:float)
15
- # these would be neat but i can't think of a use-case
16
- # when 'Array' then :array
17
- # when 'Hash' then :hash
18
- # when 'Method' then Set[:send, :csend, :def]
19
- # when 'Constant' then Set[:const, :class, :module]
15
+ when 'Array' then ::Leftovers::Matchers::NodeType.new(:array)
16
+ when 'Hash' then ::Leftovers::Matchers::NodeType.new(:hash)
17
+
18
+ when 'Proc' then ::Leftovers::Matchers::Predicate.new(:proc?)
19
+ # when 'Method' then ::Leftovers::Matchers::NodeType.new(Set[:send, :csend, :def])
20
+ # when 'Constant' then ::Leftovers::Matchers::NodeType.new(Set[:const, :class, :module])
20
21
  # :nocov:
21
22
  else raise
22
23
  # :nocov:
@@ -0,0 +1,42 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Leftovers
4
+ module MatcherBuilders
5
+ module NodeValue
6
+ class << self
7
+ def build(pattern) # rubocop:disable Metrics/MethodLength
8
+ ::Leftovers::MatcherBuilders::Or.each_or_self(pattern) do |pat|
9
+ case pat
10
+ when ::Integer, true, false, nil
11
+ ::Leftovers::Matchers::NodeScalarValue.new(pat)
12
+ when ::String
13
+ ::Leftovers::MatcherBuilders::NodeName.build(pat)
14
+ when ::Hash
15
+ build_from_hash(pat)
16
+ # :nocov:
17
+ else raise
18
+ # :nocov:
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def build_from_hash(pat) # rubocop:disable Metrics/MethodLength
26
+ matcher = ::Leftovers::MatcherBuilders::And.build([
27
+ ::Leftovers::MatcherBuilders::NodeHasArgument.build(pat.slice(:at, :has_value)),
28
+ ::Leftovers::MatcherBuilders::NodeName.build(
29
+ pat.slice(:match, :has_prefix, :has_suffix)
30
+ ),
31
+ ::Leftovers::MatcherBuilders::NodeType.build(pat[:type]),
32
+ ::Leftovers::MatcherBuilders::NodeHasReceiver.build(pat[:has_receiver])
33
+ ])
34
+
35
+ ::Leftovers::MatcherBuilders::AndNot.build(
36
+ matcher, ::Leftovers::MatcherBuilders::NodeValue.build(pat[:unless_arg])
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -4,17 +4,18 @@ module Leftovers
4
4
  module MatcherBuilders
5
5
  autoload(:AndNot, "#{__dir__}/matcher_builders/and_not")
6
6
  autoload(:And, "#{__dir__}/matcher_builders/and")
7
- autoload(:ArgumentNodeValue, "#{__dir__}/matcher_builders/argument_node_value")
8
7
  autoload(:Name, "#{__dir__}/matcher_builders/name")
8
+ autoload(:Node, "#{__dir__}/matcher_builders/node")
9
9
  autoload(:NodeHasArgument, "#{__dir__}/matcher_builders/node_has_argument")
10
10
  autoload(:NodeHasKeywordArgument, "#{__dir__}/matcher_builders/node_has_keyword_argument")
11
11
  autoload(:NodeHasPositionalArgument, "#{__dir__}/matcher_builders/node_has_positional_argument")
12
+ autoload(:NodeHasReceiver, "#{__dir__}/matcher_builders/node_has_receiver")
12
13
  autoload(:NodeName, "#{__dir__}/matcher_builders/node_name")
13
14
  autoload(:NodePairName, "#{__dir__}/matcher_builders/node_pair_name")
14
15
  autoload(:NodePairValue, "#{__dir__}/matcher_builders/node_pair_value")
15
16
  autoload(:NodePath, "#{__dir__}/matcher_builders/node_path")
16
17
  autoload(:NodeType, "#{__dir__}/matcher_builders/node_type")
17
- autoload(:Node, "#{__dir__}/matcher_builders/node")
18
+ autoload(:NodeValue, "#{__dir__}/matcher_builders/node_value")
18
19
  autoload(:Or, "#{__dir__}/matcher_builders/or")
19
20
  autoload(:Path, "#{__dir__}/matcher_builders/path")
20
21
  autoload(:StringPattern, "#{__dir__}/matcher_builders/string_pattern")
@@ -14,7 +14,10 @@ module Leftovers
14
14
  end
15
15
 
16
16
  def ===(node)
17
- node.positional_arguments.any? do |value|
17
+ args = node.positional_arguments
18
+ return false unless args
19
+
20
+ args.any? do |value|
18
21
  @matcher === value
19
22
  end
20
23
  end
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeHasPositionalArgument
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(position)
11
7
  @position = position
12
8
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ module Matchers
5
+ class NodeHasReceiver
6
+ def initialize(matcher)
7
+ @matcher = matcher
8
+
9
+ freeze
10
+ end
11
+
12
+ def ===(node)
13
+ receiver = node.receiver
14
+ @matcher === receiver if receiver
15
+ end
16
+
17
+ freeze
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ module Matchers
5
+ class Predicate
6
+ def initialize(predicate)
7
+ @predicate = predicate
8
+
9
+ freeze
10
+ end
11
+
12
+ def ===(node)
13
+ node.send(@predicate)
14
+ end
15
+
16
+ freeze
17
+ end
18
+ end
19
+ end
@@ -15,6 +15,7 @@ module Leftovers
15
15
  "#{__dir__}/matchers/node_has_positional_argument_with_value"
16
16
  )
17
17
  autoload(:NodeHasPositionalArgument, "#{__dir__}/matchers/node_has_positional_argument")
18
+ autoload(:NodeHasReceiver, "#{__dir__}/matchers/node_has_receiver")
18
19
  autoload(:NodeName, "#{__dir__}/matchers/node_name")
19
20
  autoload(:NodePairValue, "#{__dir__}/matchers/node_pair_value")
20
21
  autoload(:NodePath, "#{__dir__}/matchers/node_path")
@@ -22,5 +23,6 @@ module Leftovers
22
23
  autoload(:NodeType, "#{__dir__}/matchers/node_type")
23
24
  autoload(:Not, "#{__dir__}/matchers/not")
24
25
  autoload(:Or, "#{__dir__}/matchers/or")
26
+ autoload(:Predicate, "#{__dir__}/matchers/predicate")
25
27
  end
26
28
  end
@@ -37,10 +37,12 @@ module Leftovers
37
37
  Leftovers::Config.new(:'.leftovers_todo.yml', path: Leftovers.pwd + '.leftovers_todo.yml')
38
38
  end
39
39
 
40
- def unmemoize
40
+ def unmemoize # rubocop:disable Metrics/CyclomaticComplexity
41
41
  remove_instance_variable(:@exclude_paths) if defined?(@exclude_paths)
42
42
  remove_instance_variable(:@include_paths) if defined?(@include_paths)
43
43
  remove_instance_variable(:@test_paths) if defined?(@test_paths)
44
+ remove_instance_variable(:@haml_paths) if defined?(@haml_paths)
45
+ remove_instance_variable(:@erb_paths) if defined?(@erb_paths)
44
46
  remove_instance_variable(:@dynamic) if defined?(@dynamic)
45
47
  remove_instance_variable(:@keep) if defined?(@keep)
46
48
  end
@@ -61,6 +63,22 @@ module Leftovers
61
63
  )
62
64
  end
63
65
 
66
+ def haml_paths
67
+ @haml_paths ||= FastIgnore.new(
68
+ include_rules: @configs.flat_map(&:haml_paths),
69
+ gitignore: false,
70
+ root: Leftovers.pwd
71
+ )
72
+ end
73
+
74
+ def erb_paths
75
+ @erb_paths ||= FastIgnore.new(
76
+ include_rules: @configs.flat_map(&:erb_paths),
77
+ gitignore: false,
78
+ root: Leftovers.pwd
79
+ )
80
+ end
81
+
64
82
  def dynamic
65
83
  @dynamic ||= ::Leftovers::ProcessorBuilders::EachDynamic.build(@configs.map(&:dynamic))
66
84
  end
@@ -30,12 +30,9 @@ module Leftovers
30
30
  def report_instructions
31
31
  puts <<~MESSAGE
32
32
  generated #{path.basename}.
33
- running leftovers again will read this file and
34
- not alert you to any unused items mentioned in it.
33
+ running leftovers again will read this file and not alert you to any unused items mentioned in it.
35
34
 
36
- commit this file so you/your team can gradually
37
- address these items while still having leftovers
38
- alert you to any newly unused items.
35
+ commit this file so you/your team can gradually address these items while still having leftovers alert you to any newly unused items.
39
36
  MESSAGE
40
37
  end
41
38
 
@@ -108,12 +105,18 @@ module Leftovers
108
105
  def generate_list(title, list)
109
106
  <<~YML
110
107
  # #{title}
111
- #{list.map { |d| print_definition(d) }.join("\n")}
108
+ #{print_definition_list(list)}
112
109
 
113
110
  YML
114
111
  end
115
112
 
113
+ def print_definition_list(definition_list)
114
+ definition_list.map { |definition| print_definition(definition) }.join("\n")
115
+ end
116
+
116
117
  def print_definition(definition)
118
+ return print_definition_list(definition.definitions) if definition.is_a?(DefinitionSet)
119
+
117
120
  " - #{definition.to_s.inspect} # #{definition.location_s} #{definition.source_line.strip}"
118
121
  end
119
122
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leftovers
4
- VERSION = '0.4.3'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/leftovers.rb CHANGED
@@ -3,8 +3,6 @@
3
3
  module Leftovers # rubocop:disable Metrics/ModuleLength
4
4
  class Error < ::StandardError; end
5
5
 
6
- module_function
7
-
8
6
  autoload(:AST, "#{__dir__}/leftovers/ast")
9
7
  autoload(:Backports, "#{__dir__}/leftovers/backports")
10
8
  autoload(:CLI, "#{__dir__}/leftovers/cli")
@@ -32,124 +30,129 @@ module Leftovers # rubocop:disable Metrics/ModuleLength
32
30
  autoload(:VERSION, "#{__dir__}/leftovers/version")
33
31
 
34
32
  class << self
35
- attr_accessor :parallel, :progress, :reporter
33
+ attr_accessor :parallel, :progress
34
+ attr_writer :reporter
36
35
  alias_method :parallel?, :parallel
37
36
  alias_method :progress?, :progress
38
- end
39
37
 
40
- def stdout
41
- @stdout ||= $stdout
42
- end
38
+ def stdout
39
+ @stdout ||= $stdout
40
+ end
43
41
 
44
- def stderr
45
- @stderr ||= $stderr
46
- end
42
+ def stderr
43
+ @stderr ||= $stderr
44
+ end
47
45
 
48
- def config
49
- @config ||= Leftovers::MergedConfig.new(load_defaults: true)
50
- end
46
+ def config
47
+ @config ||= Leftovers::MergedConfig.new(load_defaults: true)
48
+ end
51
49
 
52
- def collector
53
- @collector ||= Leftovers::Collector.new
54
- end
50
+ def collector
51
+ @collector ||= Leftovers::Collector.new
52
+ end
55
53
 
56
- def reporter
57
- @reporter ||= Leftovers::Reporter.new
58
- end
54
+ def reporter
55
+ @reporter ||= Leftovers::Reporter.new
56
+ end
59
57
 
60
- def leftovers
61
- @leftovers ||= begin
62
- collector.collect
63
- collector.definitions.reject(&:in_collection?)
58
+ def leftovers
59
+ @leftovers ||= begin
60
+ collector.collect
61
+ collector.definitions.reject(&:in_collection?)
62
+ end
64
63
  end
65
- end
66
64
 
67
- def run(stdout: StringIO.new, stderr: StringIO.new) # rubocop:disable Metrics/MethodLength
68
- @stdout = stdout
69
- @stderr = stderr
70
- return reporter.report_success if leftovers.empty?
71
-
72
- only_test = []
73
- none = []
74
- leftovers.sort_by(&:location_s).each do |definition|
75
- if !definition.test? && definition.in_test_collection?
76
- only_test << definition
77
- else
78
- none << definition
65
+ def run(stdout: StringIO.new, stderr: StringIO.new) # rubocop:disable Metrics/MethodLength
66
+ @stdout = stdout
67
+ @stderr = stderr
68
+ return reporter.report_success if leftovers.empty?
69
+
70
+ only_test = []
71
+ none = []
72
+ leftovers.sort_by(&:location_s).each do |definition|
73
+ if !definition.test? && definition.in_test_collection?
74
+ only_test << definition
75
+ else
76
+ none << definition
77
+ end
79
78
  end
79
+
80
+ reporter.report(only_test: only_test, none: none)
80
81
  end
81
82
 
82
- reporter.report(only_test: only_test, none: none)
83
- end
83
+ def reset # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
84
+ remove_instance_variable(:@config) if defined?(@config)
85
+ remove_instance_variable(:@collector) if defined?(@collector)
86
+ remove_instance_variable(:@reporter) if defined?(@reporter)
87
+ remove_instance_variable(:@leftovers) if defined?(@leftovers)
88
+ remove_instance_variable(:@try_require_cache) if defined?(@try_require_cache)
89
+ remove_instance_variable(:@stdout) if defined?(@stdout)
90
+ remove_instance_variable(:@stderr) if defined?(@stderr)
91
+ remove_instance_variable(:@parallel) if defined?(@parallel)
92
+ remove_instance_variable(:@pwd) if defined?(@pwd)
93
+ end
84
94
 
85
- def reset # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
86
- remove_instance_variable(:@config) if defined?(@config)
87
- remove_instance_variable(:@collector) if defined?(@collector)
88
- remove_instance_variable(:@reporter) if defined?(@reporter)
89
- remove_instance_variable(:@leftovers) if defined?(@leftovers)
90
- remove_instance_variable(:@try_require) if defined?(@try_require)
91
- remove_instance_variable(:@stdout) if defined?(@stdout)
92
- remove_instance_variable(:@stderr) if defined?(@stderr)
93
- remove_instance_variable(:@parallel) if defined?(@parallel)
94
- remove_instance_variable(:@pwd) if defined?(@pwd)
95
- end
95
+ def resolution_instructions_link
96
+ "https://github.com/robotdana/leftovers/tree/v#{Leftovers::VERSION}/README.md#how_to_resolve"
97
+ end
96
98
 
97
- def resolution_instructions_link
98
- "https://github.com/robotdana/leftovers/tree/v#{Leftovers::VERSION}/README.md#how_to_resolve"
99
- end
99
+ def warn(message)
100
+ stderr.puts("\e[2K#{message}")
101
+ end
100
102
 
101
- def warn(message)
102
- stderr.puts("\e[2K#{message}")
103
- end
103
+ def error(message)
104
+ warn(message)
105
+ exit 1
106
+ end
104
107
 
105
- def error(message)
106
- warn(message)
107
- exit 1
108
- end
108
+ def puts(message)
109
+ stdout.puts("\e[2K#{message}")
110
+ end
109
111
 
110
- def puts(message)
111
- stdout.puts("\e[2K#{message}")
112
- end
112
+ def print(message)
113
+ stdout.print(message)
114
+ end
113
115
 
114
- def print(message)
115
- stdout.print(message)
116
- end
116
+ def newline
117
+ stdout.puts('')
118
+ end
117
119
 
118
- def newline
119
- stdout.puts('')
120
- end
120
+ def pwd
121
+ @pwd ||= Pathname.new(Dir.pwd + '/')
122
+ end
121
123
 
122
- def pwd
123
- @pwd ||= Pathname.new(Dir.pwd + '/')
124
- end
124
+ def exit(status = 0)
125
+ throw :leftovers_exit, status
126
+ end
125
127
 
126
- def exit(status = 0)
127
- throw :leftovers_exit, status
128
- end
128
+ def try_require(requirable, message: nil)
129
+ warn message if !try_require_cache(requirable) && message
130
+ try_require_cache(requirable)
131
+ end
129
132
 
130
- def try_require(requirable, message: nil) # rubocop:disable Metrics/MethodLength
131
- @try_require ||= {}
133
+ def each_or_self(value, &block)
134
+ return enum_for(__method__, value) unless block
132
135
 
133
- @try_require[requirable] = begin
134
- if @try_require.key?(requirable)
135
- @try_require[requirable]
136
- else
137
- require requirable
138
- true
136
+ case value
137
+ when nil then nil
138
+ when Array then value.each(&block)
139
+ else yield(value)
139
140
  end
140
- rescue LoadError
141
- false
142
141
  end
143
142
 
144
- warn message if !@try_require[requirable] && message
145
- @try_require[requirable]
146
- end
143
+ private
147
144
 
148
- def each_or_self(value, &block)
149
- case value
150
- when nil then nil
151
- when Array then value.each(&block)
152
- else yield(value)
145
+ def try_require_cache(requirable)
146
+ @try_require_cache ||= {}
147
+
148
+ @try_require_cache.fetch(requirable) do
149
+ begin
150
+ require requirable
151
+ @try_require_cache[requirable] = true
152
+ rescue LoadError
153
+ @try_require_cache[requirable] = false
154
+ end
155
+ end
153
156
  end
154
157
  end
155
158
  end