leftovers 0.5.2 → 0.6.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 (63) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +23 -3
  4. data/docs/Configuration.md +119 -2
  5. data/leftovers.gemspec +8 -4
  6. data/lib/.DS_Store +0 -0
  7. data/lib/config/actionpack.yml +11 -0
  8. data/lib/config/activesupport.yml +1 -0
  9. data/lib/config/ruby.yml +10 -0
  10. data/lib/config/slim.yml +4 -0
  11. data/lib/leftovers/collector.rb +3 -1
  12. data/lib/leftovers/config.rb +12 -4
  13. data/lib/leftovers/config_validator/schema_hash.rb +171 -149
  14. data/lib/leftovers/config_validator.rb +1 -0
  15. data/lib/leftovers/definition.rb +1 -1
  16. data/lib/leftovers/dynamic_processors/call.rb +0 -4
  17. data/lib/leftovers/dynamic_processors/call_definition.rb +0 -4
  18. data/lib/leftovers/dynamic_processors/definition.rb +0 -4
  19. data/lib/leftovers/erb.rb +1 -1
  20. data/lib/leftovers/file.rb +53 -5
  21. data/lib/leftovers/file_collector.rb +33 -2
  22. data/lib/leftovers/json.rb +28 -0
  23. data/lib/leftovers/matcher_builders/document.rb +13 -0
  24. data/lib/leftovers/matcher_builders/node.rb +3 -1
  25. data/lib/leftovers/matcher_builders/node_has_argument.rb +3 -4
  26. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +1 -1
  27. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +1 -1
  28. data/lib/leftovers/matcher_builders.rb +1 -0
  29. data/lib/leftovers/matchers/all.rb +0 -4
  30. data/lib/leftovers/matchers/and.rb +0 -4
  31. data/lib/leftovers/matchers/any.rb +0 -4
  32. data/lib/leftovers/matchers/node_has_any_keyword_argument.rb +1 -7
  33. data/lib/leftovers/matchers/node_has_any_positional_argument_with_value.rb +1 -7
  34. data/lib/leftovers/matchers/node_has_positional_argument_with_value.rb +0 -4
  35. data/lib/leftovers/matchers/node_name.rb +0 -4
  36. data/lib/leftovers/matchers/node_pair_value.rb +0 -4
  37. data/lib/leftovers/matchers/node_path.rb +0 -4
  38. data/lib/leftovers/matchers/node_scalar_value.rb +0 -4
  39. data/lib/leftovers/matchers/node_type.rb +0 -4
  40. data/lib/leftovers/matchers/not.rb +0 -4
  41. data/lib/leftovers/matchers/or.rb +0 -4
  42. data/lib/leftovers/merged_config.rb +43 -9
  43. data/lib/leftovers/processor_builders/dynamic.rb +2 -1
  44. data/lib/leftovers/slim.rb +21 -0
  45. data/lib/leftovers/todo_reporter.rb +10 -35
  46. data/lib/leftovers/value_processors/camelize.rb +1 -1
  47. data/lib/leftovers/value_processors/deconstantize.rb +1 -1
  48. data/lib/leftovers/value_processors/delete_prefix.rb +0 -6
  49. data/lib/leftovers/value_processors/delete_suffix.rb +0 -6
  50. data/lib/leftovers/value_processors/demodulize.rb +1 -1
  51. data/lib/leftovers/value_processors/keyword.rb +0 -4
  52. data/lib/leftovers/value_processors/keyword_argument.rb +0 -4
  53. data/lib/leftovers/value_processors/parameterize.rb +1 -1
  54. data/lib/leftovers/value_processors/pluralize.rb +1 -1
  55. data/lib/leftovers/value_processors/return_definition.rb +0 -4
  56. data/lib/leftovers/value_processors/singularize.rb +1 -1
  57. data/lib/leftovers/value_processors/titleize.rb +1 -1
  58. data/lib/leftovers/value_processors/underscore.rb +1 -1
  59. data/lib/leftovers/version.rb +1 -1
  60. data/lib/leftovers/yaml.rb +73 -0
  61. data/lib/leftovers.rb +8 -8
  62. metadata +51 -18
  63. data/lib/leftovers/backports.rb +0 -40
@@ -10,7 +10,7 @@ module Leftovers
10
10
  ::Leftovers::MatcherBuilders::NodeName.build(pat)
11
11
  when ::Hash
12
12
  build_from_hash(**pat)
13
- # :nocov:
13
+ # :nocov:
14
14
  else raise
15
15
  # :nocov:
16
16
  end
@@ -19,6 +19,7 @@ module Leftovers
19
19
 
20
20
  def self.build_from_hash( # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
21
21
  names: nil, match: nil, has_prefix: nil, has_suffix: nil,
22
+ document: false,
22
23
  paths: nil,
23
24
  has_arguments: nil,
24
25
  has_receiver: nil,
@@ -29,6 +30,7 @@ module Leftovers
29
30
  names,
30
31
  { match: match, has_prefix: has_prefix, has_suffix: has_suffix }.compact
31
32
  ]),
33
+ ::Leftovers::MatcherBuilders::Document.build(document),
32
34
  ::Leftovers::MatcherBuilders::NodePath.build(paths),
33
35
  ::Leftovers::MatcherBuilders::NodeHasArgument.build(has_arguments),
34
36
  ::Leftovers::MatcherBuilders::NodeHasReceiver.build(has_receiver),
@@ -26,12 +26,10 @@ module Leftovers
26
26
 
27
27
  ::Leftovers.each_or_self(at) do |k|
28
28
  case k
29
- when '*'
29
+ when '*', ::Integer
30
30
  positions << k
31
31
  when ::String, ::Hash
32
32
  keys << k
33
- when ::Integer
34
- positions << k
35
33
  # :nocov:
36
34
  else raise
37
35
  # :nocov:
@@ -62,7 +60,8 @@ module Leftovers
62
60
  elsif positions
63
61
  ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher)
64
62
  # :nocov:
65
- else raise
63
+ else
64
+ raise
66
65
  # :nocov:
67
66
  end
68
67
 
@@ -6,7 +6,7 @@ module Leftovers
6
6
  class << self
7
7
  def build(keywords, value_matcher) # rubocop:disable Metrics/MethodLength
8
8
  value_matcher = ::Leftovers::MatcherBuilders::NodePairValue.build(value_matcher)
9
- keyword_matcher = if ::Leftovers.each_or_self(keywords).any? { |x| x == '**' }
9
+ keyword_matcher = if ::Leftovers.each_or_self(keywords).include?('**')
10
10
  ::Leftovers::Matchers::NodeType.new(:pair)
11
11
  else
12
12
  ::Leftovers::MatcherBuilders::NodePairName.build(keywords)
@@ -14,7 +14,7 @@ module Leftovers
14
14
  end
15
15
  end
16
16
  elsif positions
17
- pos = 0 if ::Leftovers.each_or_self(positions).any? { |x| x == '*' }
17
+ pos = 0 if ::Leftovers.each_or_self(positions).include?('*')
18
18
  pos ||= ::Leftovers.each_or_self(positions).min
19
19
 
20
20
  ::Leftovers::Matchers::NodeHasPositionalArgument.new(pos)
@@ -4,6 +4,7 @@ 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(:Document, "#{__dir__}/matcher_builders/document")
7
8
  autoload(:Name, "#{__dir__}/matcher_builders/name")
8
9
  autoload(:Node, "#{__dir__}/matcher_builders/node")
9
10
  autoload(:NodeHasArgument, "#{__dir__}/matcher_builders/node_has_argument")
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class All
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(matchers)
11
7
  @matchers = matchers
12
8
 
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class And
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(lhs, rhs)
11
7
  @lhs = lhs
12
8
  @rhs = rhs
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class Any
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  attr_reader :matchers
11
7
 
12
8
  def initialize(matchers)
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeHasAnyKeywordArgument
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(pair_matcher)
11
7
  @pair_matcher = pair_matcher
12
8
 
@@ -17,9 +13,7 @@ module Leftovers
17
13
  kwargs = node.kwargs
18
14
  return false unless kwargs
19
15
 
20
- kwargs.children.any? do |pair|
21
- @pair_matcher === pair
22
- end
16
+ kwargs.children.any?(@pair_matcher)
23
17
  end
24
18
 
25
19
  freeze
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeHasAnyPositionalArgumentWithValue
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(matcher)
11
7
  @matcher = matcher
12
8
 
@@ -17,9 +13,7 @@ module Leftovers
17
13
  args = node.positional_arguments
18
14
  return false unless args
19
15
 
20
- args.any? do |value|
21
- @matcher === value
22
- end
16
+ args.any?(@matcher)
23
17
  end
24
18
 
25
19
  freeze
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeHasPositionalArgumentWithValue
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(position, matcher)
11
7
  @position = position
12
8
  @matcher = matcher
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeName
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(name_matcher)
11
7
  @name_matcher = name_matcher
12
8
 
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodePairValue
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(value_matcher)
11
7
  @value_matcher = value_matcher
12
8
 
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodePath
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(path_matcher)
11
7
  @path_matcher = path_matcher
12
8
 
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeScalarValue
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(matcher)
11
7
  @matcher = matcher
12
8
 
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class NodeType
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(type_matcher)
11
7
  @type_matcher = type_matcher
12
8
 
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class Not
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(matcher)
11
7
  @matcher = matcher
12
8
 
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module Matchers
5
5
  class Or
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  attr_reader :lhs, :rhs
11
7
 
12
8
  def initialize(lhs, rhs)
@@ -4,7 +4,7 @@ require 'set'
4
4
  require 'fast_ignore'
5
5
 
6
6
  module Leftovers
7
- class MergedConfig
7
+ class MergedConfig # rubocop:disable Metrics/ClassLength
8
8
  def initialize(load_defaults: false)
9
9
  @configs = []
10
10
  @loaded_configs = Set.new
@@ -37,14 +37,24 @@ module Leftovers
37
37
  Leftovers::Config.new(:'.leftovers_todo.yml', path: Leftovers.pwd + '.leftovers_todo.yml')
38
38
  end
39
39
 
40
- def unmemoize # rubocop:disable Metrics/CyclomaticComplexity
41
- remove_instance_variable(:@exclude_paths) if defined?(@exclude_paths)
42
- remove_instance_variable(:@include_paths) if defined?(@include_paths)
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)
46
- remove_instance_variable(:@dynamic) if defined?(@dynamic)
47
- remove_instance_variable(:@keep) if defined?(@keep)
40
+ MEMOIZED_IVARS = %i{
41
+ @exclude_paths
42
+ @include_paths
43
+ @test_paths
44
+ @haml_paths
45
+ @slim_paths
46
+ @yaml_paths
47
+ @json_paths
48
+ @erb_paths
49
+ @dynamic
50
+ @keep
51
+ @test_only
52
+ }.freeze
53
+
54
+ def unmemoize
55
+ MEMOIZED_IVARS.each do |ivar|
56
+ remove_instance_variable(ivar) if instance_variable_get(ivar)
57
+ end
48
58
  end
49
59
 
50
60
  def exclude_paths
@@ -71,6 +81,30 @@ module Leftovers
71
81
  )
72
82
  end
73
83
 
84
+ def slim_paths
85
+ @slim_paths ||= FastIgnore.new(
86
+ include_rules: @configs.flat_map(&:slim_paths),
87
+ gitignore: false,
88
+ root: Leftovers.pwd
89
+ )
90
+ end
91
+
92
+ def yaml_paths
93
+ @yaml_paths ||= FastIgnore.new(
94
+ include_rules: @configs.flat_map(&:yaml_paths),
95
+ gitignore: false,
96
+ root: Leftovers.pwd
97
+ )
98
+ end
99
+
100
+ def json_paths
101
+ @json_paths ||= FastIgnore.new(
102
+ include_rules: @configs.flat_map(&:json_paths),
103
+ gitignore: false,
104
+ root: Leftovers.pwd
105
+ )
106
+ end
107
+
74
108
  def erb_paths
75
109
  @erb_paths ||= FastIgnore.new(
76
110
  include_rules: @configs.flat_map(&:erb_paths),
@@ -17,7 +17,8 @@ module Leftovers
17
17
  elsif call
18
18
  ::Leftovers::DynamicProcessors::Call.new(matcher, call)
19
19
  # :nocov:
20
- else raise
20
+ else
21
+ raise
21
22
  # :nocov:
22
23
  end
23
24
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ module Slim
5
+ module_function
6
+
7
+ def precompile(file, name)
8
+ return '' unless Leftovers.try_require('slim', message: <<~MESSAGE) # rubocop:disable Layout/EmptyLineAfterGuardClause
9
+ Skipped parsing #{name.relative_path}, because the slim gem was not available
10
+ `gem install slim`
11
+ MESSAGE
12
+
13
+ begin
14
+ ::Slim::Engine.new(file: file).call(file)
15
+ rescue ::Slim::Parser::SyntaxError => e
16
+ Leftovers.warn "#{e.class}: \"#{e.error}\" #{name.relative_path}:#{e.lineno}:#{e.column}"
17
+ ''
18
+ end
19
+ end
20
+ end
21
+ end
@@ -64,54 +64,29 @@ module Leftovers
64
64
  end
65
65
 
66
66
  def todo_data(only_test, none)
67
- none_test = none.select(&:test?)
68
- none_non_test = none.reject(&:test?)
69
67
  [
70
- test_only_data(none_test),
71
- keep_data(only_test, none_non_test)
68
+ list_data(
69
+ :test_only, 'Only directly called in tests', only_test
70
+ ),
71
+ list_data(
72
+ :keep, 'Not directly called at all', none
73
+ )
72
74
  ].compact.join
73
75
  end
74
76
 
75
- def test_only_data(list)
77
+ def list_data(key, title, list)
76
78
  return if list.empty?
77
79
 
78
80
  <<~YML
79
- test_only:
80
- #{generate_list('Defined in tests:', list).chomp}
81
- YML
82
- end
83
-
84
- def keep_data(only_test, none_non_test)
85
- return if only_test.empty? && none_non_test.empty?
86
-
87
- <<~YML.chomp
88
- keep:
89
- #{keep_test_called_data(only_test)}#{keep_never_called_data(none_non_test)}
90
- YML
91
- end
92
-
93
- def keep_test_called_data(list)
94
- return if list.empty?
95
-
96
- generate_list('Only directly called in tests:', list)
97
- end
98
-
99
- def keep_never_called_data(list)
100
- return if list.empty?
101
-
102
- generate_list('Not directly called at all:', list)
103
- end
104
-
105
- def generate_list(title, list)
106
- <<~YML
107
- # #{title}
81
+ #{key}:
82
+ # #{title}:
108
83
  #{print_definition_list(list)}
109
84
 
110
85
  YML
111
86
  end
112
87
 
113
88
  def print_definition_list(definition_list)
114
- definition_list.map { |definition| print_definition(definition) }.join("\n")
89
+ definition_list.map { |definition| print_definition(definition) }.sort.join("\n")
115
90
  end
116
91
 
117
92
  def print_definition(definition)
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#camelize method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#deconstantize method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -3,12 +3,6 @@
3
3
  module Leftovers
4
4
  module ValueProcessors
5
5
  class DeletePrefix
6
- # :nocov:
7
- if defined?(::Leftovers::Backports::StringDeletePrefixSuffix)
8
- using ::Leftovers::Backports::StringDeletePrefixSuffix
9
- end
10
- # :nocov:
11
-
12
6
  def initialize(prefix, then_processor)
13
7
  @prefix = prefix
14
8
  @then_processor = then_processor
@@ -3,12 +3,6 @@
3
3
  module Leftovers
4
4
  module ValueProcessors
5
5
  class DeleteSuffix
6
- # :nocov:
7
- if defined?(::Leftovers::Backports::StringDeletePrefixSuffix)
8
- using ::Leftovers::Backports::StringDeletePrefixSuffix
9
- end
10
- # :nocov:
11
-
12
6
  def initialize(suffix, then_processor)
13
7
  @suffix = suffix
14
8
  @then_processor = then_processor
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#demodulize method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module ValueProcessors
5
5
  class Keyword
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(matcher, then_processor)
11
7
  @matcher = matcher
12
8
  @then_processor = then_processor
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module ValueProcessors
5
5
  class KeywordArgument
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def initialize(matcher, then_processor)
11
7
  @matcher = matcher
12
8
  @then_processor = then_processor
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#parameterize method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#pluralize method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -3,10 +3,6 @@
3
3
  module Leftovers
4
4
  module ValueProcessors
5
5
  module ReturnDefinition
6
- # :nocov:
7
- using ::Leftovers::Backports::SetCaseEq if defined?(::Leftovers::Backports::SetCaseEq)
8
- # :nocov:
9
-
10
6
  def self.process(str, node, method_node)
11
7
  return unless str
12
8
  return if str.empty?
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#singularize method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#titleize method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -16,7 +16,7 @@ module Leftovers
16
16
  rescue NoMethodError
17
17
  Leftovers.error <<~MESSAGE
18
18
  Tried using the String#underscore method, but the activesupport gem was not available and/or not required
19
- `gem install activesupport`, and/or add `requires: 'active_support/core_ext/string'` to your .leftovers.yml
19
+ `gem install activesupport`, and/or add `requires: ['active_support', 'active_support/core_ext/string']` to your .leftovers.yml
20
20
  MESSAGE
21
21
  end
22
22
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leftovers
4
- VERSION = '0.5.2'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Leftovers
6
+ module YAML
7
+ class Builder < ::Psych::TreeBuilder
8
+ def initialize
9
+ @constants = []
10
+
11
+ super
12
+ end
13
+
14
+ def add_constant_for_tag(tag, value = nil)
15
+ match = %r{\A!ruby/[^:]*(?::(.*))?\z}.match(tag)
16
+ return unless match
17
+
18
+ @constants << (match[1] || value)
19
+ end
20
+
21
+ def start_mapping(_anchor, tag, *rest) # leftovers:keep
22
+ add_constant_for_tag(tag)
23
+ tag = nil
24
+
25
+ super
26
+ end
27
+
28
+ def start_sequence(_anchor, tag, *rest) # leftovers:keep
29
+ add_constant_for_tag(tag)
30
+ tag = nil
31
+
32
+ super
33
+ end
34
+
35
+ def scalar(value, _anchor, tag, *rest) # leftovers:keep
36
+ add_constant_for_tag(tag, value)
37
+ tag = nil
38
+
39
+ super
40
+ end
41
+
42
+ def to_ruby_file
43
+ <<~RUBY
44
+ __leftovers_document(#{to_ruby_argument(root.to_ruby.first)})
45
+ #{@constants.join("\n")}
46
+ RUBY
47
+ end
48
+
49
+ private
50
+
51
+ def to_ruby_argument(value)
52
+ ruby = value.inspect
53
+ return ruby unless value.is_a?(Array)
54
+
55
+ ruby.delete_prefix!('[')
56
+ ruby.delete_suffix!(']')
57
+
58
+ ruby
59
+ end
60
+ end
61
+
62
+ def self.precompile(yaml, name)
63
+ builder = ::Leftovers::YAML::Builder.new
64
+ parser = ::Psych::Parser.new(builder)
65
+ parser.parse(yaml, name.relative_path)
66
+
67
+ builder.to_ruby_file
68
+ rescue ::Psych::SyntaxError => e
69
+ Leftovers.warn "#{e.class}: #{e.message}"
70
+ ''
71
+ end
72
+ end
73
+ end