leftovers 0.6.0 → 0.7.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 (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