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,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/initializer"
|
|
4
|
+
require "dry/schema/constants"
|
|
5
|
+
|
|
6
|
+
module Dry
|
|
7
|
+
module Schema
|
|
8
|
+
# Compiles rule results AST into human-readable format
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
class MessageCompiler
|
|
12
|
+
extend ::Dry::Initializer
|
|
13
|
+
|
|
14
|
+
resolve_key_predicate = proc { |node, opts|
|
|
15
|
+
*arg_vals, val = node.map(&:last)
|
|
16
|
+
[[*opts.path, arg_vals[0]], arg_vals[1..arg_vals.size - 1], val]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
resolve_predicate = proc { |node, opts|
|
|
20
|
+
[Array(opts.path), *node.map(&:last)]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
DEFAULT_PREDICATE_RESOLVERS = ::Hash
|
|
24
|
+
.new(resolve_predicate).update(key?: resolve_key_predicate).freeze
|
|
25
|
+
|
|
26
|
+
EMPTY_OPTS = VisitorOpts.new
|
|
27
|
+
EMPTY_MESSAGE_SET = MessageSet.new(EMPTY_ARRAY).freeze
|
|
28
|
+
FULL_MESSAGE_WHITESPACE = ::Hash.new(" ").merge(
|
|
29
|
+
ja: "",
|
|
30
|
+
zh: "",
|
|
31
|
+
bn: "",
|
|
32
|
+
th: "",
|
|
33
|
+
lo: "",
|
|
34
|
+
my: ""
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
param :messages
|
|
38
|
+
|
|
39
|
+
option :full, default: -> { false }
|
|
40
|
+
option :locale, default: -> { :en }
|
|
41
|
+
option :predicate_resolvers, default: -> { DEFAULT_PREDICATE_RESOLVERS }
|
|
42
|
+
|
|
43
|
+
attr_reader :options
|
|
44
|
+
|
|
45
|
+
attr_reader :default_lookup_options
|
|
46
|
+
|
|
47
|
+
# @api private
|
|
48
|
+
def initialize(messages, **options)
|
|
49
|
+
super
|
|
50
|
+
@options = options
|
|
51
|
+
@default_lookup_options = options[:locale] ? {locale: locale} : EMPTY_HASH
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @api private
|
|
55
|
+
def with(new_options)
|
|
56
|
+
return self if new_options.empty?
|
|
57
|
+
|
|
58
|
+
updated_opts = options.merge(new_options)
|
|
59
|
+
|
|
60
|
+
return self if updated_opts.eql?(options)
|
|
61
|
+
|
|
62
|
+
self.class.new(messages, **updated_opts)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @api private
|
|
66
|
+
def call(ast)
|
|
67
|
+
return EMPTY_MESSAGE_SET if ast.empty?
|
|
68
|
+
|
|
69
|
+
current_messages = EMPTY_ARRAY.dup
|
|
70
|
+
compiled_messages = ast.map { |node| visit(node, EMPTY_OPTS.dup(current_messages)) }
|
|
71
|
+
|
|
72
|
+
MessageSet[compiled_messages, failures: options.fetch(:failures, true)]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @api private
|
|
76
|
+
def visit(node, opts = EMPTY_OPTS.dup)
|
|
77
|
+
__send__(:"visit_#{node[0]}", node[1], opts)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @api private
|
|
81
|
+
def visit_failure(node, opts)
|
|
82
|
+
rule, other = node
|
|
83
|
+
visit(other, opts.(rule: rule))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @api private
|
|
87
|
+
def visit_hint(*)
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @api private
|
|
92
|
+
def visit_not(node, opts)
|
|
93
|
+
visit(node, opts.(not: true))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @api private
|
|
97
|
+
def visit_and(node, opts)
|
|
98
|
+
left, right = node.map { |n| visit(n, opts) }
|
|
99
|
+
|
|
100
|
+
if right
|
|
101
|
+
[left, right]
|
|
102
|
+
else
|
|
103
|
+
left
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @api private
|
|
108
|
+
def visit_unexpected_key(node, opts)
|
|
109
|
+
visit_predicate([:unexpected_key, []], opts.dup.update(path: Path[node.first]))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @api private
|
|
113
|
+
def visit_or(node, opts)
|
|
114
|
+
left, right = node.map { |n| visit(n, opts) }
|
|
115
|
+
Message::Or[left, right, or_translator]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @api private
|
|
119
|
+
def or_translator
|
|
120
|
+
@or_translator ||= proc { |k|
|
|
121
|
+
message = messages.translate(k, **default_lookup_options)
|
|
122
|
+
message.is_a?(Hash) ? message[:text] : message
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @api private
|
|
127
|
+
def visit_namespace(node, opts)
|
|
128
|
+
ns, rest = node
|
|
129
|
+
self.class.new(messages.namespaced(ns), **options).visit(rest, opts)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @api private
|
|
133
|
+
def visit_predicate(node, opts)
|
|
134
|
+
predicate, args = node
|
|
135
|
+
|
|
136
|
+
tokens = message_tokens(args)
|
|
137
|
+
path, *arg_vals, input = predicate_resolvers[predicate].(args, opts)
|
|
138
|
+
|
|
139
|
+
options = opts.dup.update(
|
|
140
|
+
path: path.last, **tokens, **lookup_options(arg_vals: arg_vals, input: input)
|
|
141
|
+
).to_h
|
|
142
|
+
|
|
143
|
+
template, meta = messages[predicate, options]
|
|
144
|
+
|
|
145
|
+
unless template
|
|
146
|
+
raise MissingMessageError.new(path, messages.looked_up_paths(predicate, options))
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
text = message_text(template, tokens, options)
|
|
150
|
+
|
|
151
|
+
message_type(options).new(
|
|
152
|
+
text: text,
|
|
153
|
+
meta: meta,
|
|
154
|
+
path: path,
|
|
155
|
+
predicate: predicate,
|
|
156
|
+
args: arg_vals,
|
|
157
|
+
input: input
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @api private
|
|
162
|
+
def message_type(*)
|
|
163
|
+
Message
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @api private
|
|
167
|
+
def visit_key(node, opts)
|
|
168
|
+
name, other = node
|
|
169
|
+
visit(other, opts.(path: name))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# @api private
|
|
173
|
+
def visit_set(node, opts)
|
|
174
|
+
node.map { |el| visit(el, opts) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @api private
|
|
178
|
+
def visit_implication(node, *args)
|
|
179
|
+
_, right = node
|
|
180
|
+
visit(right, *args)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @api private
|
|
184
|
+
def visit_xor(node, opts)
|
|
185
|
+
left, right = node
|
|
186
|
+
[visit(left, opts), visit(right, opts)].uniq
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @api private
|
|
190
|
+
def lookup_options(arg_vals:, input:)
|
|
191
|
+
default_lookup_options.merge(
|
|
192
|
+
arg_type: arg_vals.size == 1 && arg_vals[0].class,
|
|
193
|
+
val_type: input.equal?(Undefined) ? ::NilClass : input.class
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @api private
|
|
198
|
+
def message_text(template, tokens, options)
|
|
199
|
+
text = template[template.data(tokens)]
|
|
200
|
+
|
|
201
|
+
return text if !text || !full
|
|
202
|
+
|
|
203
|
+
rule = options[:path]
|
|
204
|
+
[messages.rule(rule, options) || rule,
|
|
205
|
+
text].join(FULL_MESSAGE_WHITESPACE[template.options[:locale]])
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# @api private
|
|
209
|
+
def message_tokens(args)
|
|
210
|
+
tokens = args.each_with_object({}) do |arg, hash|
|
|
211
|
+
case arg[1]
|
|
212
|
+
when ::Array, ::Set
|
|
213
|
+
hash[arg[0]] = arg[1].join(LIST_SEPARATOR)
|
|
214
|
+
when ::Range
|
|
215
|
+
hash[:"#{arg[0]}_left"] = arg[1].first
|
|
216
|
+
hash[:"#{arg[0]}_right"] = arg[1].last
|
|
217
|
+
else
|
|
218
|
+
hash[arg[0]] = arg[1]
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
args.any? { |e| e.first == :size } ? append_mapped_size_tokens(tokens) : tokens
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# @api private
|
|
225
|
+
def append_mapped_size_tokens(tokens)
|
|
226
|
+
# this is a temporary fix for the inconsistency in the "size" errors arguments
|
|
227
|
+
mapped_hash = tokens.transform_keys do |k|
|
|
228
|
+
k.to_s.gsub("size", "num").to_sym
|
|
229
|
+
end
|
|
230
|
+
tokens.merge(mapped_hash)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/equalizer"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Schema
|
|
7
|
+
# A set of messages used to generate errors
|
|
8
|
+
#
|
|
9
|
+
# @see Result#message_set
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
class MessageSet
|
|
13
|
+
include ::Enumerable
|
|
14
|
+
include ::Dry::Equalizer(:messages, :options)
|
|
15
|
+
|
|
16
|
+
# A list of compiled message objects
|
|
17
|
+
#
|
|
18
|
+
# @return [Array<Message>]
|
|
19
|
+
attr_reader :messages
|
|
20
|
+
|
|
21
|
+
# Options hash
|
|
22
|
+
#
|
|
23
|
+
# @return [Hash]
|
|
24
|
+
attr_reader :options
|
|
25
|
+
|
|
26
|
+
# @api private
|
|
27
|
+
def self.[](messages, options = EMPTY_HASH)
|
|
28
|
+
new(messages.flatten, options)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @api private
|
|
32
|
+
def initialize(messages, options = EMPTY_HASH)
|
|
33
|
+
@messages = messages
|
|
34
|
+
@options = options
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Iterate over messages
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# result.errors.each do |message|
|
|
41
|
+
# puts message.text
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# @return [Array]
|
|
45
|
+
#
|
|
46
|
+
# @api public
|
|
47
|
+
def each(&)
|
|
48
|
+
return self if empty?
|
|
49
|
+
return to_enum unless block_given?
|
|
50
|
+
|
|
51
|
+
messages.each(&)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Dump message set to a hash
|
|
55
|
+
#
|
|
56
|
+
# @return [Hash<Symbol=>Array<String>>]
|
|
57
|
+
#
|
|
58
|
+
# @api public
|
|
59
|
+
def to_h
|
|
60
|
+
@to_h ||= messages_map
|
|
61
|
+
end
|
|
62
|
+
alias_method :to_hash, :to_h
|
|
63
|
+
|
|
64
|
+
# Get a list of message texts for the given key
|
|
65
|
+
#
|
|
66
|
+
# @param [Symbol] key
|
|
67
|
+
#
|
|
68
|
+
# @return [Array<String>]
|
|
69
|
+
#
|
|
70
|
+
# @api public
|
|
71
|
+
def [](key)
|
|
72
|
+
to_h[key]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get a list of message texts for the given key
|
|
76
|
+
#
|
|
77
|
+
# @param [Symbol] key
|
|
78
|
+
#
|
|
79
|
+
# @return [Array<String>]
|
|
80
|
+
#
|
|
81
|
+
# @raise KeyError
|
|
82
|
+
#
|
|
83
|
+
# @api public
|
|
84
|
+
def fetch(key)
|
|
85
|
+
self[key] || raise(::KeyError, "+#{key}+ message was not found")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if a message set is empty
|
|
89
|
+
#
|
|
90
|
+
# @return [Boolean]
|
|
91
|
+
#
|
|
92
|
+
# @api public
|
|
93
|
+
def empty?
|
|
94
|
+
@empty ||= messages.empty?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @api private
|
|
98
|
+
def freeze
|
|
99
|
+
to_h
|
|
100
|
+
empty?
|
|
101
|
+
super
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# @api private
|
|
107
|
+
def messages_map(messages = self.messages)
|
|
108
|
+
combine_message_hashes(messages.map(&:to_h)).freeze
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @api private
|
|
112
|
+
def combine_message_hashes(hashes)
|
|
113
|
+
hashes.reduce(EMPTY_HASH.dup) do |a, e|
|
|
114
|
+
a.merge(e) do |_, *values|
|
|
115
|
+
combine_message_values(values)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @api private
|
|
121
|
+
def combine_message_values(values)
|
|
122
|
+
hashes, other = partition_message_values(values)
|
|
123
|
+
combined = combine_message_hashes(hashes)
|
|
124
|
+
flattened = other.flatten
|
|
125
|
+
|
|
126
|
+
if flattened.empty?
|
|
127
|
+
combined
|
|
128
|
+
elsif combined.empty?
|
|
129
|
+
flattened
|
|
130
|
+
else
|
|
131
|
+
[flattened, combined]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @api private
|
|
136
|
+
def partition_message_values(values)
|
|
137
|
+
values
|
|
138
|
+
.map { |value| value.is_a?(::Array) ? value : [value] }
|
|
139
|
+
.reduce(EMPTY_ARRAY.dup, :+)
|
|
140
|
+
.partition { |value| value.is_a?(::Hash) && !value[:text].is_a?(::String) }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
|
|
5
|
+
require "dry/schema/constants"
|
|
6
|
+
|
|
7
|
+
module Dry
|
|
8
|
+
module Schema
|
|
9
|
+
module Messages
|
|
10
|
+
# Abstract class for message backends
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
class Abstract
|
|
14
|
+
include ::Dry::Configurable
|
|
15
|
+
include ::Dry::Equalizer(:config)
|
|
16
|
+
|
|
17
|
+
setting :default_locale
|
|
18
|
+
setting :load_paths, default: ::Set[DEFAULT_MESSAGES_PATH]
|
|
19
|
+
setting :top_namespace, default: DEFAULT_MESSAGES_ROOT
|
|
20
|
+
setting :root, default: "errors"
|
|
21
|
+
setting :lookup_options, default: %i[root predicate path val_type arg_type].freeze
|
|
22
|
+
|
|
23
|
+
setting :lookup_paths, default: [
|
|
24
|
+
"%<root>s.rules.%<path>s.%<predicate>s.arg.%<arg_type>s",
|
|
25
|
+
"%<root>s.rules.%<path>s.%<predicate>s",
|
|
26
|
+
"%<root>s.%<predicate>s.%<message_type>s",
|
|
27
|
+
"%<root>s.%<predicate>s.value.%<path>s",
|
|
28
|
+
"%<root>s.%<predicate>s.value.%<val_type>s.arg.%<arg_type>s",
|
|
29
|
+
"%<root>s.%<predicate>s.value.%<val_type>s",
|
|
30
|
+
"%<root>s.%<predicate>s.arg.%<arg_type>s",
|
|
31
|
+
"%<root>s.%<predicate>s"
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
setting :rule_lookup_paths, default: ["rules.%<name>s"].freeze
|
|
35
|
+
|
|
36
|
+
setting :arg_types, default: ::Hash.new { |*| "default" }.update(
|
|
37
|
+
::Range => "range"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
setting :val_types, default: ::Hash.new { |*| "default" }.update(
|
|
41
|
+
::Range => "range",
|
|
42
|
+
::String => "string"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# @api private
|
|
46
|
+
def self.setting_names
|
|
47
|
+
@setting_names ||= settings.map { _1.name.to_sym }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @api private
|
|
51
|
+
def self.build(options = EMPTY_HASH)
|
|
52
|
+
messages = new
|
|
53
|
+
|
|
54
|
+
messages.configure do |config|
|
|
55
|
+
options.each do |key, value|
|
|
56
|
+
config.public_send(:"#{key}=", value)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
config.root = "#{config.top_namespace}.#{config.root}"
|
|
60
|
+
|
|
61
|
+
config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
|
|
62
|
+
"#{config.top_namespace}.#{path}"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
yield(config) if block_given?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
messages.prepare
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @api private
|
|
72
|
+
def translate(key, locale: default_locale)
|
|
73
|
+
t["#{config.top_namespace}.#{key}", locale: locale]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api private
|
|
77
|
+
def rule(name, options = {})
|
|
78
|
+
tokens = {name: name, locale: options.fetch(:locale, default_locale)}
|
|
79
|
+
path = rule_lookup_paths(tokens).detect { |key| key?(key, options) }
|
|
80
|
+
|
|
81
|
+
rule = get(path, options) if path
|
|
82
|
+
rule.is_a?(::Hash) ? rule[:text] : rule
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Retrieve a message template
|
|
86
|
+
#
|
|
87
|
+
# @return [Template]
|
|
88
|
+
#
|
|
89
|
+
# @api public
|
|
90
|
+
def call(predicate, options)
|
|
91
|
+
options = {locale: default_locale, **options}
|
|
92
|
+
opts = options.reject { |k,| config.lookup_options.include?(k) }
|
|
93
|
+
path = lookup_paths(predicate, options).detect { |key| key?(key, opts) }
|
|
94
|
+
|
|
95
|
+
return unless path
|
|
96
|
+
|
|
97
|
+
result = get(path, opts)
|
|
98
|
+
|
|
99
|
+
[
|
|
100
|
+
Template.new(
|
|
101
|
+
messages: self,
|
|
102
|
+
key: path,
|
|
103
|
+
options: opts
|
|
104
|
+
),
|
|
105
|
+
result[:meta]
|
|
106
|
+
]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
alias_method :[], :call
|
|
110
|
+
|
|
111
|
+
# Check if given key is defined
|
|
112
|
+
#
|
|
113
|
+
# @return [Boolean]
|
|
114
|
+
#
|
|
115
|
+
# @api public
|
|
116
|
+
def key?(_key, _options = EMPTY_HASH)
|
|
117
|
+
raise ::NotImplementedError
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Retrieve an array of looked up paths
|
|
121
|
+
#
|
|
122
|
+
# @param [Symbol] predicate
|
|
123
|
+
# @param [Hash] options
|
|
124
|
+
#
|
|
125
|
+
# @return [String]
|
|
126
|
+
#
|
|
127
|
+
# @api public
|
|
128
|
+
def looked_up_paths(predicate, options)
|
|
129
|
+
tokens = lookup_tokens(predicate, options)
|
|
130
|
+
filled_lookup_paths(tokens)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @api private
|
|
134
|
+
def lookup_paths(predicate, options)
|
|
135
|
+
tokens = lookup_tokens(predicate, options)
|
|
136
|
+
filled_lookup_paths(tokens)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @api private
|
|
140
|
+
def filled_lookup_paths(tokens)
|
|
141
|
+
config.lookup_paths.map { |path| path % tokens }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @api private
|
|
145
|
+
def rule_lookup_paths(tokens)
|
|
146
|
+
config.rule_lookup_paths.map { |key| key % tokens }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Return a new message backend that will look for messages under provided namespace
|
|
150
|
+
#
|
|
151
|
+
# @param [Symbol,String] namespace
|
|
152
|
+
#
|
|
153
|
+
# @api public
|
|
154
|
+
def namespaced(namespace)
|
|
155
|
+
Dry::Schema::Messages::Namespaced.new(namespace, self)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Return root path to messages file
|
|
159
|
+
#
|
|
160
|
+
# @return [Pathname]
|
|
161
|
+
#
|
|
162
|
+
# @api public
|
|
163
|
+
def root
|
|
164
|
+
config.root
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @api private
|
|
168
|
+
def default_locale
|
|
169
|
+
config.default_locale
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# @api private
|
|
173
|
+
def interpolatable_data(_key, _options, **_data)
|
|
174
|
+
raise ::NotImplementedError
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @api private
|
|
178
|
+
def interpolate(_key, _options, **_data)
|
|
179
|
+
raise ::NotImplementedError
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
# @api private
|
|
185
|
+
def lookup_tokens(predicate, options)
|
|
186
|
+
options.merge(
|
|
187
|
+
predicate: predicate,
|
|
188
|
+
root: options[:not] ? "#{root}.not" : root,
|
|
189
|
+
arg_type: config.arg_types[options[:arg_type]],
|
|
190
|
+
val_type: config.val_types[options[:val_type]],
|
|
191
|
+
message_type: options[:message_type] || :failure
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @api private
|
|
196
|
+
def custom_top_namespace?(path)
|
|
197
|
+
path.to_s == DEFAULT_MESSAGES_PATH.to_s && config.top_namespace != DEFAULT_MESSAGES_ROOT
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "i18n"
|
|
5
|
+
|
|
6
|
+
module Dry
|
|
7
|
+
module Schema
|
|
8
|
+
module Messages
|
|
9
|
+
# I18n message backend
|
|
10
|
+
#
|
|
11
|
+
# @api public
|
|
12
|
+
class I18n < Abstract
|
|
13
|
+
# Translation function
|
|
14
|
+
#
|
|
15
|
+
# @return [Method]
|
|
16
|
+
attr_reader :t
|
|
17
|
+
|
|
18
|
+
# @api private
|
|
19
|
+
def initialize
|
|
20
|
+
super
|
|
21
|
+
@t = ::I18n.method(:t)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get a message for the given key and its options
|
|
25
|
+
#
|
|
26
|
+
# @param [Symbol] key
|
|
27
|
+
# @param [Hash] options
|
|
28
|
+
#
|
|
29
|
+
# @return [String]
|
|
30
|
+
#
|
|
31
|
+
# @api public
|
|
32
|
+
def get(key, options = EMPTY_HASH)
|
|
33
|
+
return unless key
|
|
34
|
+
|
|
35
|
+
result = t.(key, locale: default_locale, **options)
|
|
36
|
+
|
|
37
|
+
if result.is_a?(Hash)
|
|
38
|
+
text = result[:text]
|
|
39
|
+
meta = result.dup.tap { |h| h.delete(:text) }
|
|
40
|
+
else
|
|
41
|
+
text = result
|
|
42
|
+
meta = EMPTY_HASH.dup
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
text: text,
|
|
47
|
+
meta: meta
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Check if given key is defined
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean]
|
|
54
|
+
#
|
|
55
|
+
# @api public
|
|
56
|
+
def key?(key, options)
|
|
57
|
+
::I18n.exists?(key, options.fetch(:locale, default_locale)) ||
|
|
58
|
+
::I18n.exists?(key, ::I18n.default_locale)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Merge messages from an additional path
|
|
62
|
+
#
|
|
63
|
+
# @param [String, Array<String>] paths
|
|
64
|
+
#
|
|
65
|
+
# @return [Messages::I18n]
|
|
66
|
+
#
|
|
67
|
+
# @api public
|
|
68
|
+
def merge(paths)
|
|
69
|
+
prepare(paths)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @api private
|
|
73
|
+
def default_locale
|
|
74
|
+
super || ::I18n.locale || ::I18n.default_locale
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @api private
|
|
78
|
+
def prepare(paths = config.load_paths)
|
|
79
|
+
paths.each do |path|
|
|
80
|
+
data = ::YAML.load_file(path)
|
|
81
|
+
|
|
82
|
+
if custom_top_namespace?(path)
|
|
83
|
+
top_namespace = config.top_namespace
|
|
84
|
+
|
|
85
|
+
mapped_data = data.transform_values { |v|
|
|
86
|
+
{top_namespace => v[DEFAULT_MESSAGES_ROOT]}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
store_translations(mapped_data)
|
|
90
|
+
else
|
|
91
|
+
store_translations(data)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @api private
|
|
99
|
+
def interpolatable_data(_key, _options, **data)
|
|
100
|
+
data
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @api private
|
|
104
|
+
def interpolate(key, options, **data)
|
|
105
|
+
text_key = "#{key}.text"
|
|
106
|
+
|
|
107
|
+
opts = {
|
|
108
|
+
locale: default_locale,
|
|
109
|
+
**options,
|
|
110
|
+
**data
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
resolved_key = key?(text_key, opts) ? text_key : key
|
|
114
|
+
|
|
115
|
+
result = t.(resolved_key, **opts)
|
|
116
|
+
|
|
117
|
+
if result.is_a?(::Hash)
|
|
118
|
+
result[:text]
|
|
119
|
+
else
|
|
120
|
+
result
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
# @api private
|
|
127
|
+
def store_translations(data)
|
|
128
|
+
locales = data.keys.map(&:to_sym)
|
|
129
|
+
|
|
130
|
+
::I18n.available_locales |= locales
|
|
131
|
+
|
|
132
|
+
locales.each do |locale|
|
|
133
|
+
::I18n.backend.store_translations(locale, data[locale.to_s])
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|