plumb 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/.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,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/visitor_handlers'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class MetadataVisitor
|
7
|
+
include VisitorHandlers
|
8
|
+
|
9
|
+
def self.call(type)
|
10
|
+
new.visit(type)
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_missing_handler(type, props, method_name)
|
14
|
+
return props.merge(type: type) if type.class == Class
|
15
|
+
|
16
|
+
puts "Missing handler for #{type.inspect} with props #{props.inspect} and method_name :#{method_name}"
|
17
|
+
props
|
18
|
+
end
|
19
|
+
|
20
|
+
on(:undefined) do |type, props|
|
21
|
+
props
|
22
|
+
end
|
23
|
+
|
24
|
+
on(:any) do |type, props|
|
25
|
+
props
|
26
|
+
end
|
27
|
+
|
28
|
+
on(:pipeline) do |type, props|
|
29
|
+
visit(type.type, props)
|
30
|
+
end
|
31
|
+
|
32
|
+
on(:step) do |type, props|
|
33
|
+
props.merge(type._metadata)
|
34
|
+
end
|
35
|
+
|
36
|
+
on(::Regexp) do |type, props|
|
37
|
+
props.merge(pattern: type)
|
38
|
+
end
|
39
|
+
|
40
|
+
on(::Range) do |type, props|
|
41
|
+
props.merge(match: type)
|
42
|
+
end
|
43
|
+
|
44
|
+
on(:match) do |type, props|
|
45
|
+
visit(type.matcher, props)
|
46
|
+
end
|
47
|
+
|
48
|
+
on(:hash) do |type, props|
|
49
|
+
props.merge(type: Hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
on(:and) do |type, props|
|
53
|
+
left = visit(type.left)
|
54
|
+
right = visit(type.right)
|
55
|
+
type = right[:type] || left[:type]
|
56
|
+
props = props.merge(left).merge(right)
|
57
|
+
props = props.merge(type:) if type
|
58
|
+
props
|
59
|
+
end
|
60
|
+
|
61
|
+
on(:or) do |type, props|
|
62
|
+
child_metas = [visit(type.left), visit(type.right)]
|
63
|
+
types = child_metas.map { |child| child[:type] }.flatten.compact
|
64
|
+
types = types.first if types.size == 1
|
65
|
+
child_metas.reduce(props) do |acc, child|
|
66
|
+
acc.merge(child)
|
67
|
+
end.merge(type: types)
|
68
|
+
end
|
69
|
+
|
70
|
+
on(:value) do |type, props|
|
71
|
+
visit(type.value, props)
|
72
|
+
end
|
73
|
+
|
74
|
+
on(:transform) do |type, props|
|
75
|
+
props.merge(type: type.target_type)
|
76
|
+
end
|
77
|
+
|
78
|
+
on(:static) do |type, props|
|
79
|
+
props.merge(static: type.value)
|
80
|
+
end
|
81
|
+
|
82
|
+
on(:rules) do |type, props|
|
83
|
+
type.rules.reduce(props) do |acc, rule|
|
84
|
+
acc.merge(rule.name => rule.arg_value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
on(:boolean) do |type, props|
|
89
|
+
props.merge(type: 'boolean')
|
90
|
+
end
|
91
|
+
|
92
|
+
on(:metadata) do |type, props|
|
93
|
+
props.merge(type.metadata)
|
94
|
+
end
|
95
|
+
|
96
|
+
on(:hash_map) do |type, props|
|
97
|
+
props.merge(type: Hash)
|
98
|
+
end
|
99
|
+
|
100
|
+
on(:build) do |type, props|
|
101
|
+
visit(type.type, props)
|
102
|
+
end
|
103
|
+
|
104
|
+
on(:array) do |type, props|
|
105
|
+
props.merge(type: Array)
|
106
|
+
end
|
107
|
+
|
108
|
+
on(:tuple) do |type, props|
|
109
|
+
props.merge(type: Array)
|
110
|
+
end
|
111
|
+
|
112
|
+
on(:tagged_hash) do |type, props|
|
113
|
+
props.merge(type: Hash)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/plumb/not.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class Not
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :step
|
10
|
+
|
11
|
+
def initialize(step, errors: nil)
|
12
|
+
@step = step
|
13
|
+
@errors = errors
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
private def _inspect
|
18
|
+
%(Not(#{@step.inspect}))
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(result)
|
22
|
+
result = @step.call(result)
|
23
|
+
result.valid? ? result.invalid(errors: @errors) : result.valid
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/plumb/or.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class Or
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :left, :right
|
10
|
+
|
11
|
+
def initialize(left, right)
|
12
|
+
@left = left
|
13
|
+
@right = right
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
private def _inspect
|
18
|
+
%((#{@left.inspect} | #{@right.inspect}))
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(result)
|
22
|
+
left_result = @left.call(result)
|
23
|
+
return left_result if left_result.valid?
|
24
|
+
|
25
|
+
right_result = @right.call(result)
|
26
|
+
right_result.valid? ? right_result : result.invalid(errors: [left_result.errors, right_result.errors].flatten)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class Pipeline
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
class AroundStep
|
10
|
+
include Steppable
|
11
|
+
|
12
|
+
def initialize(step, block)
|
13
|
+
@step = step
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(result)
|
18
|
+
@block.call(@step, result)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :type
|
23
|
+
|
24
|
+
def initialize(type = Types::Any, &setup)
|
25
|
+
@type = type
|
26
|
+
@around_blocks = []
|
27
|
+
return unless block_given?
|
28
|
+
|
29
|
+
configure(&setup)
|
30
|
+
freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(result)
|
34
|
+
@type.call(result)
|
35
|
+
end
|
36
|
+
|
37
|
+
def step(callable = nil, &block)
|
38
|
+
callable ||= block
|
39
|
+
unless is_a_step?(callable)
|
40
|
+
raise ArgumentError,
|
41
|
+
"#step expects an interface #call(Result) Result, but got #{callable.inspect}"
|
42
|
+
end
|
43
|
+
|
44
|
+
callable = @around_blocks.reduce(callable) { |cl, bl| AroundStep.new(cl, bl) } if @around_blocks.any?
|
45
|
+
@type >>= callable
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def around(callable = nil, &block)
|
50
|
+
@around_blocks << (callable || block)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def configure(&setup)
|
57
|
+
case setup.arity
|
58
|
+
when 1
|
59
|
+
setup.call(self)
|
60
|
+
when 0
|
61
|
+
instance_eval(&setup)
|
62
|
+
else
|
63
|
+
raise ArgumentError, 'setup block must have arity of 0 or 1'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def is_a_step?(callable)
|
68
|
+
return false unless callable.respond_to?(:call)
|
69
|
+
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/plumb/result.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plumb
|
4
|
+
class Result
|
5
|
+
class << self
|
6
|
+
def valid(value)
|
7
|
+
Valid.new(value)
|
8
|
+
end
|
9
|
+
|
10
|
+
def invalid(value = nil, errors: nil)
|
11
|
+
Invalid.new(value, errors:)
|
12
|
+
end
|
13
|
+
|
14
|
+
def wrap(value)
|
15
|
+
return value if value.is_a?(Result)
|
16
|
+
|
17
|
+
valid(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :value, :errors
|
22
|
+
|
23
|
+
def initialize(value, errors: nil)
|
24
|
+
@value = value
|
25
|
+
@errors = errors
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid? = true
|
29
|
+
def invalid? = false
|
30
|
+
|
31
|
+
def inspect
|
32
|
+
%(<#{self.class}##{object_id} value:#{value.inspect} errors:#{errors.inspect}>)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset(val)
|
36
|
+
@value = val
|
37
|
+
@errors = nil
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid(val = value)
|
42
|
+
Result.valid(val)
|
43
|
+
end
|
44
|
+
|
45
|
+
def invalid(val = value, errors: nil)
|
46
|
+
Result.invalid(val, errors:)
|
47
|
+
end
|
48
|
+
|
49
|
+
class Valid < self
|
50
|
+
def map(callable)
|
51
|
+
callable.call(self)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Invalid < self
|
56
|
+
def valid? = false
|
57
|
+
def invalid? = true
|
58
|
+
|
59
|
+
def map(_)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/plumb/rules.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class Rules
|
7
|
+
UnsupportedRuleError = Class.new(StandardError)
|
8
|
+
UndefinedRuleError = Class.new(KeyError)
|
9
|
+
|
10
|
+
class Registry
|
11
|
+
RuleDef = Data.define(:name, :error_tpl, :callable, :metadata_key, :expects) do
|
12
|
+
def supports?(type)
|
13
|
+
types = [type].flatten # may be an array of types for OR logic
|
14
|
+
case expects
|
15
|
+
when Symbol
|
16
|
+
types.all? { |type| type.public_instance_methods.include?(expects) }
|
17
|
+
when Class then types.all? { |type| type <= expects }
|
18
|
+
when Object then true
|
19
|
+
else raise "Unexpected expects: #{expects}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Rule = Data.define(:rule_def, :arg_value, :error_str) do
|
25
|
+
def self.build(rule_def, arg_value)
|
26
|
+
error_str = format(rule_def.error_tpl, value: arg_value)
|
27
|
+
new(rule_def, arg_value, error_str)
|
28
|
+
end
|
29
|
+
|
30
|
+
def node_name = :"rule_#{rule_def.name}"
|
31
|
+
def name = rule_def.name
|
32
|
+
def metadata_key = rule_def.metadata_key
|
33
|
+
|
34
|
+
def error_for(result)
|
35
|
+
return nil if rule_def.callable.call(result, arg_value)
|
36
|
+
|
37
|
+
error_str
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@definitions = Hash.new { |h, k| h[k] = Set.new }
|
43
|
+
end
|
44
|
+
|
45
|
+
def define(name, error_tpl, callable = nil, metadata_key: name, expects: Object, &block)
|
46
|
+
name = name.to_sym
|
47
|
+
callable ||= block
|
48
|
+
@definitions[name] << RuleDef.new(name:, error_tpl:, callable:, metadata_key:, expects:)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Ex. size: 3, match: /foo/
|
52
|
+
def resolve(rule_specs, for_type)
|
53
|
+
rule_specs.map do |(name, arg_value)|
|
54
|
+
rule_defs = @definitions.fetch(name.to_sym) { raise UndefinedRuleError, "no rule defined with :#{name}" }
|
55
|
+
rule_def = rule_defs.find { |rd| rd.supports?(for_type) }
|
56
|
+
unless rule_def
|
57
|
+
raise UnsupportedRuleError, "No :#{name} rule for type #{for_type}" unless for_type.is_a?(Array)
|
58
|
+
|
59
|
+
raise UnsupportedRuleError,
|
60
|
+
"Can't apply :#{name} rule for types #{for_type}. All types must support the same rule implementation"
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
Rule.build(rule_def, arg_value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
include Steppable
|
70
|
+
|
71
|
+
def self.registry
|
72
|
+
@registry ||= Registry.new
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.define(...)
|
76
|
+
registry.define(...)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Ex. new(size: 3, match: /foo/)
|
80
|
+
attr_reader :rules
|
81
|
+
|
82
|
+
def initialize(rule_specs, for_type)
|
83
|
+
@rules = self.class.registry.resolve(rule_specs, for_type).freeze
|
84
|
+
freeze
|
85
|
+
end
|
86
|
+
|
87
|
+
def call(result)
|
88
|
+
errors = []
|
89
|
+
err = nil
|
90
|
+
@rules.each do |rule|
|
91
|
+
err = rule.error_for(result)
|
92
|
+
errors << err if err
|
93
|
+
end
|
94
|
+
return result unless errors.any?
|
95
|
+
|
96
|
+
result.invalid(errors: errors.join(', '))
|
97
|
+
end
|
98
|
+
|
99
|
+
private def _inspect
|
100
|
+
+'Rules(' << @rules.map { |r| [r.name, r.arg_value].join(': ') }.join(', ') << +')'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/plumb/schema.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'plumb/json_schema_visitor'
|
5
|
+
|
6
|
+
module Plumb
|
7
|
+
class Schema
|
8
|
+
include Steppable
|
9
|
+
|
10
|
+
def self.wrap(sc = nil, &block)
|
11
|
+
raise ArgumentError, 'expected a block or a schema' if sc.nil? && !block_given?
|
12
|
+
|
13
|
+
if sc
|
14
|
+
raise ArgumentError, 'expected a Steppable' unless sc.is_a?(Steppable)
|
15
|
+
|
16
|
+
return sc
|
17
|
+
end
|
18
|
+
|
19
|
+
new(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :fields
|
23
|
+
|
24
|
+
def initialize(hash = Types::Hash, &block)
|
25
|
+
@pipeline = Types::Any
|
26
|
+
@before = Types::Any
|
27
|
+
@after = Types::Any
|
28
|
+
@_schema = {}
|
29
|
+
@_hash = hash
|
30
|
+
@fields = SymbolAccessHash.new({})
|
31
|
+
|
32
|
+
setup(&block) if block_given?
|
33
|
+
|
34
|
+
finish
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"#{self.class}#{fields.keys.inspect}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def before(callable = nil, &block)
|
42
|
+
@before >>= callable || block
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def after(callable = nil, &block)
|
47
|
+
@after >>= callable || block
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def json_schema
|
52
|
+
JSONSchemaVisitor.call(_hash).to_h
|
53
|
+
end
|
54
|
+
|
55
|
+
def call(result)
|
56
|
+
@pipeline.call(result)
|
57
|
+
end
|
58
|
+
|
59
|
+
private def setup(&block)
|
60
|
+
case block.arity
|
61
|
+
when 1
|
62
|
+
yield self
|
63
|
+
when 0
|
64
|
+
instance_eval(&block)
|
65
|
+
else
|
66
|
+
raise ::ArgumentError, "#{self.class} expects a block with 0 or 1 argument, but got #{block.arity}"
|
67
|
+
end
|
68
|
+
@_hash = Types::Hash.schema(@fields.transform_values(&:_type))
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
private def finish
|
73
|
+
@pipeline = @before.freeze >> @_hash.freeze >> @after.freeze
|
74
|
+
@_schema.clear.freeze
|
75
|
+
freeze
|
76
|
+
end
|
77
|
+
|
78
|
+
def field(key)
|
79
|
+
key = Key.new(key.to_sym)
|
80
|
+
@fields[key] = Field.new(key)
|
81
|
+
end
|
82
|
+
|
83
|
+
def field?(key)
|
84
|
+
key = Key.new(key.to_sym, optional: true)
|
85
|
+
@fields[key] = Field.new(key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def +(other)
|
89
|
+
self.class.new(_hash + other._hash)
|
90
|
+
end
|
91
|
+
|
92
|
+
def &(other)
|
93
|
+
self.class.new(_hash & other._hash)
|
94
|
+
end
|
95
|
+
|
96
|
+
def merge(other = nil, &block)
|
97
|
+
other = self.class.wrap(other, &block)
|
98
|
+
self + other
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
attr_reader :_hash
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
attr_reader :_schema
|
108
|
+
|
109
|
+
class SymbolAccessHash < SimpleDelegator
|
110
|
+
def [](key)
|
111
|
+
__getobj__[Key.wrap(key)]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Field
|
116
|
+
include Callable
|
117
|
+
|
118
|
+
attr_reader :_type, :key
|
119
|
+
|
120
|
+
def initialize(key)
|
121
|
+
@key = key.to_sym
|
122
|
+
@_type = Types::Any
|
123
|
+
end
|
124
|
+
|
125
|
+
def call(result) = _type.call(result)
|
126
|
+
|
127
|
+
def type(steppable)
|
128
|
+
unless steppable.respond_to?(:call)
|
129
|
+
raise ArgumentError,
|
130
|
+
"expected a Plumb type, but got #{steppable.inspect}"
|
131
|
+
end
|
132
|
+
|
133
|
+
@_type >>= steppable
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
def schema(...)
|
138
|
+
@_type >>= Schema.wrap(...)
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
def array(...)
|
143
|
+
@_type >>= Types::Array[Schema.wrap(...)]
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
def default(v, &block)
|
148
|
+
@_type = @_type.default(v, &block)
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
def meta(md = nil)
|
153
|
+
@_type = @_type.meta(md) if md
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
def metadata = @_type.metadata
|
158
|
+
|
159
|
+
def options(opts)
|
160
|
+
@_type = @_type.rule(included_in: opts)
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
def optional
|
165
|
+
@_type = Types::Nil | @_type
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
def present
|
170
|
+
@_type = @_type.present
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
def required
|
175
|
+
@_type = Types::Undefined.invalid(errors: 'is required') >> @_type
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
def rule(...)
|
180
|
+
@_type = @_type.rule(...)
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
def inspect
|
185
|
+
"#{self.class}[#{@_type.inspect}]"
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
attr_reader :registry
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class StaticClass
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value = Undefined)
|
12
|
+
raise ArgumentError, 'value must be frozen' unless value.frozen?
|
13
|
+
|
14
|
+
@value = value
|
15
|
+
freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](value)
|
19
|
+
self.class.new(value)
|
20
|
+
end
|
21
|
+
|
22
|
+
private def _inspect
|
23
|
+
%(#{name}[#{@value.inspect}])
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(result)
|
27
|
+
result.valid(@value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/plumb/step.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
class Step
|
7
|
+
include Steppable
|
8
|
+
|
9
|
+
attr_reader :_metadata
|
10
|
+
|
11
|
+
def initialize(callable = nil, &block)
|
12
|
+
@_metadata = callable.respond_to?(:metadata) ? callable.metadata : BLANK_HASH
|
13
|
+
@callable = callable || block
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(result)
|
18
|
+
@callable.call(result)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|