mega-sharp-sys 0.0.1
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.
- checksums.yaml +7 -0
- data/dry-schema-1.16.0/CHANGELOG.md +901 -0
- data/dry-schema-1.16.0/LICENSE +20 -0
- data/dry-schema-1.16.0/README.md +17 -0
- data/dry-schema-1.16.0/config/errors.yml +127 -0
- data/dry-schema-1.16.0/dry-schema.gemspec +50 -0
- data/dry-schema-1.16.0/lib/dry/schema/compiler.rb +51 -0
- data/dry-schema-1.16.0/lib/dry/schema/config.rb +83 -0
- data/dry-schema-1.16.0/lib/dry/schema/constants.rb +47 -0
- data/dry-schema-1.16.0/lib/dry/schema/dsl.rb +517 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/compiler_methods.rb +19 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/message_compiler_methods.rb +89 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/message_set_methods.rb +44 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/result_methods.rb +47 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/hints.rb +65 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/info/schema_compiler.rb +123 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/info.rb +27 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/json_schema/schema_compiler.rb +314 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/json_schema.rb +29 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/monads.rb +32 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions/struct.rb +62 -0
- data/dry-schema-1.16.0/lib/dry/schema/extensions.rb +21 -0
- data/dry-schema-1.16.0/lib/dry/schema/json.rb +17 -0
- data/dry-schema-1.16.0/lib/dry/schema/key.rb +184 -0
- data/dry-schema-1.16.0/lib/dry/schema/key_coercer.rb +41 -0
- data/dry-schema-1.16.0/lib/dry/schema/key_map.rb +136 -0
- data/dry-schema-1.16.0/lib/dry/schema/key_validator.rb +117 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/array.rb +54 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/core.rb +55 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/dsl.rb +282 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/each.rb +31 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/filled.rb +58 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/hash.rb +36 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/key.rb +50 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/maybe.rb +38 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/optional.rb +22 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/required.rb +17 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/schema.rb +103 -0
- data/dry-schema-1.16.0/lib/dry/schema/macros/value.rb +125 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or/abstract.rb +28 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or/multi_path.rb +100 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or/single_path.rb +72 -0
- data/dry-schema-1.16.0/lib/dry/schema/message/or.rb +30 -0
- data/dry-schema-1.16.0/lib/dry/schema/message.rb +116 -0
- data/dry-schema-1.16.0/lib/dry/schema/message_compiler/visitor_opts.rb +56 -0
- data/dry-schema-1.16.0/lib/dry/schema/message_compiler.rb +234 -0
- data/dry-schema-1.16.0/lib/dry/schema/message_set.rb +144 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/abstract.rb +202 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/i18n.rb +139 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/namespaced.rb +93 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/template.rb +43 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages/yaml.rb +209 -0
- data/dry-schema-1.16.0/lib/dry/schema/messages.rb +32 -0
- data/dry-schema-1.16.0/lib/dry/schema/namespaced_rule.rb +38 -0
- data/dry-schema-1.16.0/lib/dry/schema/params.rb +17 -0
- data/dry-schema-1.16.0/lib/dry/schema/path.rb +107 -0
- data/dry-schema-1.16.0/lib/dry/schema/predicate.rb +102 -0
- data/dry-schema-1.16.0/lib/dry/schema/predicate_inferrer.rb +25 -0
- data/dry-schema-1.16.0/lib/dry/schema/predicate_registry.rb +22 -0
- data/dry-schema-1.16.0/lib/dry/schema/primitive_inferrer.rb +22 -0
- data/dry-schema-1.16.0/lib/dry/schema/processor.rb +213 -0
- data/dry-schema-1.16.0/lib/dry/schema/processor_steps.rb +141 -0
- data/dry-schema-1.16.0/lib/dry/schema/result.rb +203 -0
- data/dry-schema-1.16.0/lib/dry/schema/rule_applier.rb +47 -0
- data/dry-schema-1.16.0/lib/dry/schema/step.rb +59 -0
- data/dry-schema-1.16.0/lib/dry/schema/trace.rb +114 -0
- data/dry-schema-1.16.0/lib/dry/schema/type_container.rb +27 -0
- data/dry-schema-1.16.0/lib/dry/schema/type_registry.rb +44 -0
- data/dry-schema-1.16.0/lib/dry/schema/types.rb +14 -0
- data/dry-schema-1.16.0/lib/dry/schema/types_merger.rb +139 -0
- data/dry-schema-1.16.0/lib/dry/schema/value_coercer.rb +36 -0
- data/dry-schema-1.16.0/lib/dry/schema/version.rb +7 -0
- data/dry-schema-1.16.0/lib/dry/schema.rb +114 -0
- data/dry-schema-1.16.0/lib/dry-schema.rb +3 -0
- data/mega-sharp-sys.gemspec +11 -0
- metadata +114 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Schema
|
|
5
|
+
module Messages
|
|
6
|
+
# Namespaced messages backend
|
|
7
|
+
#
|
|
8
|
+
# @api public
|
|
9
|
+
class Namespaced < ::Dry::Schema::Messages::Abstract
|
|
10
|
+
# @api private
|
|
11
|
+
attr_reader :namespace
|
|
12
|
+
|
|
13
|
+
# @api private
|
|
14
|
+
attr_reader :messages
|
|
15
|
+
|
|
16
|
+
# @api private
|
|
17
|
+
attr_reader :config
|
|
18
|
+
|
|
19
|
+
# @api private
|
|
20
|
+
attr_reader :call_opts
|
|
21
|
+
|
|
22
|
+
# @api private
|
|
23
|
+
def initialize(namespace, messages)
|
|
24
|
+
super()
|
|
25
|
+
@config = messages.config
|
|
26
|
+
@namespace = namespace
|
|
27
|
+
@messages = messages
|
|
28
|
+
@call_opts = {namespace: namespace}.freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get a message for the given key and its options
|
|
32
|
+
#
|
|
33
|
+
# @param [Symbol] key
|
|
34
|
+
# @param [Hash] options
|
|
35
|
+
#
|
|
36
|
+
# @return [String]
|
|
37
|
+
#
|
|
38
|
+
# @api public
|
|
39
|
+
def get(key, options = {})
|
|
40
|
+
messages.get(key, options)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @api public
|
|
44
|
+
def call(key, options = {})
|
|
45
|
+
super(key, options.empty? ? call_opts : options.merge(call_opts))
|
|
46
|
+
end
|
|
47
|
+
alias_method :[], :call
|
|
48
|
+
|
|
49
|
+
# Check if given key is defined
|
|
50
|
+
#
|
|
51
|
+
# @return [Boolean]
|
|
52
|
+
#
|
|
53
|
+
# @api public
|
|
54
|
+
def key?(key, *args)
|
|
55
|
+
messages.key?(key, *args)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @api private
|
|
59
|
+
def filled_lookup_paths(tokens)
|
|
60
|
+
super(tokens.merge(root: "#{tokens[:root]}.#{namespace}")) + super
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @api private
|
|
64
|
+
def rule_lookup_paths(tokens)
|
|
65
|
+
base_paths = messages.rule_lookup_paths(tokens)
|
|
66
|
+
base_paths.map { |key|
|
|
67
|
+
key.sub(config.top_namespace, "#{config.top_namespace}.#{namespace}")
|
|
68
|
+
} + base_paths
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @api private
|
|
72
|
+
def cache_key(predicate, options)
|
|
73
|
+
messages.cache_key(predicate, options)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api private
|
|
77
|
+
def interpolatable_data(key, options, **data)
|
|
78
|
+
messages.interpolatable_data(key, options, **data)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @api private
|
|
82
|
+
def interpolate(key, options, **data)
|
|
83
|
+
messages.interpolate(key, options, **data)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @api private
|
|
87
|
+
def translate(key, **args)
|
|
88
|
+
messages.translate(key, **args)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/initializer"
|
|
4
|
+
require "dry/core/equalizer"
|
|
5
|
+
|
|
6
|
+
require "dry/schema/constants"
|
|
7
|
+
|
|
8
|
+
module Dry
|
|
9
|
+
module Schema
|
|
10
|
+
module Messages
|
|
11
|
+
# @api private
|
|
12
|
+
class Template
|
|
13
|
+
extend ::Dry::Initializer
|
|
14
|
+
include ::Dry::Equalizer(:messages, :key, :options)
|
|
15
|
+
|
|
16
|
+
option :messages
|
|
17
|
+
option :key
|
|
18
|
+
option :options
|
|
19
|
+
|
|
20
|
+
# @api private
|
|
21
|
+
def data(data = EMPTY_HASH)
|
|
22
|
+
ensure_message!
|
|
23
|
+
messages.interpolatable_data(key, options, **options, **data)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @api private
|
|
27
|
+
def call(data = EMPTY_HASH)
|
|
28
|
+
ensure_message!
|
|
29
|
+
messages.interpolate(key, options, **data)
|
|
30
|
+
end
|
|
31
|
+
alias_method :[], :call
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def ensure_message!
|
|
36
|
+
return if messages.key?(key, options)
|
|
37
|
+
|
|
38
|
+
raise KeyError, "No message found for template, template=#{inspect}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
require "dry/schema/constants"
|
|
7
|
+
|
|
8
|
+
module Dry
|
|
9
|
+
module Schema
|
|
10
|
+
module Messages
|
|
11
|
+
# Plain YAML message backend
|
|
12
|
+
#
|
|
13
|
+
# @api public
|
|
14
|
+
class YAML < Abstract
|
|
15
|
+
LOCALE_TOKEN = "%<locale>s"
|
|
16
|
+
TOKEN_REGEXP = /%{(\w*)}/
|
|
17
|
+
EMPTY_CONTEXT = ::Object.new.tap { |ctx|
|
|
18
|
+
def ctx.context
|
|
19
|
+
binding
|
|
20
|
+
end
|
|
21
|
+
}.freeze.context
|
|
22
|
+
|
|
23
|
+
include ::Dry::Equalizer(:data)
|
|
24
|
+
|
|
25
|
+
# Loaded localized message templates
|
|
26
|
+
#
|
|
27
|
+
# @return [Hash]
|
|
28
|
+
attr_reader :data
|
|
29
|
+
|
|
30
|
+
# Translation function
|
|
31
|
+
#
|
|
32
|
+
# @return [Proc]
|
|
33
|
+
attr_reader :t
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
def self.build(options = EMPTY_HASH)
|
|
37
|
+
super do |config|
|
|
38
|
+
config.default_locale = :en unless config.default_locale
|
|
39
|
+
|
|
40
|
+
config.root = -"%<locale>s.#{config.root}"
|
|
41
|
+
|
|
42
|
+
config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
|
|
43
|
+
-"%<locale>s.#{path}"
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @api private
|
|
49
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
|
50
|
+
def self.flat_hash(hash, path = EMPTY_ARRAY, keys = {})
|
|
51
|
+
hash.each do |key, value|
|
|
52
|
+
flat_hash(value, [*path, key], keys) if value.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
if value.is_a?(String) && hash["text"] != value
|
|
55
|
+
keys[[*path, key].join(DOT)] = {
|
|
56
|
+
text: value,
|
|
57
|
+
meta: EMPTY_HASH
|
|
58
|
+
}
|
|
59
|
+
elsif value.is_a?(Hash) && value["text"].is_a?(String)
|
|
60
|
+
keys[[*path, key].join(DOT)] = {
|
|
61
|
+
text: value["text"],
|
|
62
|
+
meta: value.reject { _1.eql?("text") }.transform_keys(&:to_sym)
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
keys
|
|
68
|
+
end
|
|
69
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
|
70
|
+
|
|
71
|
+
# @api private
|
|
72
|
+
def self.cache
|
|
73
|
+
@cache ||= Concurrent::Map.new do |h, k|
|
|
74
|
+
h.compute_if_absent(k) { Concurrent::Map.new }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @api private
|
|
79
|
+
def self.source_cache
|
|
80
|
+
@source_cache ||= Concurrent::Map.new
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @api private
|
|
84
|
+
def initialize(data: EMPTY_HASH, config: nil)
|
|
85
|
+
super()
|
|
86
|
+
@data = data
|
|
87
|
+
@__config__ = config if config
|
|
88
|
+
@t = proc { |key, locale: default_locale| get("%<locale>s.#{key}", locale: locale) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get an array of looked up paths
|
|
92
|
+
#
|
|
93
|
+
# @param [Symbol] predicate
|
|
94
|
+
# @param [Hash] options
|
|
95
|
+
#
|
|
96
|
+
# @return [String]
|
|
97
|
+
#
|
|
98
|
+
# @api public
|
|
99
|
+
def looked_up_paths(predicate, options)
|
|
100
|
+
super.map { |path| path % {locale: options[:locale] || default_locale} }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get a message for the given key and its options
|
|
104
|
+
#
|
|
105
|
+
# @param [Symbol] key
|
|
106
|
+
# @param [Hash] options
|
|
107
|
+
#
|
|
108
|
+
# @return [String]
|
|
109
|
+
#
|
|
110
|
+
# @api public
|
|
111
|
+
def get(key, options = EMPTY_HASH)
|
|
112
|
+
data[evaluated_key(key, options)]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check if given key is defined
|
|
116
|
+
#
|
|
117
|
+
# @return [Boolean]
|
|
118
|
+
#
|
|
119
|
+
# @api public
|
|
120
|
+
def key?(key, options = EMPTY_HASH)
|
|
121
|
+
data.key?(evaluated_key(key, options))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Merge messages from an additional path
|
|
125
|
+
#
|
|
126
|
+
# @param [String] overrides
|
|
127
|
+
#
|
|
128
|
+
# @return [Messages::I18n]
|
|
129
|
+
#
|
|
130
|
+
# @api public
|
|
131
|
+
def merge(overrides)
|
|
132
|
+
if overrides.is_a?(Hash)
|
|
133
|
+
self.class.new(
|
|
134
|
+
data: data.merge(self.class.flat_hash(overrides)),
|
|
135
|
+
config: config
|
|
136
|
+
)
|
|
137
|
+
else
|
|
138
|
+
self.class.new(
|
|
139
|
+
data: Array(overrides).reduce(data) { |a, e| a.merge(load_translations(e)) },
|
|
140
|
+
config: config
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @api private
|
|
146
|
+
def prepare
|
|
147
|
+
@data = config.load_paths.map { |path| load_translations(path) }.reduce({}, :merge)
|
|
148
|
+
self
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @api private
|
|
152
|
+
def interpolatable_data(key, options, **data)
|
|
153
|
+
tokens = evaluation_context(key, options).fetch(:tokens)
|
|
154
|
+
data.select { |k,| tokens.include?(k) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @api private
|
|
158
|
+
def interpolate(key, options, **data)
|
|
159
|
+
evaluator = evaluation_context(key, options).fetch(:evaluator)
|
|
160
|
+
data.empty? ? evaluator.() : evaluator.(**data)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
# @api private
|
|
166
|
+
def evaluation_context(key, options)
|
|
167
|
+
cache.fetch_or_store(get(key, options).fetch(:text)) do |input|
|
|
168
|
+
tokens = input.scan(TOKEN_REGEXP).flatten(1).to_set(&:to_sym)
|
|
169
|
+
text = input.gsub("%", "#")
|
|
170
|
+
|
|
171
|
+
# rubocop:disable Security/Eval
|
|
172
|
+
evaluator = eval(<<~RUBY, EMPTY_CONTEXT, __FILE__, __LINE__ + 1)
|
|
173
|
+
-> (#{tokens.map { |token| "#{token}:" }.join(", ")}) { "#{text}" } # -> (a:, b:) { "Translation #\{a} #\{b}" }
|
|
174
|
+
RUBY
|
|
175
|
+
# rubocop:enable Security/Eval
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
tokens: tokens,
|
|
179
|
+
evaluator: evaluator
|
|
180
|
+
}
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @api private
|
|
185
|
+
def cache
|
|
186
|
+
@cache ||= self.class.cache[self]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @api private
|
|
190
|
+
def load_translations(path)
|
|
191
|
+
data = self.class.source_cache.fetch_or_store(path) do
|
|
192
|
+
self.class.flat_hash(::YAML.load_file(path)).freeze
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
return data unless custom_top_namespace?(path)
|
|
196
|
+
|
|
197
|
+
data.transform_keys { _1.gsub(DEFAULT_MESSAGES_ROOT, config.top_namespace) }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @api private
|
|
201
|
+
def evaluated_key(key, options)
|
|
202
|
+
return key unless key.include?(LOCALE_TOKEN)
|
|
203
|
+
|
|
204
|
+
key % {locale: options[:locale] || default_locale}
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Schema
|
|
5
|
+
# An API for configuring message backends
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
module Messages
|
|
9
|
+
BACKENDS = {
|
|
10
|
+
i18n: "I18n",
|
|
11
|
+
yaml: "YAML"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
public def setup(config)
|
|
17
|
+
backend_class = BACKENDS.fetch(config.backend) do
|
|
18
|
+
raise "+#{config.backend}+ is not a valid messages identifier"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
namespace = config.namespace
|
|
22
|
+
options = config.to_h.select { |k, _| Abstract.setting_names.include?(k) }
|
|
23
|
+
|
|
24
|
+
messages = Messages.const_get(backend_class).build(options)
|
|
25
|
+
|
|
26
|
+
return messages.namespaced(namespace) if namespace
|
|
27
|
+
|
|
28
|
+
messages
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Schema
|
|
5
|
+
# A special rule type that is configured under a specified namespace
|
|
6
|
+
#
|
|
7
|
+
# This is used internally to create rules that can be properly handled
|
|
8
|
+
# by the message compiler in situations where a schema reuses another schema
|
|
9
|
+
# but it is configured to use a message namespace
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class NamespacedRule
|
|
13
|
+
# @api private
|
|
14
|
+
attr_reader :rule
|
|
15
|
+
|
|
16
|
+
# @api private
|
|
17
|
+
attr_reader :namespace
|
|
18
|
+
|
|
19
|
+
# @api private
|
|
20
|
+
def initialize(namespace, rule)
|
|
21
|
+
@namespace = namespace
|
|
22
|
+
@rule = rule
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
def call(input)
|
|
27
|
+
result = rule.call(input)
|
|
28
|
+
Logic::Result.new(result.success?) { [:namespace, [namespace, result.to_ast]] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @api private
|
|
32
|
+
def ast(input = Undefined)
|
|
33
|
+
[:namespace, [namespace, rule.ast(input)]]
|
|
34
|
+
end
|
|
35
|
+
alias_method :to_ast, :ast
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Schema
|
|
5
|
+
# Params schema type
|
|
6
|
+
#
|
|
7
|
+
# @see Processor
|
|
8
|
+
# @see Schema#Params
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
class Params < Processor
|
|
12
|
+
config.key_map_type = :stringified
|
|
13
|
+
config.type_registry_namespace = :params
|
|
14
|
+
config.filter_empty_string = true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/schema/constants"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
# Path represents a list of keys in a hash
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
class Path
|
|
11
|
+
include ::Dry.Equalizer(:keys)
|
|
12
|
+
include ::Comparable
|
|
13
|
+
include ::Enumerable
|
|
14
|
+
|
|
15
|
+
# @return [Array<Symbol>]
|
|
16
|
+
attr_reader :keys
|
|
17
|
+
|
|
18
|
+
alias_method :root, :first
|
|
19
|
+
|
|
20
|
+
# Coerce a spec into a path object
|
|
21
|
+
#
|
|
22
|
+
# @param [Path, Symbol, String, Hash, Array<Symbol>] spec
|
|
23
|
+
#
|
|
24
|
+
# @return [Path]
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
def self.call(spec)
|
|
28
|
+
case spec
|
|
29
|
+
when ::Symbol, ::Array
|
|
30
|
+
new([*spec])
|
|
31
|
+
when ::String
|
|
32
|
+
new(spec.split(DOT).map(&:to_sym))
|
|
33
|
+
when ::Hash
|
|
34
|
+
new(keys_from_hash(spec))
|
|
35
|
+
when self
|
|
36
|
+
spec
|
|
37
|
+
else
|
|
38
|
+
raise ::ArgumentError, "+spec+ must be either a Symbol, Array, Hash or a #{name}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @api private
|
|
43
|
+
def self.[](spec)
|
|
44
|
+
call(spec)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Extract a list of keys from a hash
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
def self.keys_from_hash(hash)
|
|
51
|
+
hash.inject([]) { |a, (k, v)|
|
|
52
|
+
v.is_a?(::Hash) ? a.push(k, *keys_from_hash(v)) : a.push(k, v)
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @api private
|
|
57
|
+
def initialize(keys)
|
|
58
|
+
@keys = keys
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @api private
|
|
62
|
+
def to_h(value = EMPTY_ARRAY.dup)
|
|
63
|
+
value = [value] unless value.is_a?(::Array)
|
|
64
|
+
|
|
65
|
+
keys.reverse_each.reduce(value) { |result, key| {key => result} }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @api private
|
|
69
|
+
def each(&)
|
|
70
|
+
keys.each(&)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @api private
|
|
74
|
+
def include?(other)
|
|
75
|
+
keys[0, other.keys.length].eql?(other.keys)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @api private
|
|
79
|
+
def <=>(other)
|
|
80
|
+
return keys.length <=> other.keys.length if include?(other) || other.include?(self)
|
|
81
|
+
|
|
82
|
+
first_uncommon_index = (self & other).keys.length
|
|
83
|
+
|
|
84
|
+
keys[first_uncommon_index] <=> other.keys[first_uncommon_index]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @api private
|
|
88
|
+
def &(other)
|
|
89
|
+
self.class.new(
|
|
90
|
+
keys.take_while.with_index { |key, index| other.keys[index].eql?(key) }
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @api private
|
|
95
|
+
def last
|
|
96
|
+
keys.last
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @api private
|
|
100
|
+
def same_root?(other)
|
|
101
|
+
root.equal?(other.root)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
EMPTY = new(EMPTY_ARRAY).freeze
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/equalizer"
|
|
4
|
+
require "dry/logic/operators"
|
|
5
|
+
|
|
6
|
+
module Dry
|
|
7
|
+
module Schema
|
|
8
|
+
# Predicate objects used within the DSL
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
11
|
+
class Predicate
|
|
12
|
+
# A negated predicate
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
class Negation
|
|
16
|
+
include ::Dry::Logic::Operators
|
|
17
|
+
|
|
18
|
+
# @api private
|
|
19
|
+
attr_reader :predicate
|
|
20
|
+
|
|
21
|
+
# @api private
|
|
22
|
+
def initialize(predicate)
|
|
23
|
+
@predicate = predicate
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Dump negated predicate to an AST
|
|
27
|
+
#
|
|
28
|
+
# @return [Array]
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
def to_ast(...)
|
|
32
|
+
[:not, predicate.to_ast(...)]
|
|
33
|
+
end
|
|
34
|
+
alias_method :ast, :to_ast
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
include ::Dry::Logic::Operators
|
|
38
|
+
include ::Dry::Equalizer(:name, :args, :block)
|
|
39
|
+
|
|
40
|
+
# @api private
|
|
41
|
+
attr_reader :compiler
|
|
42
|
+
|
|
43
|
+
# @api private
|
|
44
|
+
attr_reader :name
|
|
45
|
+
|
|
46
|
+
# @api private
|
|
47
|
+
attr_reader :args
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
attr_reader :arity
|
|
51
|
+
|
|
52
|
+
# @api private
|
|
53
|
+
attr_reader :block
|
|
54
|
+
|
|
55
|
+
# @api private
|
|
56
|
+
def initialize(compiler, name, args, block)
|
|
57
|
+
@compiler = compiler
|
|
58
|
+
@name = name
|
|
59
|
+
@args = args
|
|
60
|
+
@block = block
|
|
61
|
+
# Cater for optional second argument like in case of `eql?` or `respond_to?`
|
|
62
|
+
@arity = compiler.predicates[name].arity.abs
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Negate a predicate
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# required(:name).value(:string) { !empty? }
|
|
69
|
+
#
|
|
70
|
+
# @return [Negation]
|
|
71
|
+
#
|
|
72
|
+
# @api public
|
|
73
|
+
def !
|
|
74
|
+
Negation.new(self)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @api private
|
|
78
|
+
def ensure_valid
|
|
79
|
+
if arity - 1 != args.size
|
|
80
|
+
raise ::ArgumentError, "#{name} predicate arity is invalid"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Compile predicate to a rule object
|
|
85
|
+
#
|
|
86
|
+
# @api private
|
|
87
|
+
def to_rule
|
|
88
|
+
compiler.visit(to_ast)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Dump predicate to an AST
|
|
92
|
+
#
|
|
93
|
+
# @return [Array]
|
|
94
|
+
#
|
|
95
|
+
# @api private
|
|
96
|
+
def to_ast(*)
|
|
97
|
+
[:predicate, [name, compiler.predicates.arg_list(name, *args)]]
|
|
98
|
+
end
|
|
99
|
+
alias_method :ast, :to_ast
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Schema
|
|
5
|
+
# @api private
|
|
6
|
+
class PredicateInferrer < ::Dry::Types::PredicateInferrer
|
|
7
|
+
Compiler = ::Class.new(superclass::Compiler) do
|
|
8
|
+
# @api private
|
|
9
|
+
def visit_intersection(node)
|
|
10
|
+
left_node, right_node, = node
|
|
11
|
+
left = visit(left_node)
|
|
12
|
+
right = visit(right_node)
|
|
13
|
+
|
|
14
|
+
[left, right].flatten.compact
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(registry = PredicateRegistry.new)
|
|
19
|
+
super
|
|
20
|
+
|
|
21
|
+
@compiler = Compiler.new(registry)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Schema
|
|
5
|
+
# A registry with predicate objects from `Dry::Logic::Predicates`
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class PredicateRegistry < ::Dry::Types::PredicateRegistry
|
|
9
|
+
# @api private
|
|
10
|
+
def arg_list(name, *values)
|
|
11
|
+
predicate = self[name]
|
|
12
|
+
# Cater for optional second argument like in case of `eql?` or `respond_to?`
|
|
13
|
+
arity = predicate.arity.abs
|
|
14
|
+
|
|
15
|
+
predicate
|
|
16
|
+
.parameters
|
|
17
|
+
.map(&:last)
|
|
18
|
+
.zip(values + ::Array.new(arity - values.size, Undefined))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|