leftovers 0.7.0 → 0.8.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +1 -0
  4. data/docs/Configuration.md +20 -57
  5. data/docs/Custom-Precompilers.md +38 -0
  6. data/lib/config/actionpack.yml +5 -3
  7. data/lib/config/haml.yml +4 -2
  8. data/lib/config/ruby.yml +4 -15
  9. data/lib/config/slim.yml +4 -2
  10. data/lib/leftovers/cli.rb +5 -0
  11. data/lib/leftovers/collector.rb +2 -1
  12. data/lib/leftovers/config.rb +2 -18
  13. data/lib/leftovers/config_loader/argument_position_schema.rb +2 -2
  14. data/lib/leftovers/config_loader/attribute.rb +36 -10
  15. data/lib/leftovers/config_loader/built_in_precompiler_schema.rb +13 -0
  16. data/lib/leftovers/config_loader/document_schema.rb +32 -5
  17. data/lib/leftovers/config_loader/has_argument_schema.rb +2 -2
  18. data/lib/leftovers/config_loader/has_value_schema.rb +2 -2
  19. data/lib/leftovers/config_loader/inherit_schema_attributes.rb +22 -0
  20. data/lib/leftovers/config_loader/keep_test_only_schema.rb +2 -2
  21. data/lib/leftovers/config_loader/object_schema.rb +33 -105
  22. data/lib/leftovers/config_loader/precompile_schema.rb +12 -0
  23. data/lib/leftovers/config_loader/precompiler_schema.rb +11 -0
  24. data/lib/leftovers/config_loader/privacy_processor_schema.rb +0 -2
  25. data/lib/leftovers/config_loader/require_schema.rb +3 -3
  26. data/lib/leftovers/config_loader/schema.rb +2 -0
  27. data/lib/leftovers/config_loader/string_pattern_schema.rb +2 -2
  28. data/lib/leftovers/config_loader/string_value_processor_schema.rb +2 -2
  29. data/lib/leftovers/config_loader/transform_schema.rb +4 -6
  30. data/lib/leftovers/config_loader/value_matcher_schema.rb +2 -2
  31. data/lib/leftovers/config_loader/value_or_array_schema.rb +5 -3
  32. data/lib/leftovers/config_loader/value_or_object_schema.rb +40 -0
  33. data/lib/leftovers/config_loader/value_processor_schema.rb +2 -2
  34. data/lib/leftovers/config_loader.rb +5 -1
  35. data/lib/leftovers/file.rb +7 -53
  36. data/lib/leftovers/file_collector/comments_processor.rb +57 -0
  37. data/lib/leftovers/file_collector/node_processor.rb +131 -0
  38. data/lib/leftovers/file_collector.rb +42 -203
  39. data/lib/leftovers/matcher_builders/and_not.rb +7 -5
  40. data/lib/leftovers/matcher_builders/name.rb +18 -17
  41. data/lib/leftovers/matcher_builders/node.rb +48 -34
  42. data/lib/leftovers/matcher_builders/node_has_argument.rb +48 -52
  43. data/lib/leftovers/matcher_builders/node_has_keyword_argument.rb +13 -10
  44. data/lib/leftovers/matcher_builders/node_has_positional_argument.rb +29 -15
  45. data/lib/leftovers/matcher_builders/node_type.rb +1 -1
  46. data/lib/leftovers/matcher_builders/node_value.rb +27 -22
  47. data/lib/leftovers/matcher_builders/or.rb +50 -50
  48. data/lib/leftovers/matcher_builders/string_pattern.rb +1 -1
  49. data/lib/leftovers/merged_config.rb +3 -23
  50. data/lib/leftovers/precompilers/erb.rb +22 -0
  51. data/lib/leftovers/precompilers/haml.rb +15 -0
  52. data/lib/leftovers/precompilers/json.rb +27 -0
  53. data/lib/leftovers/precompilers/precompiler.rb +28 -0
  54. data/lib/leftovers/precompilers/slim.rb +15 -0
  55. data/lib/leftovers/precompilers/yaml.rb +75 -0
  56. data/lib/leftovers/precompilers.rb +50 -0
  57. data/lib/leftovers/processor_builders/action.rb +48 -39
  58. data/lib/leftovers/processor_builders/add_prefix.rb +1 -1
  59. data/lib/leftovers/processor_builders/add_suffix.rb +1 -1
  60. data/lib/leftovers/processor_builders/argument.rb +8 -11
  61. data/lib/leftovers/processor_builders/dynamic.rb +5 -5
  62. data/lib/leftovers/processor_builders/each.rb +2 -2
  63. data/lib/leftovers/processor_builders/each_action.rb +33 -33
  64. data/lib/leftovers/processor_builders/each_dynamic.rb +4 -8
  65. data/lib/leftovers/processor_builders/each_for_definition_set.rb +25 -21
  66. data/lib/leftovers/processor_builders/keyword.rb +3 -4
  67. data/lib/leftovers/processor_builders/transform.rb +2 -2
  68. data/lib/leftovers/processor_builders/transform_chain.rb +16 -8
  69. data/lib/leftovers/processor_builders/transform_set.rb +32 -28
  70. data/lib/leftovers/rake_task.rb +1 -1
  71. data/lib/leftovers/value_processors/add_dynamic_prefix.rb +3 -10
  72. data/lib/leftovers/value_processors/add_dynamic_suffix.rb +3 -10
  73. data/lib/leftovers/value_processors/each.rb +1 -1
  74. data/lib/leftovers/value_processors/each_for_definition_set.rb +2 -5
  75. data/lib/leftovers/value_processors/each_keyword.rb +1 -1
  76. data/lib/leftovers/value_processors/each_keyword_argument.rb +1 -1
  77. data/lib/leftovers/value_processors/each_positional_argument.rb +2 -1
  78. data/lib/leftovers/value_processors/keyword.rb +3 -7
  79. data/lib/leftovers/value_processors/keyword_argument.rb +2 -6
  80. data/lib/leftovers/value_processors/split.rb +2 -2
  81. data/lib/leftovers/version.rb +1 -1
  82. data/lib/leftovers.rb +41 -6
  83. metadata +17 -7
  84. data/lib/leftovers/erb.rb +0 -20
  85. data/lib/leftovers/haml.rb +0 -21
  86. data/lib/leftovers/json.rb +0 -28
  87. data/lib/leftovers/slim.rb +0 -21
  88. data/lib/leftovers/yaml.rb +0 -73
@@ -3,71 +3,67 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasArgument
6
- def self.build(patterns) # rubocop:disable Metrics/MethodLength
7
- ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pat|
8
- case pat
9
- when ::String
10
- ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(pat, nil)
11
- when ::Integer
12
- ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(pat, nil)
13
- when ::Hash
14
- build_from_hash(**pat)
15
- # :nocov:
16
- else
17
- raise
6
+ class << self
7
+ def build(patterns)
8
+ ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pat|
9
+ case pat
10
+ when ::String
11
+ ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(pat, nil)
12
+ when ::Integer
13
+ ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(pat, nil)
14
+ when ::Hash then build_from_hash(**pat)
18
15
  # :nocov:
16
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{pat.inspect}"
17
+ # :nocov:
18
+ end
19
19
  end
20
20
  end
21
- end
22
21
 
23
- def self.separate_argument_types(at) # rubocop:disable Metrics/MethodLength
24
- keys = []
25
- positions = []
22
+ private
26
23
 
27
- ::Leftovers.each_or_self(at) do |k|
28
- case k
29
- when '*', ::Integer
30
- positions << k
31
- when ::String, ::Hash
32
- keys << k
33
- # :nocov:
34
- else raise
35
- # :nocov:
36
- end
37
- end
24
+ def build_from_hash(at: nil, has_value: nil, unless_arg: nil)
25
+ value_matcher = ::Leftovers::MatcherBuilders::NodeValue.build(has_value)
38
26
 
39
- keys = nil if keys.empty?
40
- positions = nil if positions.empty?
27
+ ::Leftovers::MatcherBuilders::AndNot.build(
28
+ build_argument_matcher(value_matcher, **separate_argument_types(at)),
29
+ ::Leftovers::MatcherBuilders::NodeHasArgument.build(unless_arg)
30
+ )
31
+ end
41
32
 
42
- [keys, positions]
43
- end
33
+ def separate_argument_types(at)
34
+ groups = ::Leftovers.each_or_self(at).group_by do |index|
35
+ case index
36
+ when '*', ::Integer then :positions
37
+ when ::String, ::Hash then :keys
38
+ # :nocov:
39
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{index.inspect}"
40
+ # :nocov:
41
+ end
42
+ end
44
43
 
45
- def self.build_from_hash( # rubocop:disable Metrics/MethodLength
46
- at: nil,
47
- has_value: nil,
48
- unless_arg: nil
49
- )
50
- keys, positions = separate_argument_types(at)
44
+ groups.transform_values { |v| Leftovers.unwrap_array(v) }
45
+ end
51
46
 
52
- value_matcher = ::Leftovers::MatcherBuilders::NodeValue.build(has_value)
53
- matcher = if (keys && positions) || (!keys && !positions)
54
- ::Leftovers::MatcherBuilders::Or.build([
55
- ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher),
56
- ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
57
- ])
58
- elsif keys
47
+ def build_has_keyword_argument(keys, value_matcher)
59
48
  ::Leftovers::MatcherBuilders::NodeHasKeywordArgument.build(keys, value_matcher)
60
- elsif positions
49
+ end
50
+
51
+ def build_has_positional_argument(positions, value_matcher)
61
52
  ::Leftovers::MatcherBuilders::NodeHasPositionalArgument.build(positions, value_matcher)
62
- # :nocov:
63
- else
64
- raise
65
- # :nocov:
66
53
  end
67
54
 
68
- ::Leftovers::MatcherBuilders::AndNot.build(
69
- matcher, ::Leftovers::MatcherBuilders::NodeHasArgument.build(unless_arg)
70
- )
55
+ def build_argument_matcher(value_matcher, keys: nil, positions: nil)
56
+ if keys && !positions
57
+ build_has_keyword_argument(keys, value_matcher)
58
+ elsif positions && !keys
59
+ build_has_positional_argument(positions, value_matcher)
60
+ else
61
+ ::Leftovers::MatcherBuilders::Or.build([
62
+ build_has_positional_argument(positions, value_matcher),
63
+ build_has_keyword_argument(keys, value_matcher)
64
+ ])
65
+ end
66
+ end
71
67
  end
72
68
  end
73
69
  end
@@ -4,21 +4,24 @@ module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasKeywordArgument
6
6
  class << self
7
- def build(keywords, value_matcher) # rubocop:disable Metrics/MethodLength
7
+ def build(keywords, value_matcher)
8
8
  value_matcher = ::Leftovers::MatcherBuilders::NodePairValue.build(value_matcher)
9
- keyword_matcher = if ::Leftovers.each_or_self(keywords).include?('**')
9
+ keyword_matcher = build_keyword_matcher(keywords)
10
+ pair_matcher = ::Leftovers::MatcherBuilders::And.build([keyword_matcher, value_matcher])
11
+
12
+ return unless pair_matcher
13
+
14
+ ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
15
+ end
16
+
17
+ private
18
+
19
+ def build_keyword_matcher(keywords)
20
+ if ::Leftovers.each_or_self(keywords).include?('**')
10
21
  ::Leftovers::Matchers::NodeType.new(:pair)
11
22
  else
12
23
  ::Leftovers::MatcherBuilders::NodePairName.build(keywords)
13
24
  end
14
-
15
- pair_matcher = ::Leftovers::MatcherBuilders::And.build([
16
- keyword_matcher, value_matcher
17
- ])
18
-
19
- return nil unless pair_matcher
20
-
21
- ::Leftovers::Matchers::NodeHasAnyKeywordArgument.new(pair_matcher)
22
25
  end
23
26
  end
24
27
  end
@@ -4,22 +4,36 @@ module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeHasPositionalArgument
6
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).include?('*')
18
- pos ||= ::Leftovers.each_or_self(positions).min
19
-
20
- ::Leftovers::Matchers::NodeHasPositionalArgument.new(pos)
7
+ def build(positions, value_matcher)
8
+ if positions && !all_positions?(positions) && value_matcher
9
+ build_has_positional_value_matcher(positions, value_matcher)
10
+ elsif positions && !value_matcher
11
+ build_has_position_matcher(positions)
21
12
  elsif value_matcher
22
- ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
13
+ build_has_any_positional_value_matcher(value_matcher)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def all_positions?(positions)
20
+ ::Leftovers.each_or_self(positions).include?('*')
21
+ end
22
+
23
+ def build_has_position_matcher(positions)
24
+ position = 0 if all_positions?(positions)
25
+ position ||= ::Leftovers.each_or_self(positions).min
26
+
27
+ ::Leftovers::Matchers::NodeHasPositionalArgument.new(position)
28
+ end
29
+
30
+ def build_has_any_positional_value_matcher(value_matcher)
31
+ ::Leftovers::Matchers::NodeHasAnyPositionalArgumentWithValue.new(value_matcher)
32
+ end
33
+
34
+ def build_has_positional_value_matcher(positions, value_matcher)
35
+ ::Leftovers::MatcherBuilders::Or.each_or_self(positions) do |position|
36
+ ::Leftovers::Matchers::NodeHasPositionalArgumentWithValue.new(position, value_matcher)
23
37
  end
24
38
  end
25
39
  end
@@ -19,7 +19,7 @@ module Leftovers
19
19
  when 'Constant'
20
20
  ::Leftovers::Matchers::NodeType.new(Set[:const, :class, :module, :casgn])
21
21
  # :nocov:
22
- else raise "Unknown type #{type}"
22
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{type.inspect}"
23
23
  # :nocov:
24
24
  end
25
25
  end
@@ -4,17 +4,15 @@ module Leftovers
4
4
  module MatcherBuilders
5
5
  module NodeValue
6
6
  class << self
7
- def build(pattern) # rubocop:disable Metrics/MethodLength
8
- ::Leftovers::MatcherBuilders::Or.each_or_self(pattern) do |pat|
9
- case pat
7
+ def build(patterns)
8
+ ::Leftovers::MatcherBuilders::Or.each_or_self(patterns) do |pattern|
9
+ case pattern
10
10
  when ::Integer, ::Float, 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)
11
+ ::Leftovers::Matchers::NodeScalarValue.new(pattern)
12
+ when ::String then ::Leftovers::MatcherBuilders::NodeName.build(pattern)
13
+ when ::Hash then build_from_hash(**pattern)
16
14
  # :nocov:
17
- else raise
15
+ else raise Leftovers::UnexpectedCase, "Unhandled value #{pattern.inspect}"
18
16
  # :nocov:
19
17
  end
20
18
  end
@@ -22,27 +20,34 @@ module Leftovers
22
20
 
23
21
  private
24
22
 
25
- def build_from_hash( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
23
+ def build_node_name(match, has_prefix, has_suffix)
24
+ ::Leftovers::MatcherBuilders::NodeName.build(
25
+ match: match, has_prefix: has_prefix, has_suffix: has_suffix
26
+ )
27
+ end
28
+
29
+ def build_unless(unless_arg)
30
+ return unless unless_arg
31
+
32
+ ::Leftovers::MatcherBuilders::Unless.build(
33
+ ::Leftovers::MatcherBuilders::NodeValue.build(unless_arg)
34
+ )
35
+ end
36
+
37
+ def build_from_hash( # rubocop:disable Metrics/ParameterLists
26
38
  at: nil, has_value: nil,
27
39
  match: nil, has_prefix: nil, has_suffix: nil,
28
40
  type: nil,
29
41
  has_receiver: nil,
30
42
  unless_arg: nil
31
43
  )
32
- matcher = ::Leftovers::MatcherBuilders::And.build([
33
- ::Leftovers::MatcherBuilders::NodeHasArgument.build(
34
- at: at, has_value: has_value
35
- ),
36
- ::Leftovers::MatcherBuilders::NodeName.build(
37
- match: match, has_prefix: has_prefix, has_suffix: has_suffix
38
- ),
44
+ ::Leftovers::MatcherBuilders::And.build([
45
+ ::Leftovers::MatcherBuilders::NodeHasArgument.build(at: at, has_value: has_value),
46
+ build_node_name(match, has_prefix, has_suffix),
39
47
  ::Leftovers::MatcherBuilders::NodeType.build(type),
40
- ::Leftovers::MatcherBuilders::NodeHasReceiver.build(has_receiver)
48
+ ::Leftovers::MatcherBuilders::NodeHasReceiver.build(has_receiver),
49
+ build_unless(unless_arg)
41
50
  ])
42
-
43
- ::Leftovers::MatcherBuilders::AndNot.build(
44
- matcher, ::Leftovers::MatcherBuilders::NodeValue.build(unless_arg)
45
- )
46
51
  end
47
52
  end
48
53
  end
@@ -5,68 +5,68 @@ require 'set'
5
5
  module Leftovers
6
6
  module MatcherBuilders
7
7
  module Or
8
- def self.each_or_self(value, &block)
9
- case value
10
- when nil then nil
11
- when Array then build(value.map(&block))
12
- else build([yield(value)])
8
+ class << self
9
+ def each_or_self(value, &block)
10
+ case value
11
+ when nil then nil
12
+ when Array then build(value.map(&block))
13
+ else build([yield(value)])
14
+ end
13
15
  end
14
- end
15
16
 
16
- def self.build(matchers)
17
- matchers = compact(matchers)
18
- case matchers.length
19
- # :nocov:
20
- when 0 then nil
21
- # :nocov:
22
- when 1 then matchers.first
23
- when 2 then ::Leftovers::Matchers::Or.new(matchers.first, matchers[1])
24
- else ::Leftovers::Matchers::Any.new(matchers.dup)
17
+ def build(matchers)
18
+ matchers = compact(matchers)
19
+ case matchers.length
20
+ # :nocov:
21
+ when 0 then nil
22
+ # :nocov:
23
+ when 1 then matchers.first
24
+ when 2 then ::Leftovers::Matchers::Or.new(matchers.first, matchers[1])
25
+ else ::Leftovers::Matchers::Any.new(matchers.dup)
26
+ end
25
27
  end
26
- end
27
28
 
28
- def self.flatten(value) # rubocop:disable Metrics/MethodLength
29
- case value
30
- when ::Leftovers::Matchers::Or
31
- [*flatten(value.lhs), *flatten(value.rhs)]
32
- when ::Leftovers::Matchers::Any
33
- flatten(value.matchers)
34
- when Array
35
- ret = value.map { |v| flatten(v) }
36
- ret.flatten!(1)
37
- ret
38
- else
39
- value
29
+ private
30
+
31
+ def flatten(value)
32
+ case value
33
+ when ::Leftovers::Matchers::Or
34
+ [*flatten(value.lhs), *flatten(value.rhs)]
35
+ when ::Leftovers::Matchers::Any
36
+ flatten(value.matchers)
37
+ when Array
38
+ value.flat_map { |v| flatten(v) }
39
+ else
40
+ value
41
+ end
40
42
  end
41
- end
42
43
 
43
- def self.compact(matchers) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize,
44
- return matchers if matchers.length <= 1
44
+ def group_by_compactable(matchers)
45
+ groups = matchers.group_by do |matcher|
46
+ case matcher
47
+ when ::Integer, ::Symbol then :set
48
+ when ::Regexp then :regexp
49
+ else :uncompactable
50
+ end
51
+ end
45
52
 
46
- set = Set.new
47
- regexps = []
48
- uncompactable = []
53
+ groups.transform_values { |v| Leftovers.unwrap_array(v) }
54
+ end
49
55
 
50
- matchers = flatten(matchers)
56
+ def build_grouped(set: nil, regexp: nil, uncompactable: nil)
57
+ set = set.to_set if set.is_a?(Array)
58
+ regexp = Regexp.union(regexp) if regexp.is_a?(Array)
51
59
 
52
- matchers.each do |matcher|
53
- case matcher
54
- when nil then nil
55
- when ::Integer, ::Symbol then set << matcher
56
- # when ::Set then set.merge(matcher) # may not be necessary
57
- when ::Regexp then regexps << matcher
58
- else uncompactable << matcher
59
- end
60
+ [set, regexp].concat(Array(uncompactable)).compact
60
61
  end
61
62
 
62
- set = set.first if set.length <= 1
63
- regexps = if regexps.length <= 1
64
- regexps.first
65
- else
66
- Regexp.union(regexps)
67
- end
63
+ def compact(matchers)
64
+ matchers = flatten(matchers)
65
+
66
+ return matchers if matchers.length <= 1
68
67
 
69
- [set, regexps].compact.concat(uncompactable)
68
+ build_grouped(**group_by_compactable(matchers))
69
+ end
70
70
  end
71
71
  end
72
72
  end
@@ -3,7 +3,7 @@
3
3
  module Leftovers
4
4
  module MatcherBuilders
5
5
  module StringPattern
6
- def self.build(match: nil, has_prefix: nil, has_suffix: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
6
+ def self.build(match: nil, has_prefix: nil, has_suffix: nil) # rubocop:disable Metrics
7
7
  has_prefix = ::Regexp.escape(has_prefix) if has_prefix
8
8
  has_suffix = ::Regexp.escape(has_suffix) if has_suffix
9
9
 
@@ -39,11 +39,7 @@ module Leftovers
39
39
  @exclude_paths
40
40
  @include_paths
41
41
  @test_paths
42
- @haml_paths
43
- @slim_paths
44
- @yaml_paths
45
- @json_paths
46
- @erb_paths
42
+ @precompilers
47
43
  @dynamic
48
44
  @keep
49
45
  @test_only
@@ -67,24 +63,8 @@ module Leftovers
67
63
  @test_paths ||= Leftovers::MatcherBuilders::Path.build(@configs.flat_map(&:test_paths))
68
64
  end
69
65
 
70
- def haml_paths
71
- @haml_paths ||= Leftovers::MatcherBuilders::Path.build(@configs.flat_map(&:haml_paths))
72
- end
73
-
74
- def slim_paths
75
- @slim_paths ||= Leftovers::MatcherBuilders::Path.build(@configs.flat_map(&:slim_paths))
76
- end
77
-
78
- def yaml_paths
79
- @yaml_paths ||= Leftovers::MatcherBuilders::Path.build(@configs.flat_map(&:yaml_paths))
80
- end
81
-
82
- def json_paths
83
- @json_paths ||= Leftovers::MatcherBuilders::Path.build(@configs.flat_map(&:json_paths))
84
- end
85
-
86
- def erb_paths
87
- @erb_paths ||= Leftovers::MatcherBuilders::Path.build(@configs.flat_map(&:erb_paths))
66
+ def precompilers
67
+ @precompilers ||= Leftovers::Precompilers.build(@configs.flat_map(&:precompile))
88
68
  end
89
69
 
90
70
  def dynamic
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module Leftovers
6
+ module Precompilers
7
+ class ERB < ::ERB::Compiler
8
+ def self.precompile(erb)
9
+ @compiler ||= new('-')
10
+ @compiler.compile(erb).first
11
+ end
12
+
13
+ def add_insert_cmd(out, content) # leftovers:keep
14
+ out.push("\n#{content}\n")
15
+ end
16
+
17
+ def add_put_cmd(out, _content) # leftovers:keep
18
+ out.push("\n")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'haml'
4
+
5
+ module Leftovers
6
+ module Precompilers
7
+ module Haml
8
+ def self.precompile(haml)
9
+ ::Haml::Engine.new(haml).precompiled
10
+ rescue ::Haml::SyntaxError => e
11
+ raise Leftovers::PrecompileError.new(e.message, line: e.line)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Leftovers
6
+ module Precompilers
7
+ module JSON
8
+ class << self
9
+ def precompile(json)
10
+ "__leftovers_document(#{to_ruby_argument(::JSON.parse(json))})"
11
+ end
12
+
13
+ private
14
+
15
+ def to_ruby_argument(value)
16
+ ruby = value.inspect
17
+ return ruby unless value.is_a?(Array)
18
+
19
+ ruby.delete_prefix!('[')
20
+ ruby.delete_suffix!(']')
21
+
22
+ ruby
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Leftovers
6
+ module Precompilers
7
+ class Precompiler
8
+ def initialize(precompiler, matcher)
9
+ @precompiler = precompiler
10
+ @matcher = matcher
11
+ end
12
+
13
+ def precompile(content, file)
14
+ return unless @matcher === file.relative_path
15
+
16
+ begin
17
+ @precompiler.precompile(content)
18
+ rescue Leftovers::PrecompileError => e
19
+ e.warn(path: file.relative_path)
20
+ ''
21
+ rescue StandardError => e
22
+ Leftovers.warn "#{e.class}: #{file.relative_path} #{e.message}"
23
+ ''
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slim'
4
+
5
+ module Leftovers
6
+ module Precompilers
7
+ module Slim
8
+ def self.precompile(file)
9
+ ::Slim::Engine.new(file: file).call(file)
10
+ rescue ::Slim::Parser::SyntaxError => e
11
+ raise Leftovers::PrecompileError.new(e.error, line: e.lineno, column: e.column)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Leftovers
6
+ module Precompilers
7
+ module YAML
8
+ class Builder < ::Psych::TreeBuilder
9
+ def initialize
10
+ @constants = []
11
+
12
+ super
13
+ end
14
+
15
+ def add_constant_for_tag(tag, value = nil)
16
+ match = %r{\A!ruby/[^:]*(?::(.*))?\z}.match(tag)
17
+ return unless match
18
+
19
+ @constants << (match[1] || value)
20
+ end
21
+
22
+ def start_mapping(_anchor, tag, *rest) # leftovers:keep
23
+ add_constant_for_tag(tag)
24
+ tag = nil
25
+
26
+ super
27
+ end
28
+
29
+ def start_sequence(_anchor, tag, *rest) # leftovers:keep
30
+ add_constant_for_tag(tag)
31
+ tag = nil
32
+
33
+ super
34
+ end
35
+
36
+ def scalar(value, _anchor, tag, *rest) # leftovers:keep
37
+ add_constant_for_tag(tag, value)
38
+ tag = nil
39
+
40
+ super
41
+ end
42
+
43
+ def to_ruby_file
44
+ <<~FILE
45
+ __leftovers_document(#{to_ruby_argument(root.to_ruby.first)})
46
+ #{@constants.join("\n")}
47
+ FILE
48
+ end
49
+
50
+ private
51
+
52
+ def to_ruby_argument(value)
53
+ ruby = value.inspect
54
+ return ruby unless value.is_a?(Array)
55
+
56
+ ruby.delete_prefix!('[')
57
+ ruby.delete_suffix!(']')
58
+
59
+ ruby
60
+ end
61
+ end
62
+
63
+ def self.precompile(yaml)
64
+ builder = ::Leftovers::Precompilers::YAML::Builder.new
65
+ parser = ::Psych::Parser.new(builder)
66
+ parser.parse(yaml)
67
+
68
+ builder.to_ruby_file
69
+ rescue ::Psych::SyntaxError => e
70
+ message = [e.problem, e.context].compact.join(' ')
71
+ raise ::Leftovers::PrecompileError.new(message, line: e.line, column: e.column)
72
+ end
73
+ end
74
+ end
75
+ end