leftovers 0.7.0 → 0.8.0

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