leftovers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.leftovers.yml +19 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +216 -0
  6. data/.ruby-version +1 -0
  7. data/.spellr.yml +13 -0
  8. data/.spellr_wordlists/english.txt +21 -0
  9. data/.spellr_wordlists/lorem.txt +1 -0
  10. data/.spellr_wordlists/ruby.txt +67 -0
  11. data/.spellr_wordlists/shell.txt +3 -0
  12. data/.travis.yml +10 -0
  13. data/Configuration.md +427 -0
  14. data/Gemfile +6 -0
  15. data/LICENSE.txt +21 -0
  16. data/README.md +172 -0
  17. data/Rakefile +16 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/exe/leftovers +6 -0
  21. data/leftovers.gemspec +42 -0
  22. data/lib/config/attr_encrypted.yml +16 -0
  23. data/lib/config/builder.yml +3 -0
  24. data/lib/config/capistrano.yml +3 -0
  25. data/lib/config/datagrid.yml +9 -0
  26. data/lib/config/flipper.yml +3 -0
  27. data/lib/config/graphql.yml +20 -0
  28. data/lib/config/guard.yml +3 -0
  29. data/lib/config/haml.yml +2 -0
  30. data/lib/config/jbuilder.yml +2 -0
  31. data/lib/config/okcomputer.yml +3 -0
  32. data/lib/config/parser.yml +91 -0
  33. data/lib/config/pry.yml +3 -0
  34. data/lib/config/rack.yml +2 -0
  35. data/lib/config/rails.yml +387 -0
  36. data/lib/config/rake.yml +5 -0
  37. data/lib/config/redcarpet.yml +38 -0
  38. data/lib/config/rollbar.yml +3 -0
  39. data/lib/config/rspec.yml +56 -0
  40. data/lib/config/ruby.yml +77 -0
  41. data/lib/config/selenium.yml +21 -0
  42. data/lib/config/simplecov.yml +2 -0
  43. data/lib/config/will_paginate.yml +14 -0
  44. data/lib/leftovers/argument_rule.rb +216 -0
  45. data/lib/leftovers/backports.rb +56 -0
  46. data/lib/leftovers/cli.rb +50 -0
  47. data/lib/leftovers/collector.rb +67 -0
  48. data/lib/leftovers/config.rb +53 -0
  49. data/lib/leftovers/core_ext.rb +32 -0
  50. data/lib/leftovers/definition.rb +70 -0
  51. data/lib/leftovers/definition_set.rb +44 -0
  52. data/lib/leftovers/erb.rb +20 -0
  53. data/lib/leftovers/file.rb +30 -0
  54. data/lib/leftovers/file_collector.rb +219 -0
  55. data/lib/leftovers/file_list.rb +24 -0
  56. data/lib/leftovers/haml.rb +24 -0
  57. data/lib/leftovers/hash_rule.rb +40 -0
  58. data/lib/leftovers/merged_config.rb +71 -0
  59. data/lib/leftovers/name_rule.rb +53 -0
  60. data/lib/leftovers/node.rb +182 -0
  61. data/lib/leftovers/rake_task.rb +62 -0
  62. data/lib/leftovers/reporter.rb +11 -0
  63. data/lib/leftovers/rule.rb +74 -0
  64. data/lib/leftovers/transform_rule.rb +171 -0
  65. data/lib/leftovers/value_rule.rb +56 -0
  66. data/lib/leftovers/version.rb +5 -0
  67. data/lib/leftovers.rb +127 -0
  68. metadata +281 -0
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser'
4
+
5
+ module Parser
6
+ module AST
7
+ class Node # rubocop:disable Metrics/ClassLength
8
+ def initialize(type, children = [], properties = {})
9
+ # ::AST::Node#initialize freezes itself.
10
+ # so can't use normal memoizations
11
+ @memo = {}
12
+
13
+ super
14
+ end
15
+
16
+ def first
17
+ children.first
18
+ end
19
+
20
+ def file
21
+ @memo[:file]
22
+ end
23
+
24
+ def file=(value)
25
+ @memo[:file] = value
26
+ end
27
+
28
+ def test
29
+ @memo[:test]
30
+ end
31
+ alias_method :test?, :test
32
+
33
+ def test=(value)
34
+ @memo[:test] = value
35
+ end
36
+
37
+ def to_scalar_value # rubocop:disable Metrics/MethodLength
38
+ @memo[:scalar_value] ||= case type
39
+ when :sym
40
+ first
41
+ when :str
42
+ first.to_s.freeze
43
+ when :true
44
+ true
45
+ when :false
46
+ false
47
+ when :nil
48
+ nil
49
+ else
50
+ raise "Not scalar node, (#{type})"
51
+ end
52
+ end
53
+
54
+ def to_s # rubocop:disable Metrics/MethodLength
55
+ @memo[:to_s] ||= if scalar?
56
+ to_scalar_value
57
+ elsif named?
58
+ name
59
+ else
60
+ raise "No to_s, (#{type})"
61
+ end.to_s.freeze
62
+ end
63
+
64
+ def to_sym
65
+ case type
66
+ when :str, :sym then first
67
+ when :nil, :true, :false then type
68
+ else to_s.to_sym
69
+ end
70
+ end
71
+
72
+ SCALAR_TYPES = %i{sym str true false nil}.freeze
73
+ def scalar?
74
+ SCALAR_TYPES.include?(type)
75
+ end
76
+
77
+ def string_or_symbol?
78
+ type == :str || type == :sym
79
+ end
80
+
81
+ def send?
82
+ type == :send || type == :csend
83
+ end
84
+
85
+ def named?
86
+ send? || type == :casgn
87
+ end
88
+
89
+ def arguments
90
+ @memo[:arguments] ||= case type
91
+ when :send, :csend then children.drop(2)
92
+ when :casgn then [children[2]]
93
+ else raise "Not argument node (#{type})"
94
+ end
95
+ end
96
+
97
+ def positional_arguments
98
+ @memo[:positional_arguments] ||= kwargs ? arguments[0...-1] : arguments
99
+ end
100
+
101
+ def unwrap_freeze
102
+ return self unless type == :send && name == :freeze
103
+
104
+ first
105
+ end
106
+
107
+ def kwargs
108
+ @memo.fetch(:kwargs) do
109
+ last_arg = arguments[-1]&.unwrap_freeze
110
+ @memo[:kwargs] = (last_arg if last_arg&.type == :hash)
111
+ end
112
+ end
113
+
114
+ def keys
115
+ each_pair.map { |k, _| k }
116
+ end
117
+
118
+ def key?(key)
119
+ each_pair.find do |k, _v|
120
+ next unless k.string_or_symbol?
121
+
122
+ k.to_sym == key
123
+ end
124
+ end
125
+
126
+ def values
127
+ @memo[:kwargs] ||= case type
128
+ when :hash then each_pair.map { |_, v| v }
129
+ when :array then children
130
+ else []
131
+ end
132
+ end
133
+
134
+ def values_at_match(matcher)
135
+ each_pair.with_object([]) do |(key, value), values|
136
+ values << value if matcher.match?(key.to_sym, key.to_s)
137
+ end
138
+ end
139
+
140
+ def positional_arguments_at(positions)
141
+ positional_arguments.values_at(*positions).compact
142
+ end
143
+
144
+ def each_pair
145
+ raise "not hash node (#{type})" unless type == :hash
146
+
147
+ return enum_for(:each_pair) unless block_given?
148
+
149
+ children.each do |pair|
150
+ yield(*pair.children) if pair.type == :pair
151
+ end
152
+ end
153
+
154
+ def name
155
+ return "Not named node (#{type})" unless named?
156
+
157
+ @memo[:name] ||= children[1]
158
+ end
159
+
160
+ def name_s
161
+ @memo[:name_s] ||= name.to_s.freeze
162
+ end
163
+
164
+ def [](index) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
165
+ case type
166
+ when :send, :csend
167
+ index.is_a?(Integer) ? arguments[index] : kwargs && kwargs[index]
168
+ when :array
169
+ children[index]
170
+ when :hash
171
+ each_pair do |key, value|
172
+ next unless key.string_or_symbol?
173
+
174
+ return value if key.to_sym == index
175
+ end
176
+
177
+ nil
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+ require 'shellwords'
5
+ require_relative 'cli'
6
+
7
+ module Leftovers
8
+ class RakeTask
9
+ include ::Rake::DSL
10
+
11
+ def self.generate_task(name = :leftovers, *default_argv) # rubocop:disable
12
+ new(name, default_argv)
13
+
14
+ name
15
+ end
16
+
17
+ def initialize(name, default_argv)
18
+ @name = name
19
+ @default_argv = default_argv
20
+
21
+ describe_task
22
+ define_task
23
+ end
24
+
25
+ private
26
+
27
+ def escaped_argv(argv = @default_argv)
28
+ return if argv.empty?
29
+
30
+ Shellwords.shelljoin(argv)
31
+ end
32
+
33
+ def describe_task
34
+ return desc('Run leftovers') if @default_argv.empty?
35
+
36
+ desc("Run leftovers (default args: #{escaped_argv})")
37
+ end
38
+
39
+ def define_task
40
+ task(@name, :'*args') do |_, task_argv|
41
+ argv = argv_or_default(task_argv)
42
+ write_cli_cmd(argv)
43
+ run(argv)
44
+ end
45
+ end
46
+
47
+ def write_cli_cmd(argv)
48
+ $stdout.puts("\e[2mleftovers #{escaped_argv(argv)}\e[0m")
49
+ end
50
+
51
+ def run(argv)
52
+ Leftovers::CLI.new(argv: argv, stdout: $stdout, stderr: $stderr)
53
+ rescue SystemExit => e
54
+ raise unless e.status == 0
55
+ end
56
+
57
+ def argv_or_default(task_argv)
58
+ task_argv = task_argv.to_a.compact
59
+ task_argv.empty? ? @default_argv : task_argv
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class Reporter
5
+ def call(definition)
6
+ Leftovers.puts(
7
+ "\e[36m#{definition.full_location}\e[0m #{definition.full_name} \e[2m#{definition.highlighted_source("\e[33m", "\e[0;2m")}\e[0m" # rubocop:disable Layout/LineLength
8
+ )
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'name_rule'
4
+ require_relative 'argument_rule'
5
+ require 'fast_ignore'
6
+
7
+ module Leftovers
8
+ class Rule
9
+ def self.wrap(rules)
10
+ case rules
11
+ when Array then rules.flat_map { |r| wrap(r) }
12
+ when nil then [].freeze
13
+ else new(**rules)
14
+ end
15
+ end
16
+
17
+ attr_reader :skip
18
+ alias_method :skip?, :skip
19
+
20
+ def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
21
+ name: nil,
22
+ names: nil,
23
+ calls: nil,
24
+ call: nil,
25
+ skip: false,
26
+ defines: nil,
27
+ define: nil,
28
+ path: nil,
29
+ paths: nil
30
+ )
31
+ raise ArgumentError, 'Only use one of name/names' if name && names
32
+ raise ArgumentError, 'Only use one of path/paths' if path && paths
33
+ raise ArgumentError, 'Only use one of call/calls' if call && calls
34
+ raise ArgumentError, 'Only use one of define/defines' if define && defines
35
+ if skip && (defines || calls)
36
+ raise ArgumentError, "skip can't exist with defines or calls for #{name || names}"
37
+ end
38
+
39
+ @name_matcher = NameRule.new(name || names)
40
+ @path = FastIgnore.new(include_rules: path || paths, gitignore: false) if path || paths
41
+ @skip = skip
42
+
43
+ begin
44
+ @calls = ArgumentRule.wrap(calls)
45
+ rescue ArgumentError => e
46
+ raise e, "#{e.message} for calls for #{name}", e.backtrace
47
+ end
48
+
49
+ begin
50
+ @defines = ArgumentRule.wrap(defines, definer: true)
51
+ rescue ArgumentError => e
52
+ raise e, "#{e.message} for defines for #{name}", e.backtrace
53
+ end
54
+ end
55
+
56
+ def filename?(file)
57
+ return true unless @path
58
+
59
+ @path.allowed?(file)
60
+ end
61
+
62
+ def match?(name, name_s, file)
63
+ @name_matcher.match?(name, name_s) && filename?(file)
64
+ end
65
+
66
+ def calls(node)
67
+ @calls.flat_map { |m| m.matches(node) }
68
+ end
69
+
70
+ def definitions(node)
71
+ @defines.flat_map { |m| m.matches(node) }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class TransformRule # rubocop:disable Metrics/ClassLength
5
+ # :nocov:
6
+ using ::Leftovers::StringDeletePrefixSuffix if defined?(::Leftovers::StringDeletePrefixSuffix)
7
+ # :nocov:
8
+
9
+ RUBY_STRING_METHODS = %i{
10
+ downcase
11
+ upcase
12
+ capitalize
13
+ swapcase
14
+ }.freeze
15
+
16
+ ACTIVESUPPORT_STRING_METHODS = %i{
17
+ pluralize
18
+ singularize
19
+ camelize
20
+ camelcase
21
+ underscore
22
+ titleize
23
+ titlecase
24
+ demodulize
25
+ deconstantize
26
+ parameterize
27
+ }.freeze
28
+
29
+ CUSTOM_TRANSFORMS = %i{
30
+ original
31
+ delete_before
32
+ delete_after
33
+ add_prefix
34
+ add_suffix
35
+ delete_prefix
36
+ delete_suffix
37
+ replace_with
38
+ }.freeze
39
+
40
+ VALID_TRANSFORMS = RUBY_STRING_METHODS + ACTIVESUPPORT_STRING_METHODS + CUSTOM_TRANSFORMS
41
+ # more possible transformations
42
+ # gsub sub tr tr_s
43
+ def initialize(transforms)
44
+ @transforms = prepare_transforms(transforms)
45
+
46
+ freeze
47
+ end
48
+
49
+ def transform(original_string, method_node)
50
+ string = original_string
51
+ @transforms.each { |proc| string = proc.call(string, method_node) }
52
+
53
+ string.to_sym
54
+ end
55
+
56
+ private
57
+
58
+ def prepare_transforms(transforms) # rubocop:disable Metrics/MethodLength
59
+ transforms.map do |key, value|
60
+ unless VALID_TRANSFORMS.include?(key)
61
+ raise ArgumentError, <<~MESSAGE
62
+ invalid transform key: (#{key}: #{value}).
63
+ Valid transform keys are #{ALL_VALID_TRANSFORMS}"
64
+ MESSAGE
65
+ end
66
+
67
+ key, value = prepare_hash_value(key, value) if value.is_a?(Hash)
68
+
69
+ instance_variable_set(:"@#{key}", value.freeze)
70
+ method(key)
71
+ end
72
+ end
73
+
74
+ HASH_VALUE_TRANSFORMS = %i{add_prefix add_suffix}.freeze
75
+ HASH_VALUE_KEYS = %i{from_argument joiner}.freeze
76
+ def prepare_hash_value(method, hash) # rubocop:disable Metrics/MethodLength
77
+ raise ArgumentError, <<~MESSAGE unless HASH_VALUE_TRANSFORMS.include?(method)
78
+ invalid transform value (#{key}: #{value}).
79
+ Hash values are only valid for #{HASH_VALUE_TRANSFORMS}
80
+ MESSAGE
81
+
82
+ hash = hash.map do |k, v|
83
+ raise ArgumentError, <<~MESSAGE unless HASH_VALUE_KEYS.include?(k)
84
+ invalid transform value argument (#{method}: { #{k}: #{v} }).
85
+ Hash values are only valid for #{HASH_VALUE_TRANSFORMS}
86
+ MESSAGE
87
+
88
+ [k, v.to_sym]
89
+ end.to_h
90
+
91
+ ["#{method}_dynamic", hash]
92
+ end
93
+
94
+ RUBY_STRING_METHODS.each do |method|
95
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
96
+ def #{method}(string, _method_node)
97
+ string.#{method}
98
+ end
99
+ RUBY
100
+ end
101
+
102
+ # leftovers:call activesupport_available?
103
+ ACTIVESUPPORT_STRING_METHODS.each do |method|
104
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
105
+ def #{method}(string, _method_node)
106
+ return string unless activesupport_available?(:#{method})
107
+
108
+ string.#{method}
109
+ end
110
+ RUBY
111
+ end
112
+
113
+ def original(string, _method_node)
114
+ string
115
+ end
116
+
117
+ def delete_prefix(string, _method_node)
118
+ string.delete_prefix(@delete_prefix)
119
+ end
120
+
121
+ def delete_suffix(string, _method_node)
122
+ string.delete_suffix(@delete_suffix)
123
+ end
124
+
125
+ def delete_before(string, _method_node)
126
+ string.split(@delete_before, 2)[1] || string
127
+ end
128
+
129
+ def delete_after(string, _method_node)
130
+ string.split(@delete_after, 2).first
131
+ end
132
+
133
+ def replace_with(_string, _method_node)
134
+ @replace_with
135
+ end
136
+
137
+ def add_prefix(string, _method_node)
138
+ "#{@add_prefix}#{string}"
139
+ end
140
+
141
+ def add_suffix(string, _method_node)
142
+ "#{string}#{@add_suffix}"
143
+ end
144
+
145
+ def add_prefix_dynamic(string, method_node)
146
+ "#{dynamic_value(@add_prefix_dynamic, method_node)}#{@add_prefix_dynamic[:joiner]}#{string}"
147
+ end
148
+
149
+ def add_suffix_dynamic(string, method_node)
150
+ "#{string}#{@add_suffix_dynamic[:joiner]}#{dynamic_value(@add_suffix_dynamic, method_node)}"
151
+ end
152
+
153
+ def dynamic_value(value, method_node)
154
+ method_node.kwargs[value[:from_argument]].to_s if value[:from_argument]
155
+ end
156
+
157
+ def activesupport_available?(method) # rubocop:disable Metrics/MethodLength
158
+ Leftovers.try_require(
159
+ 'active_support/core_ext/string', 'active_support/inflections',
160
+ message: <<~MESSAGE
161
+ Tried transforming a string using an activesupport method (#{method}), but the activesupport gem was not available
162
+ `gem install activesupport`
163
+ MESSAGE
164
+ )
165
+
166
+ Leftovers.try_require(::File.join(Leftovers.pwd, 'config', 'initializers', 'inflections.rb'))
167
+
168
+ defined?(ActiveSupport)
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ValueRule
5
+ def initialize(values) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
6
+ literal_values = Set.new
7
+ value_types = Set.new
8
+
9
+ Array.each_or_self(values) do |value|
10
+ case value
11
+ when Hash
12
+ raise ArgumentError, "invalid value #{value.inspect}" unless value[:type]
13
+
14
+ value_types.merge(
15
+ Array(value[:type]).map do |v|
16
+ case v
17
+ when 'String', :String then :str
18
+ when 'Symbol', :Symbol then :sym
19
+ else v.to_s.downcase.to_sym
20
+ end
21
+ end
22
+ )
23
+ else
24
+ (literal_values << value) if value
25
+ end
26
+ end
27
+
28
+ if literal_values.length <= 1
29
+ @literal_value = literal_values.first
30
+ @literal_values = nil
31
+ else
32
+ @literal_value = nil
33
+ @literal_values = literal_values
34
+ end
35
+
36
+ if value_types.length <= 1
37
+ @value_type = value_types.first
38
+ @value_types = nil
39
+ else
40
+ @value_type = nil
41
+ @value_types = value_types
42
+ end
43
+
44
+ freeze
45
+ end
46
+
47
+ def match?(value_node) # rubocop:disable Metrics/CyclomaticComplexity
48
+ return true if @value_type&.== value_node.type
49
+ return true if @value_types&.include?(value_node.type)
50
+ return false unless (@literal_value || @literal_values) && value_node.scalar?
51
+ return true if @literal_value&.== value_node.to_scalar_value
52
+
53
+ @literal_values&.include?(value_node.to_scalar_value)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ VERSION = '0.1.0'
5
+ end
data/lib/leftovers.rb ADDED
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './leftovers/core_ext'
4
+ require_relative './leftovers/backports'
5
+ require_relative './leftovers/collector'
6
+ require_relative './leftovers/merged_config'
7
+ require_relative './leftovers/reporter'
8
+
9
+ module Leftovers
10
+ module_function
11
+
12
+ class << self
13
+ attr_accessor :parallel
14
+ alias_method :parallel?, :parallel
15
+
16
+ attr_accessor :quiet
17
+ alias_method :quiet?, :quiet
18
+
19
+ attr_accessor :progress
20
+ alias_method :progress?, :progress
21
+ end
22
+
23
+ def stdout
24
+ @stdout ||= StringIO.new
25
+ end
26
+
27
+ def stderr
28
+ @stderr ||= StringIO.new
29
+ end
30
+
31
+ def config
32
+ @config ||= Leftovers::MergedConfig.new
33
+ end
34
+
35
+ def collector
36
+ @collector ||= Leftovers::Collector.new
37
+ end
38
+
39
+ def reporter
40
+ @reporter ||= Leftovers::Reporter.new
41
+ end
42
+
43
+ def leftovers # rubocop:disable Metrics/MethodLength
44
+ @leftovers ||= begin
45
+ collector.collect
46
+ collector.definitions.reject do |definition|
47
+ definition.skipped? || definition.in_collection?
48
+ end
49
+ end
50
+ end
51
+
52
+ def run(stdout: StringIO.new, stderr: StringIO.new) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
53
+ reset
54
+ @stdout = stdout
55
+ @stderr = stderr
56
+ return 0 if leftovers.empty?
57
+
58
+ only_test = []
59
+ none = []
60
+ leftovers.sort.each do |definition|
61
+ if !definition.test? && definition.in_test_collection?
62
+ only_test << definition
63
+ else
64
+ none << definition
65
+ end
66
+ end
67
+
68
+ unless only_test.empty?
69
+ puts "\e[31mOnly directly called in tests:\e[0m"
70
+ only_test.each { |definition| reporter.call(definition) }
71
+ end
72
+
73
+ unless none.empty?
74
+ puts "\e[31mNot directly called at all:\e[0m"
75
+ none.each { |definition| reporter.call(definition) }
76
+ end
77
+
78
+ 1
79
+ end
80
+
81
+ def reset # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
82
+ remove_instance_variable(:@config) if defined?(@config)
83
+ remove_instance_variable(:@collector) if defined?(@collector)
84
+ remove_instance_variable(:@reporter) if defined?(@reporter)
85
+ remove_instance_variable(:@leftovers) if defined?(@leftovers)
86
+ remove_instance_variable(:@try_require) if defined?(@try_require)
87
+ remove_instance_variable(:@stdout) if defined?(@stdout)
88
+ remove_instance_variable(:@stderr) if defined?(@stderr)
89
+ remove_instance_variable(:@parallel) if defined?(@parallel)
90
+ remove_instance_variable(:@quiet) if defined?(@quiet)
91
+ remove_instance_variable(:@pwd) if defined?(@pwd)
92
+ end
93
+
94
+ def warn(message)
95
+ stderr.puts("\e[2K#{message}") unless quiet?
96
+ end
97
+
98
+ def puts(message)
99
+ stdout.puts("\e[2K#{message}") unless quiet?
100
+ end
101
+
102
+ def print(message)
103
+ stdout.print(message) unless quiet?
104
+ end
105
+
106
+ def newline
107
+ stdout.puts('')
108
+ end
109
+
110
+ def pwd
111
+ @pwd ||= Pathname.new(Dir.pwd + '/')
112
+ end
113
+
114
+ def try_require(*requirables, message: nil) # rubocop:disable Metrics/MethodLength
115
+ @try_require ||= {}
116
+ requirables.each do |requirable|
117
+ begin
118
+ return @try_require[requirable] if @try_require.key?(requirable)
119
+
120
+ @try_require[requirable] = require requirable
121
+ rescue LoadError
122
+ warn message if message
123
+ @try_require[requirable] = false
124
+ end
125
+ end
126
+ end
127
+ end