leftovers 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -3
  3. data/docs/Configuration.md +82 -18
  4. data/leftovers.gemspec +1 -1
  5. data/lib/config/actionmailer.yml +11 -11
  6. data/lib/config/activesupport.yml +1 -1
  7. data/lib/config/rails.yml +1 -1
  8. data/lib/config/railties.yml +11 -0
  9. data/lib/config/ruby.yml +72 -0
  10. data/lib/leftovers/ast/node.rb +16 -11
  11. data/lib/leftovers/config.rb +1 -24
  12. data/lib/leftovers/config_loader/argument_position_schema.rb +11 -0
  13. data/lib/leftovers/config_loader/argumentless_transform_schema.rb +21 -0
  14. data/lib/leftovers/config_loader/attribute.rb +30 -0
  15. data/lib/leftovers/config_loader/document_schema.rb +21 -0
  16. data/lib/leftovers/config_loader/dynamic_schema.rb +17 -0
  17. data/lib/leftovers/config_loader/has_argument_schema.rb +13 -0
  18. data/lib/leftovers/config_loader/has_value_schema.rb +18 -0
  19. data/lib/leftovers/config_loader/keep_test_only_schema.rb +13 -0
  20. data/lib/leftovers/config_loader/node.rb +106 -0
  21. data/lib/leftovers/config_loader/object_schema.rb +189 -0
  22. data/lib/leftovers/config_loader/privacy_processor_schema.rb +12 -0
  23. data/lib/leftovers/config_loader/privacy_schema.rb +15 -0
  24. data/lib/leftovers/config_loader/require_schema.rb +11 -0
  25. data/lib/leftovers/config_loader/rule_pattern_schema.rb +18 -0
  26. data/lib/leftovers/config_loader/scalar_argument_schema.rb +14 -0
  27. data/lib/leftovers/config_loader/scalar_value_schema.rb +14 -0
  28. data/lib/leftovers/config_loader/schema.rb +21 -0
  29. data/lib/leftovers/config_loader/string_enum_schema.rb +62 -0
  30. data/lib/leftovers/config_loader/string_pattern_schema.rb +14 -0
  31. data/lib/leftovers/config_loader/string_schema.rb +14 -0
  32. data/lib/leftovers/config_loader/string_value_processor_schema.rb +11 -0
  33. data/lib/leftovers/config_loader/suggester.rb +22 -0
  34. data/lib/leftovers/config_loader/transform_schema.rb +28 -0
  35. data/lib/leftovers/config_loader/true_schema.rb +18 -0
  36. data/lib/leftovers/config_loader/value_matcher_schema.rb +18 -0
  37. data/lib/leftovers/config_loader/value_or_array_schema.rb +64 -0
  38. data/lib/leftovers/config_loader/value_processor_schema.rb +14 -0
  39. data/lib/leftovers/config_loader/value_type_schema.rb +17 -0
  40. data/lib/leftovers/config_loader.rb +82 -0
  41. data/lib/leftovers/definition_node.rb +6 -17
  42. data/lib/leftovers/definition_node_set.rb +11 -0
  43. data/lib/leftovers/definition_to_add.rb +31 -0
  44. data/lib/leftovers/dynamic_processors/call.rb +4 -3
  45. data/lib/leftovers/dynamic_processors/call_definition.rb +14 -7
  46. data/lib/leftovers/dynamic_processors/definition.rb +8 -3
  47. data/lib/leftovers/dynamic_processors/set_default_privacy.rb +18 -0
  48. data/lib/leftovers/dynamic_processors/set_privacy.rb +23 -0
  49. data/lib/leftovers/dynamic_processors.rb +2 -0
  50. data/lib/leftovers/file.rb +5 -1
  51. data/lib/leftovers/file_collector.rb +44 -17
  52. data/lib/leftovers/matcher_builders/node.rb +4 -0
  53. data/lib/leftovers/matcher_builders/node_privacy.rb +13 -0
  54. data/lib/leftovers/matcher_builders/node_type.rb +4 -4
  55. data/lib/leftovers/matcher_builders/node_value.rb +1 -1
  56. data/lib/leftovers/matcher_builders/string_pattern.rb +14 -5
  57. data/lib/leftovers/matcher_builders.rb +1 -0
  58. data/lib/leftovers/matchers/node_privacy.rb +19 -0
  59. data/lib/leftovers/matchers.rb +1 -0
  60. data/lib/leftovers/merged_config.rb +18 -34
  61. data/lib/leftovers/processor_builders/add_prefix.rb +1 -1
  62. data/lib/leftovers/processor_builders/add_suffix.rb +1 -1
  63. data/lib/leftovers/processor_builders/dynamic.rb +50 -16
  64. data/lib/leftovers/processor_builders/transform.rb +2 -2
  65. data/lib/leftovers/processor_builders/transform_set.rb +8 -8
  66. data/lib/leftovers/value_processors/each_for_definition_set.rb +2 -5
  67. data/lib/leftovers/value_processors/each_positional_argument.rb +1 -1
  68. data/lib/leftovers/value_processors/return_definition_node.rb +14 -0
  69. data/lib/leftovers/value_processors/{return_string.rb → return_sym.rb} +1 -1
  70. data/lib/leftovers/value_processors.rb +2 -2
  71. data/lib/leftovers/version.rb +1 -1
  72. data/lib/leftovers.rb +23 -14
  73. metadata +54 -22
  74. data/lib/config/actioncable.yml +0 -4
  75. data/lib/leftovers/config_validator/error_processor.rb +0 -196
  76. data/lib/leftovers/config_validator/schema_hash.rb +0 -551
  77. data/lib/leftovers/config_validator.rb +0 -61
  78. data/lib/leftovers/value_processors/return_definition.rb +0 -22
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class Node
6
+ class_loader = Psych::ClassLoader::Restricted.new([], [])
7
+ ToRuby = Psych::Visitors::ToRuby.new(
8
+ Psych::ScalarScanner.new(class_loader),
9
+ class_loader
10
+ )
11
+
12
+ attr_reader :node, :file, :name
13
+ attr_accessor :error
14
+
15
+ def initialize(node, file, name = nil)
16
+ @node = node
17
+ @file = file
18
+ @name = name
19
+ end
20
+
21
+ def name_
22
+ "#{name} " if name
23
+ end
24
+
25
+ def valid?
26
+ !error
27
+ end
28
+
29
+ def error_message
30
+ "Config SchemaError: #{location} #{@error}" if @error
31
+ end
32
+
33
+ def all_errors
34
+ Array(error_message) + children.flat_map(&:all_errors)
35
+ end
36
+
37
+ def to_s
38
+ to_ruby.to_s
39
+ end
40
+
41
+ def hash?
42
+ node.is_a?(Psych::Nodes::Mapping)
43
+ end
44
+
45
+ def location
46
+ "#{file.relative_path}:#{node.start_line + 1}:#{node.start_column}"
47
+ end
48
+
49
+ def children
50
+ @children ||= if hash?
51
+ prepare_hash_children
52
+ elsif array?
53
+ node.children.map { |value| self.class.new(value, file, "#{name} value") }
54
+ else
55
+ []
56
+ end
57
+ end
58
+
59
+ def keys
60
+ @keys ||= pairs.map(&:first)
61
+ end
62
+
63
+ def pairs
64
+ @pairs ||= children.each_slice(2).to_a
65
+ end
66
+
67
+ def each_key(&block)
68
+ keys.each(&block)
69
+ end
70
+
71
+ def to_ruby
72
+ @to_ruby ||= ToRuby.accept(node)
73
+ end
74
+
75
+ def to_sym
76
+ to_ruby.to_sym if string?
77
+ end
78
+
79
+ def string?
80
+ to_ruby.is_a?(String)
81
+ end
82
+
83
+ def scalar?
84
+ !array? and !hash?
85
+ end
86
+
87
+ def array?
88
+ node.is_a?(Psych::Nodes::Sequence)
89
+ end
90
+
91
+ def integer?
92
+ to_ruby.is_a?(Integer)
93
+ end
94
+
95
+ private
96
+
97
+ def prepare_hash_children
98
+ node.children.each_slice(2).flat_map do |key, value|
99
+ key = self.class.new(key, file, name)
100
+ value = self.class.new(value, file, key.to_ruby)
101
+ [key, value]
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ObjectSchema < Schema # rubocop:disable Metrics/ClassLength
6
+ class << self
7
+ def inherit_attributes_from(schema, require_group: true, except: nil)
8
+ attributes_and_schemas_to_inherit_from << schema
9
+
10
+ inherit_except[schema] = Leftovers.each_or_self(except)
11
+
12
+ return if require_group
13
+
14
+ skip_require_group << schema
15
+ end
16
+
17
+ def attributes
18
+ nonexcluded_attributes = attributes_and_schemas_to_inherit_from.map do |attr_or_schema|
19
+ attr_or_schema.attributes.dup.tap do |attributes_copy|
20
+ inherit_except[attr_or_schema]&.each { |e| attributes_copy.delete(e) }
21
+ end
22
+ end
23
+
24
+ nonexcluded_attributes.reduce { |a, e| a.merge(e) { raise 'Duplicate attributes' } }
25
+ end
26
+
27
+ def aliases
28
+ attributes_and_schemas_to_inherit_from.map(&:aliases)
29
+ .reduce { |a, e| a.merge(e) { raise 'Duplicate aliases' } }
30
+ end
31
+
32
+ def require_groups
33
+ (attributes_and_schemas_to_inherit_from - skip_require_group)
34
+ .map(&:require_groups).each_with_object({}) do |require_groups, hash|
35
+ require_groups.each do |group, keys|
36
+ hash[group] ||= []
37
+ hash[group] += keys
38
+ end
39
+ end
40
+ end
41
+
42
+ attr_accessor :or_schema
43
+
44
+ def attribute(name, value_schema, aliases: nil, require_group: nil)
45
+ attributes_and_schemas_to_inherit_from << Attribute.new(
46
+ name,
47
+ value_schema,
48
+ aliases: aliases,
49
+ require_group: require_group
50
+ )
51
+ end
52
+
53
+ def validate(node)
54
+ if node.hash?
55
+ validate_attributes(node)
56
+ elsif or_schema
57
+ validate_or_schema(node)
58
+ else
59
+ validate_is_hash(node)
60
+ end
61
+ end
62
+
63
+ def to_ruby(node)
64
+ if node.hash?
65
+ node.pairs.map { |(key, value)| pair_to_ruby(key, value) }.to_h
66
+ else
67
+ or_schema.to_ruby(node)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def inherit_except
74
+ @inherit_except ||= {}
75
+ end
76
+
77
+ def skip_require_group
78
+ @skip_require_group ||= []
79
+ end
80
+
81
+ def attributes_and_schemas_to_inherit_from
82
+ @attributes_and_schemas_to_inherit_from ||= []
83
+ end
84
+
85
+ def pair_to_ruby(key, value)
86
+ key_sym = key.to_sym
87
+ key_sym = aliases[key_sym] || key_sym
88
+ key_sym = :unless_arg if key_sym == :unless
89
+ [key_sym, schema_for_attribute(key).to_ruby(value)]
90
+ end
91
+
92
+ def validate_attributes(node)
93
+ validate_attribute_keys(node) && validate_required_keys(node)
94
+ validate_alias_uniqueness_of_keys(node)
95
+ validate_valid_attribute_values(node)
96
+
97
+ node.children.all?(&:valid?)
98
+ end
99
+
100
+ def validate_or_schema(node)
101
+ or_schema.validate(node)
102
+ return true if node.valid?
103
+
104
+ if node.string? && valid_keys.include?(node.to_sym)
105
+ node.error = "#{node.name_}#{node.to_sym} must be a hash key"
106
+ else
107
+ node.error += " or a hash with any of #{attributes.keys.join(', ')}"
108
+ end
109
+ end
110
+
111
+ def validate_is_hash(node)
112
+ error(node, 'be a hash')
113
+ node.valid?
114
+ end
115
+
116
+ def validate_attribute_keys(node)
117
+ node.each_key { |key| validate_recognized_key(key, node) }
118
+ node.keys.all?(&:valid?)
119
+ end
120
+
121
+ def valid_keys
122
+ attributes.keys + aliases.keys
123
+ end
124
+
125
+ def validate_alias_uniqueness_of_keys(node)
126
+ node.keys.select(&:valid?)
127
+ .group_by { |key| aliases[key.to_sym] || key.to_sym }
128
+ .each_value do |keys|
129
+ next unless keys.length > 1
130
+
131
+ error_message_for_non_unique_keys(node, keys)
132
+ end
133
+ end
134
+
135
+ def error_message_for_non_unique_keys(node, keys)
136
+ keys.each do |key|
137
+ key.error = "#{node.name_}must only use one of #{keys.uniq.join(' or ')}"
138
+ end
139
+ end
140
+
141
+ def validate_recognized_key(key, node)
142
+ return true if valid_keys.include?(key.to_sym)
143
+
144
+ suggestions = suggestions_for_unrecognized_key(key, node)
145
+ did_you_mean = "\nDid you mean: #{suggestions.join(', ')}" unless suggestions.empty?
146
+ for_name = " for #{node.name}" if node.name
147
+
148
+ key.error = "unrecognized key #{key}#{for_name}#{did_you_mean}"
149
+
150
+ false
151
+ end
152
+
153
+ def suggester
154
+ @suggester ||= Suggester.new(attributes.keys)
155
+ end
156
+
157
+ def suggestions_for_unrecognized_key(key, node)
158
+ existing_keys = node.keys.flat_map { |k| [k.to_sym, aliases[k.to_sym]] }.compact
159
+ suggester.suggest(key.to_ruby) - existing_keys
160
+ end
161
+
162
+ def validate_required_keys(node)
163
+ missing_groups = require_groups.map do |_name, group|
164
+ next if node.keys.any? { |key| group.include?(key.to_sym) }
165
+
166
+ "include at least one of #{(attributes.keys & group).join(', ')}"
167
+ end.compact
168
+
169
+ error(node, missing_groups.join(' and ')) unless missing_groups.empty?
170
+
171
+ node.valid?
172
+ end
173
+
174
+ def schema_for_attribute(key)
175
+ key_sym = key.to_sym
176
+ attributes[key_sym] || attributes.fetch(aliases[key_sym])
177
+ end
178
+
179
+ def validate_valid_attribute_values(node)
180
+ node.pairs.each do |(key, value)|
181
+ next unless key.valid?
182
+
183
+ schema_for_attribute(key).validate(value)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class PrivacyProcessorSchema < ObjectSchema
6
+ inherit_attributes_from ValueProcessorSchema
7
+ attribute :to, PrivacySchema, require_group: :privacy_setting
8
+
9
+ self.or_schema = nil
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class PrivacySchema < StringEnumSchema
6
+ value :public
7
+ value :protected
8
+ value :private
9
+
10
+ def self.to_ruby(node)
11
+ super.to_sym
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class RequireSchema < ObjectSchema
6
+ attribute :quiet, StringSchema, aliases: :name, require_group: :quiet
7
+
8
+ self.or_schema = StringSchema
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class RulePatternSchema < ObjectSchema
6
+ attribute :names, ValueOrArraySchema[StringPatternSchema], aliases: :name,
7
+ require_group: :matcher
8
+ attribute :paths, ValueOrArraySchema[StringSchema], aliases: :path, require_group: :matcher
9
+ attribute :document, TrueSchema, require_group: :matcher
10
+ attribute :has_arguments, ValueOrArraySchema[HasArgumentSchema], aliases: :has_argument,
11
+ require_group: :matcher
12
+ attribute :has_receiver, ValueOrArraySchema[HasValueSchema], require_group: :matcher
13
+ attribute :type, ValueOrArraySchema[ValueTypeSchema], require_group: :matcher
14
+ attribute :privacy, ValueOrArraySchema[PrivacySchema], require_group: :matcher
15
+ attribute :unless, ValueOrArraySchema[RulePatternSchema], require_group: :matcher
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ScalarArgumentSchema < Schema
6
+ class << self
7
+ def validate(node)
8
+ error(node, 'be a string or an integer') unless node.string? || node.integer?
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ScalarValueSchema < Schema
6
+ class << self
7
+ def validate(node)
8
+ error(node, 'be any scalar value') unless node.scalar?
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class Schema
6
+ class << self
7
+ def error(node, requirement)
8
+ node.error = "#{node.name_}must #{requirement}"
9
+ end
10
+
11
+ def validate(node)
12
+ node.valid?
13
+ end
14
+
15
+ def to_ruby(node)
16
+ node.to_ruby
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class StringEnumSchema < Schema
6
+ class << self
7
+ def value(value, aliases: nil)
8
+ values << value
9
+ Leftovers.each_or_self(aliases) do |alias_name|
10
+ self.aliases[alias_name] = value
11
+ end
12
+ end
13
+
14
+ def aliases
15
+ @aliases ||= {}
16
+ end
17
+
18
+ def aliases_for(value)
19
+ aliases.select { |_k, v| v == value }.keys
20
+ end
21
+
22
+ def values
23
+ @values ||= []
24
+ end
25
+
26
+ def each_value(&block)
27
+ @values.each(&block)
28
+ end
29
+
30
+ def to_ruby(node)
31
+ aliases[node.to_sym]&.to_s || node.to_ruby
32
+ end
33
+
34
+ def validate(node)
35
+ if node.string?
36
+ node.error = error_message_with_suggestions(node) unless valid_value?(node.to_sym)
37
+ else
38
+ error(node, 'be a string')
39
+ end
40
+
41
+ super
42
+ end
43
+
44
+ private
45
+
46
+ def valid_value?(val)
47
+ values.include?(val) || aliases.key?(val)
48
+ end
49
+
50
+ def suggester
51
+ @suggester ||= Suggester.new(values)
52
+ end
53
+
54
+ def error_message_with_suggestions(node)
55
+ suggestions = suggester.suggest(node.to_ruby)
56
+
57
+ "unrecognized value #{node} for #{node.name}\nDid you mean: #{suggestions.join(', ')}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class StringPatternSchema < ObjectSchema
6
+ attribute :match, StringSchema, aliases: :matches, require_group: :matcher
7
+ attribute :has_prefix, StringSchema, require_group: :matcher
8
+ attribute :has_suffix, StringSchema, require_group: :matcher
9
+ attribute :unless, ValueOrArraySchema[StringPatternSchema], require_group: :matcher
10
+
11
+ self.or_schema = StringSchema
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class StringSchema < Schema
6
+ class << self
7
+ def validate(node)
8
+ error(node, 'be a string') unless node.string?
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class StringValueProcessorSchema < ObjectSchema
6
+ inherit_attributes_from ValueProcessorSchema
7
+
8
+ self.or_schema = StringSchema
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class Suggester
6
+ def initialize(words)
7
+ @words = words
8
+ @did_you_mean = ::DidYouMean::SpellChecker.new(dictionary: words) if defined?(::DidYouMean)
9
+ end
10
+
11
+ def suggest(word)
12
+ suggestions = did_you_mean.correct(word) if did_you_mean
13
+ suggestions = words if !suggestions || suggestions.empty?
14
+ suggestions
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :words, :did_you_mean
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class TransformSchema < ObjectSchema
6
+ ArgumentlessTransformSchema.each_value do |transform|
7
+ attribute(
8
+ transform, TrueSchema,
9
+ aliases: ArgumentlessTransformSchema.aliases_for(transform),
10
+ require_group: :processor
11
+ )
12
+ end
13
+
14
+ attribute :add_prefix, ValueOrArraySchema[StringValueProcessorSchema],
15
+ require_group: :processor
16
+ attribute :add_suffix, ValueOrArraySchema[StringValueProcessorSchema],
17
+ require_group: :processor
18
+
19
+ attribute :split, StringSchema, require_group: :processor
20
+ attribute :delete_prefix, StringSchema, require_group: :processor
21
+ attribute :delete_suffix, StringSchema, require_group: :processor
22
+ attribute :delete_before, StringSchema, require_group: :processor
23
+ attribute :delete_after, StringSchema, require_group: :processor
24
+
25
+ self.or_schema = ArgumentlessTransformSchema
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class TrueSchema < Schema
6
+ class << self
7
+ def validate(node)
8
+ error(node, 'be true') unless to_ruby(node)
9
+ super
10
+ end
11
+
12
+ def to_ruby(node)
13
+ node.to_ruby == true || node.to_ruby == 'true'
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ValueMatcherSchema < ObjectSchema
6
+ attribute :arguments, ValueOrArraySchema[ArgumentPositionSchema], aliases: :argument,
7
+ require_group: :matcher
8
+ attribute :keywords, ValueOrArraySchema[StringPatternSchema], aliases: :keyword,
9
+ require_group: :matcher
10
+ attribute :itself, TrueSchema, require_group: :matcher
11
+ attribute :nested, ValueOrArraySchema[ValueMatcherSchema]
12
+ attribute :value, StringSchema, require_group: :matcher
13
+ attribute :recursive, TrueSchema
14
+
15
+ self.or_schema = ScalarArgumentSchema
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ValueOrArraySchema < Schema
6
+ class << self
7
+ def [](value_schema)
8
+ new(value_schema)
9
+ end
10
+ end
11
+
12
+ attr_reader :value_schema
13
+
14
+ def initialize(value_schema)
15
+ @value_schema = value_schema
16
+
17
+ super()
18
+ end
19
+
20
+ def validate(node)
21
+ if node.array?
22
+ validate_length(node) && validate_values(node)
23
+ else
24
+ validate_or_schema(node)
25
+ end
26
+ end
27
+
28
+ def to_ruby(node)
29
+ if node.array?
30
+ node.children.map do |value|
31
+ value_schema.to_ruby(value)
32
+ end
33
+ else
34
+ value_schema.to_ruby(node)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def validate_or_schema(node)
41
+ value_schema.validate(node)
42
+ return true if node.valid?
43
+
44
+ node.error += ' or an array'
45
+
46
+ false
47
+ end
48
+
49
+ def validate_length(node)
50
+ self.class.error(node, 'not be empty') if node.children.empty?
51
+
52
+ node.valid?
53
+ end
54
+
55
+ def validate_values(node)
56
+ node.children.each do |value|
57
+ value_schema.validate(value)
58
+ end
59
+
60
+ node.children.all?(&:valid?)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leftovers
4
+ class ConfigLoader
5
+ class ValueProcessorSchema < ObjectSchema
6
+ inherit_attributes_from ValueMatcherSchema
7
+
8
+ attribute :transforms, ValueOrArraySchema[TransformSchema]
9
+ inherit_attributes_from TransformSchema, require_group: nil
10
+
11
+ self.or_schema = ScalarArgumentSchema
12
+ end
13
+ end
14
+ end