leftovers 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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