super-smart-tool 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-types-1.9.1/CHANGELOG.md +1145 -0
- data/dry-types-1.9.1/LICENSE +20 -0
- data/dry-types-1.9.1/README.md +17 -0
- data/dry-types-1.9.1/dry-types.gemspec +45 -0
- data/dry-types-1.9.1/lib/dry/types/any.rb +41 -0
- data/dry-types-1.9.1/lib/dry/types/array/constructor.rb +24 -0
- data/dry-types-1.9.1/lib/dry/types/array/member.rb +118 -0
- data/dry-types-1.9.1/lib/dry/types/array.rb +32 -0
- data/dry-types-1.9.1/lib/dry/types/builder.rb +221 -0
- data/dry-types-1.9.1/lib/dry/types/builder_methods.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/coercions/json.rb +57 -0
- data/dry-types-1.9.1/lib/dry/types/coercions/params.rb +168 -0
- data/dry-types-1.9.1/lib/dry/types/coercions.rb +105 -0
- data/dry-types-1.9.1/lib/dry/types/compat.rb +1 -0
- data/dry-types-1.9.1/lib/dry/types/compiler.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/composition.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/constrained/coercible.rb +59 -0
- data/dry-types-1.9.1/lib/dry/types/constrained.rb +146 -0
- data/dry-types-1.9.1/lib/dry/types/constraints.rb +30 -0
- data/dry-types-1.9.1/lib/dry/types/constructor/function.rb +203 -0
- data/dry-types-1.9.1/lib/dry/types/constructor/wrapper.rb +88 -0
- data/dry-types-1.9.1/lib/dry/types/constructor.rb +195 -0
- data/dry-types-1.9.1/lib/dry/types/container.rb +12 -0
- data/dry-types-1.9.1/lib/dry/types/core.rb +106 -0
- data/dry-types-1.9.1/lib/dry/types/decorator.rb +94 -0
- data/dry-types-1.9.1/lib/dry/types/default.rb +122 -0
- data/dry-types-1.9.1/lib/dry/types/enum.rb +121 -0
- data/dry-types-1.9.1/lib/dry/types/errors.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/extensions/maybe.rb +128 -0
- data/dry-types-1.9.1/lib/dry/types/extensions/monads.rb +34 -0
- data/dry-types-1.9.1/lib/dry/types/extensions.rb +9 -0
- data/dry-types-1.9.1/lib/dry/types/fn_container.rb +37 -0
- data/dry-types-1.9.1/lib/dry/types/hash/constructor.rb +25 -0
- data/dry-types-1.9.1/lib/dry/types/hash.rb +134 -0
- data/dry-types-1.9.1/lib/dry/types/implication.rb +64 -0
- data/dry-types-1.9.1/lib/dry/types/inflector.rb +9 -0
- data/dry-types-1.9.1/lib/dry/types/intersection.rb +102 -0
- data/dry-types-1.9.1/lib/dry/types/json.rb +35 -0
- data/dry-types-1.9.1/lib/dry/types/lax.rb +66 -0
- data/dry-types-1.9.1/lib/dry/types/map.rb +134 -0
- data/dry-types-1.9.1/lib/dry/types/meta.rb +53 -0
- data/dry-types-1.9.1/lib/dry/types/module.rb +127 -0
- data/dry-types-1.9.1/lib/dry/types/nominal.rb +201 -0
- data/dry-types-1.9.1/lib/dry/types/options.rb +34 -0
- data/dry-types-1.9.1/lib/dry/types/params.rb +38 -0
- data/dry-types-1.9.1/lib/dry/types/predicate_inferrer.rb +235 -0
- data/dry-types-1.9.1/lib/dry/types/predicate_registry.rb +32 -0
- data/dry-types-1.9.1/lib/dry/types/primitive_inferrer.rb +75 -0
- data/dry-types-1.9.1/lib/dry/types/printable.rb +14 -0
- data/dry-types-1.9.1/lib/dry/types/printer/composition.rb +44 -0
- data/dry-types-1.9.1/lib/dry/types/printer.rb +313 -0
- data/dry-types-1.9.1/lib/dry/types/result.rb +72 -0
- data/dry-types-1.9.1/lib/dry/types/schema/key.rb +138 -0
- data/dry-types-1.9.1/lib/dry/types/schema.rb +408 -0
- data/dry-types-1.9.1/lib/dry/types/spec/types.rb +164 -0
- data/dry-types-1.9.1/lib/dry/types/sum.rb +105 -0
- data/dry-types-1.9.1/lib/dry/types/type.rb +53 -0
- data/dry-types-1.9.1/lib/dry/types/version.rb +7 -0
- data/dry-types-1.9.1/lib/dry/types.rb +259 -0
- data/dry-types-1.9.1/lib/dry-types.rb +3 -0
- data/super-smart-tool.gemspec +11 -0
- metadata +101 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/types/builder_methods"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Types
|
|
7
|
+
# Export types registered in a container as module constants.
|
|
8
|
+
# @example
|
|
9
|
+
# module Types
|
|
10
|
+
# include Dry.Types(:strict, :coercible, :nominal, default: :strict)
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# Types.constants
|
|
14
|
+
# # => [:Class, :Strict, :Symbol, :Integer, :Float, :String, :Array, :Hash,
|
|
15
|
+
# # :Decimal, :Nil, :True, :False, :Bool, :Date, :Nominal, :DateTime, :Range,
|
|
16
|
+
# # :Coercible, :Time]
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
class Module < ::Module
|
|
20
|
+
def initialize(registry, *args, **kwargs)
|
|
21
|
+
@registry = registry
|
|
22
|
+
check_parameters(*args, **kwargs)
|
|
23
|
+
constants = type_constants(*args, **kwargs)
|
|
24
|
+
define_constants(constants)
|
|
25
|
+
extend(BuilderMethods)
|
|
26
|
+
|
|
27
|
+
if constants.key?(:Nominal)
|
|
28
|
+
singleton_class.define_method(:included) do |base|
|
|
29
|
+
super(base)
|
|
30
|
+
base.instance_exec(const_get(:Nominal, false)) do |nominal|
|
|
31
|
+
extend Dry::Core::Deprecations[:"dry-types"]
|
|
32
|
+
const_set(:Definition, nominal)
|
|
33
|
+
deprecate_constant(:Definition, message: "Nominal")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @api private
|
|
40
|
+
# rubocop:disable Metrics/AbcSize
|
|
41
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
42
|
+
def type_constants(*namespaces, default: Undefined, **aliases)
|
|
43
|
+
if namespaces.empty? && aliases.empty? && Undefined.equal?(default)
|
|
44
|
+
default_ns = :Strict
|
|
45
|
+
elsif Undefined.equal?(default)
|
|
46
|
+
default_ns = Undefined
|
|
47
|
+
else
|
|
48
|
+
default_ns = Types::Inflector.camelize(default).to_sym
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
tree = registry_tree
|
|
52
|
+
|
|
53
|
+
if namespaces.empty? && aliases.empty?
|
|
54
|
+
modules = tree.select { _2.is_a?(::Hash) }.map(&:first)
|
|
55
|
+
else
|
|
56
|
+
modules = (namespaces + aliases.keys).map { |n|
|
|
57
|
+
Types::Inflector.camelize(n).to_sym
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
tree.each_with_object({}) do |(key, value), constants|
|
|
62
|
+
if modules.include?(key)
|
|
63
|
+
name = aliases.fetch(Inflector.underscore(key).to_sym, key)
|
|
64
|
+
constants[name] = value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
constants.update(value) if key == default_ns
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
# rubocop:enable Metrics/AbcSize
|
|
71
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
72
|
+
|
|
73
|
+
# @api private
|
|
74
|
+
def registry_tree
|
|
75
|
+
@registry_tree ||= @registry.keys.each_with_object({}) { |key, tree|
|
|
76
|
+
type = @registry[key]
|
|
77
|
+
*modules, const_name = key.split(".").map { |part|
|
|
78
|
+
Types::Inflector.camelize(part).to_sym
|
|
79
|
+
}
|
|
80
|
+
next if modules.empty?
|
|
81
|
+
|
|
82
|
+
modules.reduce(tree) { |br, name| br[name] ||= {} }[const_name] = type
|
|
83
|
+
}.freeze
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# @api private
|
|
89
|
+
def check_parameters(*namespaces, default: Undefined, **aliases)
|
|
90
|
+
referenced = namespaces.dup
|
|
91
|
+
referenced << default unless false.equal?(default) || Undefined.equal?(default)
|
|
92
|
+
referenced.concat(aliases.keys)
|
|
93
|
+
|
|
94
|
+
known = @registry.keys.map { |k|
|
|
95
|
+
ns, *path = k.split(".")
|
|
96
|
+
ns.to_sym unless path.empty?
|
|
97
|
+
}.compact.uniq
|
|
98
|
+
|
|
99
|
+
unknown = (referenced.uniq - known).first
|
|
100
|
+
|
|
101
|
+
if unknown
|
|
102
|
+
raise ::ArgumentError,
|
|
103
|
+
"#{unknown.inspect} is not a known type namespace. " \
|
|
104
|
+
"Supported options are #{known.map(&:inspect).join(", ")}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @api private
|
|
109
|
+
def define_constants(constants, mod = self)
|
|
110
|
+
constants.each do |name, value|
|
|
111
|
+
case value
|
|
112
|
+
when ::Hash
|
|
113
|
+
if mod.const_defined?(name, false)
|
|
114
|
+
define_constants(value, mod.const_get(name, false))
|
|
115
|
+
else
|
|
116
|
+
m = ::Module.new
|
|
117
|
+
mod.const_set(name, m)
|
|
118
|
+
define_constants(value, m)
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
mod.const_set(name, value)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Nominal types define a primitive class and do not apply any constructors or constraints
|
|
6
|
+
#
|
|
7
|
+
# Use these types for annotations and the base for building more complex types on top of them.
|
|
8
|
+
#
|
|
9
|
+
# @api public
|
|
10
|
+
class Nominal
|
|
11
|
+
include Type
|
|
12
|
+
include Options
|
|
13
|
+
include Meta
|
|
14
|
+
include Builder
|
|
15
|
+
include Printable
|
|
16
|
+
include ::Dry::Equalizer(:primitive, :options, :meta, inspect: false, immutable: true)
|
|
17
|
+
|
|
18
|
+
# @return [Class]
|
|
19
|
+
attr_reader :primitive
|
|
20
|
+
|
|
21
|
+
# @return [String, nil]
|
|
22
|
+
attr_reader :namespace
|
|
23
|
+
|
|
24
|
+
# @param [Class] primitive
|
|
25
|
+
#
|
|
26
|
+
# @return [Type]
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
def self.[](primitive)
|
|
30
|
+
if primitive == ::Array
|
|
31
|
+
Types::Array
|
|
32
|
+
elsif primitive == ::Hash
|
|
33
|
+
Types::Hash
|
|
34
|
+
else
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ALWAYS = proc { true }
|
|
40
|
+
|
|
41
|
+
# @param [Type,Class] primitive
|
|
42
|
+
# @param [Hash] options
|
|
43
|
+
#
|
|
44
|
+
# @api private
|
|
45
|
+
def initialize(primitive, **options)
|
|
46
|
+
super
|
|
47
|
+
@primitive = primitive
|
|
48
|
+
@namespace = options[:namespace]
|
|
49
|
+
freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [String]
|
|
53
|
+
#
|
|
54
|
+
# @api public
|
|
55
|
+
def name = primitive.name
|
|
56
|
+
|
|
57
|
+
# @return [false]
|
|
58
|
+
#
|
|
59
|
+
# @api public
|
|
60
|
+
def default? = false
|
|
61
|
+
|
|
62
|
+
# @return [false]
|
|
63
|
+
#
|
|
64
|
+
# @api public
|
|
65
|
+
def constrained? = false
|
|
66
|
+
|
|
67
|
+
# @return [false]
|
|
68
|
+
#
|
|
69
|
+
# @api public
|
|
70
|
+
def optional? = false
|
|
71
|
+
|
|
72
|
+
# @param [BasicObject] input
|
|
73
|
+
#
|
|
74
|
+
# @return [BasicObject]
|
|
75
|
+
#
|
|
76
|
+
# @api private
|
|
77
|
+
def call_unsafe(input) = input
|
|
78
|
+
|
|
79
|
+
# @param [BasicObject] input
|
|
80
|
+
#
|
|
81
|
+
# @return [BasicObject]
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
84
|
+
def call_safe(input, &) = input
|
|
85
|
+
|
|
86
|
+
# @param [Object] input
|
|
87
|
+
#
|
|
88
|
+
# @yieldparam [Failure] failure
|
|
89
|
+
# @yieldreturn [Result]
|
|
90
|
+
#
|
|
91
|
+
# @return [Result,Logic::Result] when a block is not provided
|
|
92
|
+
# @return [nil] otherwise
|
|
93
|
+
#
|
|
94
|
+
# @api public
|
|
95
|
+
def try(input, &) = success(input)
|
|
96
|
+
|
|
97
|
+
# @param (see Dry::Types::Success#initialize)
|
|
98
|
+
#
|
|
99
|
+
# @return [Result::Success]
|
|
100
|
+
#
|
|
101
|
+
# @api public
|
|
102
|
+
def success(input) = Result::Success.new(input)
|
|
103
|
+
|
|
104
|
+
# @param (see Failure#initialize)
|
|
105
|
+
#
|
|
106
|
+
# @return [Result::Failure]
|
|
107
|
+
#
|
|
108
|
+
# @api public
|
|
109
|
+
def failure(input, error)
|
|
110
|
+
raise ::ArgumentError, "error must be a CoercionError" unless error.is_a?(CoercionError)
|
|
111
|
+
|
|
112
|
+
Result::Failure.new(input, error)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Checks whether value is of a #primitive class
|
|
116
|
+
#
|
|
117
|
+
# @param [Object] value
|
|
118
|
+
#
|
|
119
|
+
# @return [Boolean]
|
|
120
|
+
#
|
|
121
|
+
# @api public
|
|
122
|
+
def primitive?(value) = value.is_a?(primitive)
|
|
123
|
+
|
|
124
|
+
# @api private
|
|
125
|
+
def coerce(input, &)
|
|
126
|
+
if primitive?(input)
|
|
127
|
+
input
|
|
128
|
+
elsif block_given?
|
|
129
|
+
yield
|
|
130
|
+
else
|
|
131
|
+
raise CoercionError, "#{input.inspect} must be an instance of #{primitive}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @api private
|
|
136
|
+
def try_coerce(input)
|
|
137
|
+
result = success(input)
|
|
138
|
+
|
|
139
|
+
coerce(input) do
|
|
140
|
+
result = failure(
|
|
141
|
+
input,
|
|
142
|
+
CoercionError.new("#{input.inspect} must be an instance of #{primitive}")
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if block_given?
|
|
147
|
+
yield(result)
|
|
148
|
+
else
|
|
149
|
+
result
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Return AST representation of a type nominal
|
|
154
|
+
#
|
|
155
|
+
# @return [Array]
|
|
156
|
+
#
|
|
157
|
+
# @api public
|
|
158
|
+
def to_ast(meta: true)
|
|
159
|
+
[:nominal, [primitive, namespace_ast, meta_ast(meta)]]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Return self. Nominal types are lax by definition
|
|
163
|
+
#
|
|
164
|
+
# @return [Nominal]
|
|
165
|
+
#
|
|
166
|
+
# @api public
|
|
167
|
+
def lax = self
|
|
168
|
+
|
|
169
|
+
# Wrap the type with a proc
|
|
170
|
+
#
|
|
171
|
+
# @return [Proc]
|
|
172
|
+
#
|
|
173
|
+
# @api public
|
|
174
|
+
def to_proc = ALWAYS
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
# @api private
|
|
179
|
+
def namespace_ast
|
|
180
|
+
if @namespace
|
|
181
|
+
{namespace: @namespace}
|
|
182
|
+
else
|
|
183
|
+
EMPTY_HASH
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# @api private
|
|
188
|
+
def meta_ast(meta)
|
|
189
|
+
if meta
|
|
190
|
+
self.meta
|
|
191
|
+
else
|
|
192
|
+
EMPTY_HASH
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
extend ::Dry::Core::Deprecations[:"dry-types"]
|
|
198
|
+
Definition = Nominal
|
|
199
|
+
deprecate_constant(:Definition, message: "Nominal")
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# Common API for types with options
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
module Options
|
|
9
|
+
# @return [Hash]
|
|
10
|
+
attr_reader :options
|
|
11
|
+
|
|
12
|
+
# @see Nominal#initialize
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
def initialize(*args, **options)
|
|
16
|
+
@__args__ = args.freeze
|
|
17
|
+
@options = options.freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param [Hash] new_options
|
|
21
|
+
#
|
|
22
|
+
# @return [Type]
|
|
23
|
+
#
|
|
24
|
+
# @api private
|
|
25
|
+
def with(**new_options)
|
|
26
|
+
if new_options.empty?
|
|
27
|
+
self
|
|
28
|
+
else
|
|
29
|
+
self.class.new(*@__args__, **options, **new_options)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/types/coercions/params"
|
|
4
|
+
|
|
5
|
+
module Dry
|
|
6
|
+
module Types
|
|
7
|
+
options = {namespace: "params"}
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
"nil" => :to_nil,
|
|
11
|
+
"date" => :to_date,
|
|
12
|
+
"date_time" => :to_date_time,
|
|
13
|
+
"time" => :to_time,
|
|
14
|
+
"true" => :to_true,
|
|
15
|
+
"false" => :to_false,
|
|
16
|
+
"integer" => :to_int,
|
|
17
|
+
"float" => :to_float,
|
|
18
|
+
"decimal" => :to_decimal,
|
|
19
|
+
"array" => :to_ary,
|
|
20
|
+
"hash" => :to_hash,
|
|
21
|
+
"symbol" => :to_symbol
|
|
22
|
+
}.each do |name, method|
|
|
23
|
+
register("params.#{name}") do
|
|
24
|
+
self["nominal.#{name}"].with(**options).constructor(Coercions::Params.method(method))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
register("params.bool") do
|
|
29
|
+
self["params.true"] | self["params.false"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
register("params.string", self["string"].with(**options))
|
|
33
|
+
|
|
34
|
+
COERCIBLE.each_key do |name|
|
|
35
|
+
register("optional.params.#{name}", self["params.nil"] | self["params.#{name}"])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# PredicateInferrer returns the list of predicates used by a type.
|
|
6
|
+
#
|
|
7
|
+
# @api public
|
|
8
|
+
class PredicateInferrer
|
|
9
|
+
extend Core::Cache
|
|
10
|
+
|
|
11
|
+
TYPE_TO_PREDICATE = {
|
|
12
|
+
::DateTime => :date_time?,
|
|
13
|
+
::Date => :date?,
|
|
14
|
+
::Time => :time?,
|
|
15
|
+
::FalseClass => :false?,
|
|
16
|
+
::Integer => :int?,
|
|
17
|
+
::Float => :float?,
|
|
18
|
+
::NilClass => :nil?,
|
|
19
|
+
::String => :str?,
|
|
20
|
+
::TrueClass => :true?,
|
|
21
|
+
::BigDecimal => :decimal?,
|
|
22
|
+
::Array => :array?
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
REDUCED_TYPES = {
|
|
26
|
+
[[[:true?], [:false?]]] => %i[bool?]
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
HASH = %i[hash?].freeze
|
|
30
|
+
|
|
31
|
+
ARRAY = %i[array?].freeze
|
|
32
|
+
|
|
33
|
+
NIL = %i[nil?].freeze
|
|
34
|
+
|
|
35
|
+
# Compiler reduces type AST into a list of predicates
|
|
36
|
+
#
|
|
37
|
+
# @api private
|
|
38
|
+
class Compiler
|
|
39
|
+
extend Core::ClassAttributes
|
|
40
|
+
|
|
41
|
+
defines :infer_predicate_by_class_name
|
|
42
|
+
infer_predicate_by_class_name nil
|
|
43
|
+
|
|
44
|
+
# @return [PredicateRegistry]
|
|
45
|
+
# @api private
|
|
46
|
+
attr_reader :registry
|
|
47
|
+
|
|
48
|
+
# @api private
|
|
49
|
+
def initialize(registry)
|
|
50
|
+
@registry = registry
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @api private
|
|
54
|
+
def infer_predicate(type) # rubocop:disable Metrics/PerceivedComplexity
|
|
55
|
+
pred = TYPE_TO_PREDICATE.fetch(type) do
|
|
56
|
+
if type.name.nil? || self.class.infer_predicate_by_class_name.equal?(false)
|
|
57
|
+
nil
|
|
58
|
+
else
|
|
59
|
+
candidate = :"#{type.name.split("::").last.downcase}?"
|
|
60
|
+
|
|
61
|
+
if registry.key?(candidate)
|
|
62
|
+
if self.class.infer_predicate_by_class_name
|
|
63
|
+
candidate
|
|
64
|
+
else
|
|
65
|
+
raise ::KeyError, <<~MESSAGE
|
|
66
|
+
Automatic predicate inferring from class names is deprecated
|
|
67
|
+
and will be removed in dry-types 2.0.
|
|
68
|
+
Use `Dry::Types::PredicateInferrer::Compiler.infer_predicate_by_class_name true`
|
|
69
|
+
to restore the previous behavior
|
|
70
|
+
or `Dry::Types::PredicateInferrer::Compiler.infer_predicate_by_class_name false`
|
|
71
|
+
to explicitly opt-out (i.e. no exception + no inferring).
|
|
72
|
+
Note: for dry-schema and dry-validation use Dry::Schema::PredicateInferrer::Compiler.
|
|
73
|
+
MESSAGE
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if pred.nil?
|
|
82
|
+
EMPTY_ARRAY
|
|
83
|
+
else
|
|
84
|
+
[pred]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @api private
|
|
89
|
+
def visit(node)
|
|
90
|
+
meth, rest = node
|
|
91
|
+
public_send(:"visit_#{meth}", rest)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @api private
|
|
95
|
+
def visit_nominal(node)
|
|
96
|
+
type = node[0]
|
|
97
|
+
predicate = infer_predicate(type)
|
|
98
|
+
|
|
99
|
+
if !predicate.empty? && registry.key?(predicate[0])
|
|
100
|
+
predicate
|
|
101
|
+
else
|
|
102
|
+
[type?: type]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @api private
|
|
107
|
+
def visit_hash(_)
|
|
108
|
+
HASH
|
|
109
|
+
end
|
|
110
|
+
alias_method :visit_schema, :visit_hash
|
|
111
|
+
|
|
112
|
+
# @api private
|
|
113
|
+
def visit_array(_)
|
|
114
|
+
ARRAY
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @api private
|
|
118
|
+
def visit_lax(node)
|
|
119
|
+
visit(node)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @api private
|
|
123
|
+
def visit_constructor(node)
|
|
124
|
+
other, * = node
|
|
125
|
+
visit(other)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @api private
|
|
129
|
+
def visit_enum(node)
|
|
130
|
+
other, * = node
|
|
131
|
+
visit(other)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @api private
|
|
135
|
+
def visit_sum(node)
|
|
136
|
+
left_node, right_node, = node
|
|
137
|
+
left = visit(left_node)
|
|
138
|
+
right = visit(right_node)
|
|
139
|
+
|
|
140
|
+
if left.eql?(NIL)
|
|
141
|
+
right
|
|
142
|
+
else
|
|
143
|
+
[[left, right]]
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @api private
|
|
148
|
+
def visit_constrained(node)
|
|
149
|
+
other, rules = node
|
|
150
|
+
predicates = visit(rules)
|
|
151
|
+
|
|
152
|
+
if predicates.empty?
|
|
153
|
+
visit(other)
|
|
154
|
+
else
|
|
155
|
+
[*visit(other), *merge_predicates(predicates)]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @api private
|
|
160
|
+
def visit_any(_) = EMPTY_ARRAY
|
|
161
|
+
|
|
162
|
+
# @api private
|
|
163
|
+
def visit_and(node)
|
|
164
|
+
left, right = node
|
|
165
|
+
visit(left) + visit(right)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @api private
|
|
169
|
+
def visit_predicate(node)
|
|
170
|
+
pred, args = node
|
|
171
|
+
|
|
172
|
+
if pred.equal?(:type?) || !registry.key?(pred)
|
|
173
|
+
EMPTY_ARRAY
|
|
174
|
+
else
|
|
175
|
+
*curried, _ = args
|
|
176
|
+
values = curried.map { |_, v| v }
|
|
177
|
+
|
|
178
|
+
if values.empty?
|
|
179
|
+
[pred]
|
|
180
|
+
else
|
|
181
|
+
[pred => values[0]]
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @api private
|
|
187
|
+
def visit_map(_node)
|
|
188
|
+
raise ::NotImplementedError, "map types are not supported yet"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
# @api private
|
|
194
|
+
def merge_predicates(nodes)
|
|
195
|
+
preds, merged = nodes.each_with_object([[], {}]) do |predicate, (ps, h)|
|
|
196
|
+
if predicate.is_a?(::Hash)
|
|
197
|
+
h.update(predicate)
|
|
198
|
+
else
|
|
199
|
+
ps << predicate
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
merged.empty? ? preds : [*preds, merged]
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @return [Compiler]
|
|
208
|
+
# @api private
|
|
209
|
+
attr_reader :compiler
|
|
210
|
+
|
|
211
|
+
# @api private
|
|
212
|
+
def initialize(registry = PredicateRegistry.new)
|
|
213
|
+
@compiler = Compiler.new(registry)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Infer predicate identifier from the provided type
|
|
217
|
+
#
|
|
218
|
+
# @param [Type] type
|
|
219
|
+
# @return [Symbol]
|
|
220
|
+
#
|
|
221
|
+
# @api private
|
|
222
|
+
def [](type)
|
|
223
|
+
self.class.fetch_or_store(type) do
|
|
224
|
+
predicates = compiler.visit(type.to_ast)
|
|
225
|
+
|
|
226
|
+
if predicates.is_a?(::Hash)
|
|
227
|
+
predicates
|
|
228
|
+
else
|
|
229
|
+
REDUCED_TYPES[predicates] || predicates
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Types
|
|
5
|
+
# A registry with predicate objects from `Dry::Logic::Predicates`
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class PredicateRegistry
|
|
9
|
+
# @api private
|
|
10
|
+
attr_reader :predicates
|
|
11
|
+
|
|
12
|
+
# @api private
|
|
13
|
+
attr_reader :has_predicate
|
|
14
|
+
|
|
15
|
+
KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?)
|
|
16
|
+
private_constant(:KERNEL_RESPOND_TO)
|
|
17
|
+
|
|
18
|
+
# @api private
|
|
19
|
+
def initialize(predicates = Logic::Predicates)
|
|
20
|
+
@predicates = predicates
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @api private
|
|
24
|
+
def key?(name)
|
|
25
|
+
KERNEL_RESPOND_TO.bind_call(@predicates, name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @api private
|
|
29
|
+
def [](name) = predicates[name]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|