plumb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +628 -0
- data/Rakefile +8 -0
- data/lib/plumb/and.rb +25 -0
- data/lib/plumb/any_class.rb +19 -0
- data/lib/plumb/array_class.rb +87 -0
- data/lib/plumb/build.rb +18 -0
- data/lib/plumb/deferred.rb +31 -0
- data/lib/plumb/hash_class.rb +126 -0
- data/lib/plumb/hash_map.rb +35 -0
- data/lib/plumb/interface_class.rb +35 -0
- data/lib/plumb/json_schema_visitor.rb +222 -0
- data/lib/plumb/key.rb +41 -0
- data/lib/plumb/match_class.rb +39 -0
- data/lib/plumb/metadata.rb +15 -0
- data/lib/plumb/metadata_visitor.rb +116 -0
- data/lib/plumb/not.rb +26 -0
- data/lib/plumb/or.rb +29 -0
- data/lib/plumb/pipeline.rb +73 -0
- data/lib/plumb/result.rb +64 -0
- data/lib/plumb/rules.rb +103 -0
- data/lib/plumb/schema.rb +193 -0
- data/lib/plumb/static_class.rb +30 -0
- data/lib/plumb/step.rb +21 -0
- data/lib/plumb/steppable.rb +242 -0
- data/lib/plumb/tagged_hash.rb +37 -0
- data/lib/plumb/transform.rb +20 -0
- data/lib/plumb/tuple_class.rb +42 -0
- data/lib/plumb/type_registry.rb +37 -0
- data/lib/plumb/types.rb +140 -0
- data/lib/plumb/value_class.rb +23 -0
- data/lib/plumb/version.rb +5 -0
- data/lib/plumb/visitor_handlers.rb +34 -0
- data/lib/plumb.rb +25 -0
- metadata +107 -0
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/metadata_visitor'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class UndefinedClass
|
7
|
+
def inspect
|
8
|
+
%(Undefined)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s = inspect
|
12
|
+
def node_name = :undefined
|
13
|
+
end
|
14
|
+
|
15
|
+
TypeError = Class.new(::TypeError)
|
16
|
+
Undefined = UndefinedClass.new.freeze
|
17
|
+
|
18
|
+
BLANK_STRING = ''
|
19
|
+
BLANK_ARRAY = [].freeze
|
20
|
+
BLANK_HASH = {}.freeze
|
21
|
+
BLANK_RESULT = Result.wrap(Undefined)
|
22
|
+
|
23
|
+
module Callable
|
24
|
+
def metadata
|
25
|
+
MetadataVisitor.call(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve(value = Undefined)
|
29
|
+
call(Result.wrap(value))
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse(value = Undefined)
|
33
|
+
result = resolve(value)
|
34
|
+
raise TypeError, result.errors if result.invalid?
|
35
|
+
|
36
|
+
result.value
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(result)
|
40
|
+
raise NotImplementedError, "Implement #call(Result) => Result in #{self.class}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Steppable
|
45
|
+
include Callable
|
46
|
+
|
47
|
+
def self.included(base)
|
48
|
+
nname = base.name.split('::').last
|
49
|
+
nname.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
50
|
+
nname.downcase!
|
51
|
+
nname.gsub!(/_class$/, '')
|
52
|
+
nname = nname.to_sym
|
53
|
+
base.define_method(:node_name) { nname }
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.wrap(callable)
|
57
|
+
if callable.is_a?(Steppable)
|
58
|
+
callable
|
59
|
+
elsif callable.respond_to?(:call)
|
60
|
+
Step.new(callable)
|
61
|
+
else
|
62
|
+
StaticClass.new(callable)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :name
|
67
|
+
|
68
|
+
class Name
|
69
|
+
def initialize(name)
|
70
|
+
@name = name
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s = @name
|
74
|
+
|
75
|
+
def set(n)
|
76
|
+
@name = n
|
77
|
+
self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def freeze
|
82
|
+
return self if frozen?
|
83
|
+
|
84
|
+
@name = Name.new(_inspect)
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
private def _inspect = self.class.name
|
89
|
+
|
90
|
+
def inspect = name.to_s
|
91
|
+
|
92
|
+
def node_name = self.class.name.split('::').last.to_sym
|
93
|
+
|
94
|
+
def defer(definition = nil, &block)
|
95
|
+
Deferred.new(definition || block)
|
96
|
+
end
|
97
|
+
|
98
|
+
def >>(other)
|
99
|
+
And.new(self, Steppable.wrap(other))
|
100
|
+
end
|
101
|
+
|
102
|
+
def |(other)
|
103
|
+
Or.new(self, Steppable.wrap(other))
|
104
|
+
end
|
105
|
+
|
106
|
+
def transform(target_type, callable = nil, &block)
|
107
|
+
self >> Transform.new(target_type, callable || block)
|
108
|
+
end
|
109
|
+
|
110
|
+
def check(errors = 'did not pass the check', &block)
|
111
|
+
a_check = lambda { |result|
|
112
|
+
block.call(result.value) ? result : result.invalid(errors:)
|
113
|
+
}
|
114
|
+
|
115
|
+
self >> a_check
|
116
|
+
end
|
117
|
+
|
118
|
+
def meta(data = {})
|
119
|
+
self >> Metadata.new(data)
|
120
|
+
end
|
121
|
+
|
122
|
+
def not(other = self)
|
123
|
+
Not.new(other)
|
124
|
+
end
|
125
|
+
|
126
|
+
def invalid(errors: nil)
|
127
|
+
Not.new(self, errors:)
|
128
|
+
end
|
129
|
+
|
130
|
+
def value(val)
|
131
|
+
self >> ValueClass.new(val)
|
132
|
+
end
|
133
|
+
|
134
|
+
def match(*args)
|
135
|
+
self >> MatchClass.new(*args)
|
136
|
+
end
|
137
|
+
|
138
|
+
def [](val) = match(val)
|
139
|
+
|
140
|
+
DefaultProc = proc do |callable|
|
141
|
+
proc do |result|
|
142
|
+
result.valid(callable.call)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def default(val = Undefined, &block)
|
147
|
+
val_type = if val == Undefined
|
148
|
+
DefaultProc.call(block)
|
149
|
+
else
|
150
|
+
Types::Static[val]
|
151
|
+
end
|
152
|
+
|
153
|
+
self | (Types::Undefined >> val_type)
|
154
|
+
end
|
155
|
+
|
156
|
+
class Node
|
157
|
+
include Steppable
|
158
|
+
|
159
|
+
attr_reader :node_name, :type, :attributes
|
160
|
+
|
161
|
+
def initialize(node_name, type, attributes = BLANK_HASH)
|
162
|
+
@node_name = node_name
|
163
|
+
@type = type
|
164
|
+
@attributes = attributes
|
165
|
+
freeze
|
166
|
+
end
|
167
|
+
|
168
|
+
def call(result) = type.call(result)
|
169
|
+
end
|
170
|
+
|
171
|
+
def as_node(node_name, metadata = BLANK_HASH)
|
172
|
+
Node.new(node_name, self, metadata)
|
173
|
+
end
|
174
|
+
|
175
|
+
def nullable
|
176
|
+
Types::Nil | self
|
177
|
+
end
|
178
|
+
|
179
|
+
def present
|
180
|
+
Types::Present >> self
|
181
|
+
end
|
182
|
+
|
183
|
+
def options(opts = [])
|
184
|
+
rule(included_in: opts)
|
185
|
+
end
|
186
|
+
|
187
|
+
def rule(*args)
|
188
|
+
specs = case args
|
189
|
+
in [::Symbol => rule_name, value]
|
190
|
+
{ rule_name => value }
|
191
|
+
in [::Hash => rules]
|
192
|
+
rules
|
193
|
+
else
|
194
|
+
raise ArgumentError, "expected 1 or 2 arguments, but got #{args.size}"
|
195
|
+
end
|
196
|
+
|
197
|
+
self >> Rules.new(specs, metadata[:type])
|
198
|
+
end
|
199
|
+
|
200
|
+
def is_a(klass)
|
201
|
+
rule(is_a: klass)
|
202
|
+
end
|
203
|
+
|
204
|
+
def ===(other)
|
205
|
+
case other
|
206
|
+
when Steppable
|
207
|
+
other == self
|
208
|
+
else
|
209
|
+
resolve(other).valid?
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def coerce(type, coercion = nil, &block)
|
214
|
+
coercion ||= block
|
215
|
+
step = lambda { |result|
|
216
|
+
if type === result.value
|
217
|
+
result.valid(coercion.call(result.value))
|
218
|
+
else
|
219
|
+
result.invalid(errors: "%s can't be coerced" % result.value.inspect)
|
220
|
+
end
|
221
|
+
}
|
222
|
+
self >> step
|
223
|
+
end
|
224
|
+
|
225
|
+
def build(cns, factory_method = :new, &block)
|
226
|
+
self >> Build.new(cns, factory_method:, &block)
|
227
|
+
end
|
228
|
+
|
229
|
+
def pipeline(&block)
|
230
|
+
Pipeline.new(self, &block)
|
231
|
+
end
|
232
|
+
|
233
|
+
def to_s
|
234
|
+
inspect
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
require 'plumb/deferred'
|
240
|
+
require 'plumb/transform'
|
241
|
+
require 'plumb/build'
|
242
|
+
require 'plumb/metadata'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class TaggedHash
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :key, :types
|
10
|
+
|
11
|
+
def initialize(hash_type, key, types)
|
12
|
+
@hash_type = hash_type
|
13
|
+
@key = Key.wrap(key)
|
14
|
+
@types = types
|
15
|
+
|
16
|
+
raise ArgumentError, 'all types must be HashClass' if @types.size == 0 || @types.any? do |t|
|
17
|
+
!t.is_a?(HashClass)
|
18
|
+
end
|
19
|
+
raise ArgumentError, "all types must define key #{@key}" unless @types.all? { |t| !!t.at_key(@key) }
|
20
|
+
|
21
|
+
# types are assumed to have static values for the index field :key
|
22
|
+
@index = @types.each.with_object({}) do |t, memo|
|
23
|
+
memo[t.at_key(@key).resolve.value] = t
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(result)
|
28
|
+
result = @hash_type.call(result)
|
29
|
+
return result unless result.valid?
|
30
|
+
|
31
|
+
child = @index[result.value[@key.to_sym]]
|
32
|
+
return result.invalid(errors: "expected :#{@key.to_sym} to be one of #{@index.keys.join(', ')}") unless child
|
33
|
+
|
34
|
+
child.call(result)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class Transform
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :target_type
|
10
|
+
|
11
|
+
def initialize(target_type, callable)
|
12
|
+
@target_type = target_type
|
13
|
+
@callable = callable
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(result)
|
17
|
+
result.valid(@callable.call(result.value))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class TupleClass
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :types
|
10
|
+
|
11
|
+
def initialize(*types)
|
12
|
+
@types = types.map { |t| t.is_a?(Steppable) ? t : Types::Any.value(t) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def of(*types)
|
16
|
+
self.class.new(*types)
|
17
|
+
end
|
18
|
+
|
19
|
+
alias [] of
|
20
|
+
|
21
|
+
private def _inspect
|
22
|
+
"#{name}[#{@types.map(&:inspect).join(', ')}]"
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(result)
|
26
|
+
return result.invalid(errors: 'must be an Array') unless result.value.is_a?(::Array)
|
27
|
+
return result.invalid(errors: 'must have the same size') unless result.value.size == @types.size
|
28
|
+
|
29
|
+
errors = {}
|
30
|
+
values = @types.map.with_index do |type, idx|
|
31
|
+
val = result.value[idx]
|
32
|
+
r = type.resolve(val)
|
33
|
+
errors[idx] = ["expected #{type.inspect}, got #{val.inspect}", r.errors].flatten unless r.valid?
|
34
|
+
r.value
|
35
|
+
end
|
36
|
+
|
37
|
+
return result.valid(values) unless errors.any?
|
38
|
+
|
39
|
+
result.invalid(errors:)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plumb
|
4
|
+
module TypeRegistry
|
5
|
+
def const_added(const_name)
|
6
|
+
obj = const_get(const_name)
|
7
|
+
case obj
|
8
|
+
when Module
|
9
|
+
obj.extend TypeRegistry
|
10
|
+
when Steppable
|
11
|
+
anc = [name, const_name].join('::')
|
12
|
+
obj.freeze.name.set(anc)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def included(host)
|
17
|
+
host.extend TypeRegistry
|
18
|
+
constants(false).each do |const_name|
|
19
|
+
const = const_get(const_name)
|
20
|
+
anc = [host.name, const_name].join('::')
|
21
|
+
case const
|
22
|
+
when Module
|
23
|
+
child_mod = Module.new
|
24
|
+
child_mod.define_singleton_method(:name) do
|
25
|
+
anc
|
26
|
+
end
|
27
|
+
child_mod.send(:include, const)
|
28
|
+
host.const_set(const_name, child_mod)
|
29
|
+
when Steppable
|
30
|
+
type = const.dup
|
31
|
+
type.freeze.name.set(anc)
|
32
|
+
host.const_set(const_name, type)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/plumb/types.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
Rules.define :eq, 'must be equal to %<value>s' do |result, value|
|
7
|
+
value == result.value
|
8
|
+
end
|
9
|
+
Rules.define :not_eq, 'must not be equal to %<value>s' do |result, value|
|
10
|
+
value != result.value
|
11
|
+
end
|
12
|
+
# :gt for numbers and #size (arrays, strings, hashes)
|
13
|
+
[::String, ::Array, ::Hash].each do |klass|
|
14
|
+
Rules.define :gt, 'must contain more than %<value>s elements', expects: klass do |result, value|
|
15
|
+
value < result.value.size
|
16
|
+
end
|
17
|
+
|
18
|
+
# :lt for numbers and #size (arrays, strings, hashes)
|
19
|
+
Rules.define :lt, 'must contain fewer than %<value>s elements', expects: klass do |result, value|
|
20
|
+
value > result.value.size
|
21
|
+
end
|
22
|
+
|
23
|
+
Rules.define :gte, 'must be size greater or equal to %<value>s', expects: klass do |result, value|
|
24
|
+
value <= result.value.size
|
25
|
+
end
|
26
|
+
|
27
|
+
Rules.define :lte, 'must be size less or equal to %<value>s', expects: klass do |result, value|
|
28
|
+
value >= result.value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# :gt and :lt for numbers, BigDecimal
|
32
|
+
[::Numeric].each do |klass|
|
33
|
+
Rules.define :gt, 'must be greater than %<value>s', expects: klass do |result, value|
|
34
|
+
value < result.value
|
35
|
+
end
|
36
|
+
Rules.define :lt, 'must be greater than %<value>s', expects: klass do |result, value|
|
37
|
+
value > result.value
|
38
|
+
end
|
39
|
+
Rules.define :gte, 'must be greater or equal to %<value>s', expects: klass do |result, value|
|
40
|
+
value <= result.value
|
41
|
+
end
|
42
|
+
# :lte for numbers and #size (arrays, strings, hashes)
|
43
|
+
Rules.define :lte, 'must be less or equal to %<value>s', expects: klass do |result, value|
|
44
|
+
value >= result.value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Rules.define :match, 'must match %<value>s', metadata_key: :pattern do |result, value|
|
49
|
+
value === result.value
|
50
|
+
end
|
51
|
+
Rules.define :included_in, 'elements must be included in %<value>s', expects: ::Array,
|
52
|
+
metadata_key: :options do |result, opts|
|
53
|
+
result.value.all? { |v| opts.include?(v) }
|
54
|
+
end
|
55
|
+
Rules.define :included_in, 'must be included in %<value>s', metadata_key: :options do |result, opts|
|
56
|
+
opts.include? result.value
|
57
|
+
end
|
58
|
+
Rules.define :excluded_from, 'elements must not be included in %<value>s', expects: ::Array do |result, value|
|
59
|
+
result.value.all? { |v| !value.include?(v) }
|
60
|
+
end
|
61
|
+
Rules.define :excluded_from, 'must not be included in %<value>s' do |result, value|
|
62
|
+
!value.include?(result.value)
|
63
|
+
end
|
64
|
+
Rules.define :respond_to, 'must respond to %<value>s' do |result, value|
|
65
|
+
Array(value).all? { |m| result.value.respond_to?(m) }
|
66
|
+
end
|
67
|
+
Rules.define :size, 'must be of size %<value>s', expects: :size, metadata_key: :size do |result, value|
|
68
|
+
value === result.value.size
|
69
|
+
end
|
70
|
+
|
71
|
+
module Types
|
72
|
+
extend TypeRegistry
|
73
|
+
|
74
|
+
Any = AnyClass.new
|
75
|
+
Undefined = Any.value(Plumb::Undefined)
|
76
|
+
String = Any[::String]
|
77
|
+
Symbol = Any[::Symbol]
|
78
|
+
Numeric = Any[::Numeric]
|
79
|
+
Integer = Any[::Integer]
|
80
|
+
Decimal = Any[BigDecimal]
|
81
|
+
Static = StaticClass.new
|
82
|
+
Value = ValueClass.new
|
83
|
+
Nil = Any[::NilClass]
|
84
|
+
True = Any[::TrueClass]
|
85
|
+
False = Any[::FalseClass]
|
86
|
+
Boolean = (True | False).as_node(:boolean)
|
87
|
+
Array = ArrayClass.new
|
88
|
+
Tuple = TupleClass.new
|
89
|
+
Hash = HashClass.new
|
90
|
+
Interface = InterfaceClass.new
|
91
|
+
# TODO: type-speficic concept of blank, via Rules
|
92
|
+
Blank = (
|
93
|
+
Undefined \
|
94
|
+
| Nil \
|
95
|
+
| String.value(BLANK_STRING) \
|
96
|
+
| Hash.value(BLANK_HASH) \
|
97
|
+
| Array.value(BLANK_ARRAY)
|
98
|
+
)
|
99
|
+
|
100
|
+
Present = Blank.invalid(errors: 'must be present')
|
101
|
+
Split = String.transform(::String) { |v| v.split(/\s*,\s*/) }
|
102
|
+
|
103
|
+
module Lax
|
104
|
+
NUMBER_EXPR = /^\d{1,3}(?:,\d{3})*(?:\.\d+)?$/
|
105
|
+
|
106
|
+
String = Types::String \
|
107
|
+
| Any.coerce(BigDecimal) { |v| v.to_s('F') } \
|
108
|
+
| Any.coerce(::Numeric, &:to_s)
|
109
|
+
|
110
|
+
Symbol = Types::Symbol | Types::String.transform(::Symbol, &:to_sym)
|
111
|
+
|
112
|
+
NumberString = Types::String.match(NUMBER_EXPR)
|
113
|
+
CoercibleNumberString = NumberString.transform(::String) { |v| v.tr(',', '') }
|
114
|
+
|
115
|
+
Numeric = Types::Numeric | CoercibleNumberString.transform(::Numeric, &:to_f)
|
116
|
+
|
117
|
+
Decimal = Types::Decimal | \
|
118
|
+
(Types::Numeric.transform(::String, &:to_s) | CoercibleNumberString) \
|
119
|
+
.transform(::BigDecimal) { |v| BigDecimal(v) }
|
120
|
+
|
121
|
+
Integer = Numeric.transform(::Integer, &:to_i)
|
122
|
+
end
|
123
|
+
|
124
|
+
module Forms
|
125
|
+
True = Types::True \
|
126
|
+
| Types::String >> Any.coerce(/^true$/i) { |_| true } \
|
127
|
+
| Any.coerce('1') { |_| true } \
|
128
|
+
| Any.coerce(1) { |_| true }
|
129
|
+
|
130
|
+
False = Types::False \
|
131
|
+
| Types::String >> Any.coerce(/^false$/i) { |_| false } \
|
132
|
+
| Any.coerce('0') { |_| false } \
|
133
|
+
| Any.coerce(0) { |_| false }
|
134
|
+
|
135
|
+
Boolean = True | False
|
136
|
+
|
137
|
+
Nil = Nil | (String[BLANK_STRING] >> nil)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class ValueClass
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value = Undefined)
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect = @value.inspect
|
16
|
+
|
17
|
+
def [](value) = self.class.new(value)
|
18
|
+
|
19
|
+
def call(result)
|
20
|
+
@value == result.value ? result : result.invalid(errors: "Must be equal to #{@value}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plumb
|
4
|
+
module VisitorHandlers
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def on(node_name, &block)
|
11
|
+
name = node_name.is_a?(Symbol) ? node_name : :"#{node_name}_class"
|
12
|
+
self.define_method("visit_#{name}", &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit(type, props = BLANK_HASH)
|
16
|
+
new.visit(type, props)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit(type, props = BLANK_HASH)
|
21
|
+
method_name = type.respond_to?(:node_name) ? type.node_name : :"#{(type.is_a?(::Class) ? type : type.class)}_class"
|
22
|
+
method_name = "visit_#{method_name}"
|
23
|
+
if respond_to?(method_name)
|
24
|
+
send(method_name, type, props)
|
25
|
+
else
|
26
|
+
on_missing_handler(type, props, method_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_missing_handler(type, _props, method_name)
|
31
|
+
raise "No handler for #{type.inspect} with :#{method_name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/plumb.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plumb
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'plumb/result'
|
7
|
+
require 'plumb/type_registry'
|
8
|
+
require 'plumb/steppable'
|
9
|
+
require 'plumb/any_class'
|
10
|
+
require 'plumb/step'
|
11
|
+
require 'plumb/and'
|
12
|
+
require 'plumb/pipeline'
|
13
|
+
require 'plumb/rules'
|
14
|
+
require 'plumb/static_class'
|
15
|
+
require 'plumb/value_class'
|
16
|
+
require 'plumb/match_class'
|
17
|
+
require 'plumb/not'
|
18
|
+
require 'plumb/or'
|
19
|
+
require 'plumb/tuple_class'
|
20
|
+
require 'plumb/array_class'
|
21
|
+
require 'plumb/hash_class'
|
22
|
+
require 'plumb/interface_class'
|
23
|
+
require 'plumb/types'
|
24
|
+
require 'plumb/json_schema_visitor'
|
25
|
+
require 'plumb/schema'
|