plumb 0.0.2 → 0.0.3
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 +4 -4
- data/README.md +287 -119
- data/examples/env_config.rb +122 -0
- data/examples/weekdays.rb +1 -1
- data/lib/plumb/hash_class.rb +1 -3
- data/lib/plumb/json_schema_visitor.rb +49 -6
- data/lib/plumb/match_class.rb +3 -4
- data/lib/plumb/metadata_visitor.rb +10 -1
- data/lib/plumb/policies.rb +81 -0
- data/lib/plumb/policy.rb +31 -0
- data/lib/plumb/schema.rb +2 -2
- data/lib/plumb/steppable.rb +24 -39
- data/lib/plumb/types.rb +114 -23
- data/lib/plumb/version.rb +1 -1
- data/lib/plumb/visitor_handlers.rb +6 -1
- data/lib/plumb.rb +52 -1
- metadata +8 -6
- data/lib/plumb/rules.rb +0 -102
@@ -12,12 +12,17 @@ module Plumb
|
|
12
12
|
DEFAULT = 'default'
|
13
13
|
ANY_OF = 'anyOf'
|
14
14
|
ALL_OF = 'allOf'
|
15
|
+
NOT = 'not'
|
15
16
|
ENUM = 'enum'
|
16
17
|
CONST = 'const'
|
17
18
|
ITEMS = 'items'
|
18
19
|
PATTERN = 'pattern'
|
19
20
|
MINIMUM = 'minimum'
|
20
21
|
MAXIMUM = 'maximum'
|
22
|
+
MIN_ITEMS = 'minItems'
|
23
|
+
MAX_ITEMS = 'maxItems'
|
24
|
+
MIN_LENGTH = 'minLength'
|
25
|
+
MAX_LENGTH = 'maxLength'
|
21
26
|
|
22
27
|
def self.call(node)
|
23
28
|
{
|
@@ -78,7 +83,7 @@ module Plumb
|
|
78
83
|
end
|
79
84
|
|
80
85
|
on(:not) do |node, props|
|
81
|
-
props.merge(
|
86
|
+
props.merge(NOT => visit(node.step))
|
82
87
|
end
|
83
88
|
|
84
89
|
on(:value) do |node, props|
|
@@ -113,14 +118,52 @@ module Plumb
|
|
113
118
|
visit(node.value, props)
|
114
119
|
end
|
115
120
|
|
116
|
-
on(:
|
117
|
-
|
118
|
-
|
121
|
+
on(:policy) do |node, props|
|
122
|
+
props = visit(node.step, props)
|
123
|
+
method_name = :"visit_#{node.policy_name}_policy"
|
124
|
+
if respond_to?(method_name)
|
125
|
+
send(method_name, node, props)
|
126
|
+
else
|
127
|
+
props
|
119
128
|
end
|
120
129
|
end
|
121
130
|
|
122
|
-
on(:
|
123
|
-
props.merge(ENUM => node.
|
131
|
+
on(:options_policy) do |node, props|
|
132
|
+
props.merge(ENUM => node.arg)
|
133
|
+
end
|
134
|
+
|
135
|
+
on(:size_policy) do |node, props|
|
136
|
+
opts = {}
|
137
|
+
case props[TYPE]
|
138
|
+
when 'array'
|
139
|
+
case node.arg
|
140
|
+
when Range
|
141
|
+
opts[MIN_ITEMS] = node.arg.min if node.arg.begin
|
142
|
+
opts[MAX_ITEMS] = node.arg.max if node.arg.end
|
143
|
+
when Numeric
|
144
|
+
opts[MIN_ITEMS] = node.arg
|
145
|
+
opts[MAX_ITEMS] = node.arg
|
146
|
+
end
|
147
|
+
when 'string'
|
148
|
+
case node.arg
|
149
|
+
when Range
|
150
|
+
opts[MIN_LENGTH] = node.arg.min if node.arg.begin
|
151
|
+
opts[MAX_LENGTH] = node.arg.max if node.arg.end
|
152
|
+
when Numeric
|
153
|
+
opts[MIN_LENGTH] = node.arg
|
154
|
+
opts[MAX_LENGTH] = node.arg
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
props.merge(opts)
|
159
|
+
end
|
160
|
+
|
161
|
+
on(:excluded_from_policy) do |node, props|
|
162
|
+
props.merge(NOT => { ENUM => node.arg })
|
163
|
+
end
|
164
|
+
|
165
|
+
on(Proc) do |_node, props|
|
166
|
+
props
|
124
167
|
end
|
125
168
|
|
126
169
|
on(:match) do |node, props|
|
data/lib/plumb/match_class.rb
CHANGED
@@ -8,11 +8,12 @@ module Plumb
|
|
8
8
|
|
9
9
|
attr_reader :matcher
|
10
10
|
|
11
|
-
def initialize(matcher = Undefined, error: nil)
|
11
|
+
def initialize(matcher = Undefined, error: nil, label: nil)
|
12
12
|
raise TypeError 'matcher must respond to #===' unless matcher.respond_to?(:===)
|
13
13
|
|
14
14
|
@matcher = matcher
|
15
15
|
@error = error.nil? ? build_error(matcher) : (error % matcher)
|
16
|
+
@label = matcher.is_a?(Class) ? matcher.inspect : "Match(#{label || @matcher.inspect})"
|
16
17
|
freeze
|
17
18
|
end
|
18
19
|
|
@@ -22,9 +23,7 @@ module Plumb
|
|
22
23
|
|
23
24
|
private
|
24
25
|
|
25
|
-
def _inspect
|
26
|
-
@matcher.inspect
|
27
|
-
end
|
26
|
+
def _inspect = @label
|
28
27
|
|
29
28
|
def build_error(matcher)
|
30
29
|
case matcher
|
@@ -77,7 +77,12 @@ module Plumb
|
|
77
77
|
end
|
78
78
|
|
79
79
|
on(:static) do |node, props|
|
80
|
-
|
80
|
+
type = node.value.is_a?(Class) ? node.value : node.value.class
|
81
|
+
props.merge(static: node.value, type:)
|
82
|
+
end
|
83
|
+
|
84
|
+
on(:policy) do |node, props|
|
85
|
+
visit(node.step, props).merge(node.policy_name => node.arg)
|
81
86
|
end
|
82
87
|
|
83
88
|
on(:rules) do |node, props|
|
@@ -117,5 +122,9 @@ module Plumb
|
|
117
122
|
on(:tagged_hash) do |_node, props|
|
118
123
|
props.merge(type: Hash)
|
119
124
|
end
|
125
|
+
|
126
|
+
on(Proc) do |_node, props|
|
127
|
+
props
|
128
|
+
end
|
120
129
|
end
|
121
130
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/policies'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
# A policy registry for Plumb
|
7
|
+
# It holds and gets registered policies.
|
8
|
+
# Policies are callable objects that act as factories for type compositions.
|
9
|
+
class Policies
|
10
|
+
UnknownPolicyError = Class.new(StandardError)
|
11
|
+
MethodAlreadyDefinedError = Class.new(StandardError)
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@policies = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Register a policy for all or specific outpyt types.
|
18
|
+
# Example for a policy that works for all types:
|
19
|
+
# #register(Object, :my_policy, ->(node, arg) { ... })
|
20
|
+
# Example for a policy that works for a specific type:
|
21
|
+
# #register(String, :my_policy, ->(node, arg) { ... })
|
22
|
+
# Example for a policy that works for a specific interface:
|
23
|
+
# #register(:size, :my_policy, ->(node, arg) { ... })
|
24
|
+
#
|
25
|
+
# The policy callable takes the step it is applied to, a policy argument (if any) and a policy block (if any).
|
26
|
+
# Example for a policy #default(default_value = Undefined) { 'some-default-value' }
|
27
|
+
# policy = proc do |type, default_value = Undefined, &block|
|
28
|
+
# type | (Plumb::Types::Undefined >> Plumb::Types::Static[default_value])
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @param for_type [Class, Symbol] the type the policy is for.
|
32
|
+
# @param name [Symbol] the name of the policy.
|
33
|
+
# @param policy [Proc] the policy to register.
|
34
|
+
def register(for_type, name, policy)
|
35
|
+
@policies[name] ||= {}
|
36
|
+
@policies[name][for_type] = policy
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get a policy for a given type.
|
40
|
+
# @param types [Array<Class>] the types
|
41
|
+
# @param name [Symbol] the policy name
|
42
|
+
# @return [#call] the policy callable
|
43
|
+
# @raise [UnknownPolicyError] if the policy is not registered for the given types
|
44
|
+
def get(types, name)
|
45
|
+
if (pol = resolve_shared_policy(types, name))
|
46
|
+
pol
|
47
|
+
elsif (pol = @policies.dig(name, Object))
|
48
|
+
raise UnknownPolicyError, "Unknown policy #{name} for #{types.inspect}" unless pol
|
49
|
+
|
50
|
+
pol
|
51
|
+
else
|
52
|
+
raise UnknownPolicyError, "Unknown or incompatible policy #{name} for #{types.inspect}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def resolve_shared_policy(types, name)
|
59
|
+
pols = types.map do |type|
|
60
|
+
resolve_policy(type, name)
|
61
|
+
end.uniq
|
62
|
+
pols.size == 1 ? pols.first : nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def resolve_policy(type, name)
|
66
|
+
policies = @policies[name]
|
67
|
+
return nil unless policies
|
68
|
+
|
69
|
+
# { Object => policy1, String => policy2, size: policy3 }
|
70
|
+
#
|
71
|
+
policies.find do |for_type, _pol|
|
72
|
+
case for_type
|
73
|
+
when Symbol # :size
|
74
|
+
type.instance_methods.include?(for_type)
|
75
|
+
when Class # String
|
76
|
+
for_type == type
|
77
|
+
end
|
78
|
+
end&.last
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/plumb/policy.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'plumb/steppable'
|
4
|
+
|
5
|
+
module Plumb
|
6
|
+
# Wrap a policy composition ("step") in a Policy object.
|
7
|
+
# So that visitors such as JSONSchema and Metadata visitors
|
8
|
+
# can define dedicated handlers for policies, if they need to.
|
9
|
+
class Policy
|
10
|
+
include Steppable
|
11
|
+
|
12
|
+
attr_reader :policy_name, :arg, :step
|
13
|
+
|
14
|
+
# @param policy_name [Symbol]
|
15
|
+
# @param arg [Object, nil] the argument to the policy, if any.
|
16
|
+
# @param step [Step] the step composition wrapped by this policy.
|
17
|
+
def initialize(policy_name, arg, step)
|
18
|
+
@policy_name = policy_name
|
19
|
+
@arg = arg
|
20
|
+
@step = step
|
21
|
+
freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
# The standard Step interface.
|
25
|
+
# @param result [Result::Valid]
|
26
|
+
# @return [Result::Valid, Result::Invalid]
|
27
|
+
def call(result) = @step.call(result)
|
28
|
+
|
29
|
+
private def _inspect = @step.inspect
|
30
|
+
end
|
31
|
+
end
|
data/lib/plumb/schema.rb
CHANGED
data/lib/plumb/steppable.rb
CHANGED
@@ -10,6 +10,7 @@ module Plumb
|
|
10
10
|
|
11
11
|
def to_s = inspect
|
12
12
|
def node_name = :undefined
|
13
|
+
def empty? = true
|
13
14
|
end
|
14
15
|
|
15
16
|
TypeError = Class.new(::TypeError)
|
@@ -111,7 +112,7 @@ module Plumb
|
|
111
112
|
end
|
112
113
|
|
113
114
|
def check(errors = 'did not pass the check', &block)
|
114
|
-
self >> MatchClass.new(block, error: errors)
|
115
|
+
self >> MatchClass.new(block, error: errors, label: errors)
|
115
116
|
end
|
116
117
|
|
117
118
|
def meta(data = {})
|
@@ -136,22 +137,6 @@ module Plumb
|
|
136
137
|
|
137
138
|
def [](val) = match(val)
|
138
139
|
|
139
|
-
DefaultProc = proc do |callable|
|
140
|
-
proc do |result|
|
141
|
-
result.valid(callable.call)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def default(val = Undefined, &block)
|
146
|
-
val_type = if val == Undefined
|
147
|
-
DefaultProc.call(block)
|
148
|
-
else
|
149
|
-
Types::Static[val]
|
150
|
-
end
|
151
|
-
|
152
|
-
self | (Types::Undefined >> val_type)
|
153
|
-
end
|
154
|
-
|
155
140
|
class Node
|
156
141
|
include Steppable
|
157
142
|
|
@@ -171,29 +156,28 @@ module Plumb
|
|
171
156
|
Node.new(node_name, self, metadata)
|
172
157
|
end
|
173
158
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
end
|
159
|
+
# Register a policy for this step.
|
160
|
+
# Mode 1.a: #policy(:name, arg) a single policy with an argument
|
161
|
+
# Mode 1.b: #policy(:name) a single policy without an argument
|
162
|
+
# Mode 2: #policy(p1: value, p2: value) multiple policies with arguments
|
163
|
+
# The latter mode will be expanded to multiple #policy calls.
|
164
|
+
# @return [Step]
|
165
|
+
def policy(*args, &blk)
|
166
|
+
case args
|
167
|
+
in [::Symbol => name, *rest] # #policy(:name, arg)
|
168
|
+
types = Array(metadata[:type]).uniq
|
185
169
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
in [::Hash => rules]
|
191
|
-
rules
|
192
|
-
else
|
193
|
-
raise ArgumentError, "expected 1 or 2 arguments, but got #{args.size}"
|
194
|
-
end
|
170
|
+
bargs = [self]
|
171
|
+
bargs << rest.first if rest.any?
|
172
|
+
block = Plumb.policies.get(types, name)
|
173
|
+
pol = block.call(*bargs, &blk)
|
195
174
|
|
196
|
-
|
175
|
+
Policy.new(name, rest.first, pol)
|
176
|
+
in [::Hash => opts] # #policy(p1: value, p2: value)
|
177
|
+
opts.reduce(self) { |step, (name, value)| step.policy(name, value) }
|
178
|
+
else
|
179
|
+
raise ArgumentError, "expected a symbol or hash, got #{args.inspect}"
|
180
|
+
end
|
197
181
|
end
|
198
182
|
|
199
183
|
def ===(other)
|
@@ -217,7 +201,7 @@ module Plumb
|
|
217
201
|
inspect
|
218
202
|
end
|
219
203
|
|
220
|
-
# Build a step that will invoke
|
204
|
+
# Build a step that will invoke one or more methods on the value.
|
221
205
|
# Ex 1: Types::String.invoke(:downcase)
|
222
206
|
# Ex 2: Types::Array.invoke(:[], 1)
|
223
207
|
# Ex 3 chain of methods: Types::String.invoke([:downcase, :to_sym])
|
@@ -240,5 +224,6 @@ end
|
|
240
224
|
|
241
225
|
require 'plumb/deferred'
|
242
226
|
require 'plumb/transform'
|
227
|
+
require 'plumb/policy'
|
243
228
|
require 'plumb/build'
|
244
229
|
require 'plumb/metadata'
|
data/lib/plumb/types.rb
CHANGED
@@ -3,25 +3,127 @@
|
|
3
3
|
require 'bigdecimal'
|
4
4
|
|
5
5
|
module Plumb
|
6
|
-
|
7
|
-
|
6
|
+
# Define core policies
|
7
|
+
# Allowed options for an array type.
|
8
|
+
# It validates that each element is in the options array.
|
9
|
+
# Usage:
|
10
|
+
# type = Types::Array.options(['a', 'b'])
|
11
|
+
policy :options, helper: true, for_type: ::Array do |type, opts|
|
12
|
+
type.check("must be included in #{opts.inspect}") do |v|
|
13
|
+
v.all? { |val| opts.include?(val) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Generic options policy for all other types.
|
18
|
+
# Usage:
|
19
|
+
# type = Types::String.options(['a', 'b'])
|
20
|
+
policy :options do |type, opts|
|
21
|
+
type.check("must be included in #{opts.inspect}") do |v|
|
22
|
+
opts.include?(v)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Validate that array elements are NOT in the options array.
|
27
|
+
# Usage:
|
28
|
+
# type = Types::Array.policy(excluded_from: ['a', 'b'])
|
29
|
+
policy :excluded_from, for_type: ::Array do |type, opts|
|
30
|
+
type.check("must not be included in #{opts.inspect}") do |v|
|
31
|
+
v.none? { |val| opts.include?(val) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Usage:
|
36
|
+
# type = Types::String.policy(excluded_from: ['a', 'b'])
|
37
|
+
policy :excluded_from do |type, opts|
|
38
|
+
type.check("must not be included in #{opts.inspect}") do |v|
|
39
|
+
!opts.include?(v)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Validate #size against a number or any object that responds to #===.
|
44
|
+
# This works with any type that repsonds to #size.
|
45
|
+
# Usage:
|
46
|
+
# type = Types::String.policy(size: 10)
|
47
|
+
# type = Types::Integer.policy(size: 1..10)
|
48
|
+
# type = Types::Array.policy(size: 1..)
|
49
|
+
# type = Types::Any[Set].policy(size: 1..)
|
50
|
+
policy :size, for_type: :size do |type, size|
|
51
|
+
type.check("must be of size #{size}") { |v| size === v.size }
|
8
52
|
end
|
9
|
-
|
10
|
-
|
53
|
+
|
54
|
+
# Validate that an object is not #empty? nor #nil?
|
55
|
+
# Usage:
|
56
|
+
# Types::String.present
|
57
|
+
# Types::Array.present
|
58
|
+
policy :present, helper: true do |type, *_args|
|
59
|
+
type.check('must be present') do |v|
|
60
|
+
if v.respond_to?(:empty?)
|
61
|
+
!v.empty?
|
62
|
+
else
|
63
|
+
!v.nil?
|
64
|
+
end
|
65
|
+
end
|
11
66
|
end
|
12
|
-
|
13
|
-
|
67
|
+
|
68
|
+
# Allow nil values for a type.
|
69
|
+
# Usage:
|
70
|
+
# nullable_int = Types::Integer.nullable
|
71
|
+
# nullable_int.parse(nil) # => nil
|
72
|
+
# nullable_int.parse(10) # => 10
|
73
|
+
# nullable_int.parse('nope') # => error: not an Integer
|
74
|
+
policy :nullable, helper: true do |type, *_args|
|
75
|
+
Types::Nil | type
|
14
76
|
end
|
15
|
-
|
16
|
-
|
77
|
+
|
78
|
+
# Validate that a value responds to a method
|
79
|
+
# Usage:
|
80
|
+
# type = Types::Any.policy(respond_to: :upcase)
|
81
|
+
# type = Types::Any.policy(respond_to: [:upcase, :strip])
|
82
|
+
policy :respond_to do |type, method_names|
|
83
|
+
type.check("must respond to #{method_names.inspect}") do |value|
|
84
|
+
Array(method_names).all? { |m| value.respond_to?(m) }
|
85
|
+
end
|
17
86
|
end
|
18
|
-
|
19
|
-
|
87
|
+
|
88
|
+
# Return a default value if the input value is Undefined (ie key not present in a Hash).
|
89
|
+
# Usage:
|
90
|
+
# type = Types::String.default('default')
|
91
|
+
# type.parse(Undefined) # => 'default'
|
92
|
+
# type.parse('yes') # => 'yes'
|
93
|
+
#
|
94
|
+
# Works with a block too:
|
95
|
+
# date = Type::Any[Date].default { Date.today }
|
96
|
+
#
|
97
|
+
policy :default, helper: true do |type, value = Undefined, &block|
|
98
|
+
val_type = if value == Undefined
|
99
|
+
Step.new(->(result) { result.valid(block.call) }, 'default proc')
|
100
|
+
else
|
101
|
+
Types::Static[value]
|
102
|
+
end
|
103
|
+
|
104
|
+
type | (Types::Undefined >> val_type)
|
20
105
|
end
|
21
|
-
|
22
|
-
|
106
|
+
|
107
|
+
# Split a string into an array. Default separator is /\s*,\s*/
|
108
|
+
# Usage:
|
109
|
+
# type = Types::String.split
|
110
|
+
# type.parse('a,b,c') # => ['a', 'b', 'c']
|
111
|
+
#
|
112
|
+
# Custom separator:
|
113
|
+
# type = Types::String.split(';')
|
114
|
+
module SplitPolicy
|
115
|
+
DEFAULT_SEPARATOR = /\s*,\s*/
|
116
|
+
|
117
|
+
def self.call(type, separator = DEFAULT_SEPARATOR)
|
118
|
+
type.invoke(:split, separator) >> Types::Array[String]
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.for_type = ::String
|
122
|
+
def self.helper = false
|
23
123
|
end
|
24
124
|
|
125
|
+
policy :split, SplitPolicy
|
126
|
+
|
25
127
|
module Types
|
26
128
|
extend TypeRegistry
|
27
129
|
|
@@ -43,17 +145,6 @@ module Plumb
|
|
43
145
|
Tuple = TupleClass.new
|
44
146
|
Hash = HashClass.new
|
45
147
|
Interface = InterfaceClass.new
|
46
|
-
# TODO: type-speficic concept of blank, via Rules
|
47
|
-
Blank = (
|
48
|
-
Undefined \
|
49
|
-
| Nil \
|
50
|
-
| String.value(BLANK_STRING) \
|
51
|
-
| Hash.value(BLANK_HASH) \
|
52
|
-
| Array.value(BLANK_ARRAY)
|
53
|
-
)
|
54
|
-
|
55
|
-
Present = Blank.invalid(errors: 'must be present')
|
56
|
-
Split = String.transform(::String) { |v| v.split(/\s*,\s*/) }
|
57
148
|
|
58
149
|
module Lax
|
59
150
|
NUMBER_EXPR = /^\d{1,3}(?:,\d{3})*(?:\.\d+)?$/
|
data/lib/plumb/version.rb
CHANGED
@@ -23,7 +23,12 @@ module Plumb
|
|
23
23
|
else
|
24
24
|
:"#{(node.is_a?(::Class) ? node : node.class)}_class"
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
|
+
visit_name(method_name, node, props)
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_name(method_name, node, props = BLANK_HASH)
|
31
|
+
method_name = :"visit_#{method_name}"
|
27
32
|
if respond_to?(method_name)
|
28
33
|
send(method_name, node, props)
|
29
34
|
else
|
data/lib/plumb.rb
CHANGED
@@ -1,6 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'plumb/policies'
|
4
|
+
|
3
5
|
module Plumb
|
6
|
+
@policies = Policies.new
|
7
|
+
|
8
|
+
def self.policies
|
9
|
+
@policies
|
10
|
+
end
|
11
|
+
|
12
|
+
# Register a policy with the given name and block.
|
13
|
+
# Optionally define a method on the Steppable method to call the policy.
|
14
|
+
# Example:
|
15
|
+
# Plumb.policy(:multiply_by, for_type: Integer, helper: true) do |step, factor, &block|
|
16
|
+
# step.transform(Integer) { |number| number * factor }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# type = Types::Integer.multiply_by(2)
|
20
|
+
# type.parse(10) # => 20
|
21
|
+
#
|
22
|
+
# @param name [Symbol] the name of the policy
|
23
|
+
# @param opts [Hash] options for the policy
|
24
|
+
# @yield [Step, Object, &block] the step (type), policy argument, and policy block, if any.
|
25
|
+
def self.policy(name, opts = {}, &block)
|
26
|
+
name = name.to_sym
|
27
|
+
if opts.is_a?(Hash) && block_given?
|
28
|
+
for_type = opts[:for_type] || Object
|
29
|
+
helper = opts[:helper] || false
|
30
|
+
elsif opts.respond_to?(:call) && opts.respond_to?(:for_type) && opts.respond_to?(:helper)
|
31
|
+
for_type = opts.for_type
|
32
|
+
helper = opts.helper
|
33
|
+
block = opts.method(:call)
|
34
|
+
else
|
35
|
+
raise ArgumentError, 'Expected a block or a hash with :for_type and :helper keys'
|
36
|
+
end
|
37
|
+
|
38
|
+
policies.register(for_type, name, block)
|
39
|
+
|
40
|
+
return self unless helper
|
41
|
+
|
42
|
+
if Steppable.instance_methods.include?(name)
|
43
|
+
raise Policies::MethodAlreadyDefinedError, "Method #{name} is already defined on Steppable"
|
44
|
+
end
|
45
|
+
|
46
|
+
Steppable.define_method(name) do |arg = Undefined, &bl|
|
47
|
+
if arg == Undefined
|
48
|
+
policy(name, &bl)
|
49
|
+
else
|
50
|
+
policy(name, arg, &bl)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
self
|
55
|
+
end
|
4
56
|
end
|
5
57
|
|
6
58
|
require 'plumb/result'
|
@@ -10,7 +62,6 @@ require 'plumb/any_class'
|
|
10
62
|
require 'plumb/step'
|
11
63
|
require 'plumb/and'
|
12
64
|
require 'plumb/pipeline'
|
13
|
-
require 'plumb/rules'
|
14
65
|
require 'plumb/static_class'
|
15
66
|
require 'plumb/value_class'
|
16
67
|
require 'plumb/match_class'
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plumb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismael Celis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bigdecimal
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: concurrent-ruby
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -53,6 +53,7 @@ files:
|
|
53
53
|
- examples/command_objects.rb
|
54
54
|
- examples/concurrent_downloads.rb
|
55
55
|
- examples/csv_stream.rb
|
56
|
+
- examples/env_config.rb
|
56
57
|
- examples/programmers.csv
|
57
58
|
- examples/weekdays.rb
|
58
59
|
- lib/plumb.rb
|
@@ -72,8 +73,9 @@ files:
|
|
72
73
|
- lib/plumb/not.rb
|
73
74
|
- lib/plumb/or.rb
|
74
75
|
- lib/plumb/pipeline.rb
|
76
|
+
- lib/plumb/policies.rb
|
77
|
+
- lib/plumb/policy.rb
|
75
78
|
- lib/plumb/result.rb
|
76
|
-
- lib/plumb/rules.rb
|
77
79
|
- lib/plumb/schema.rb
|
78
80
|
- lib/plumb/static_class.rb
|
79
81
|
- lib/plumb/step.rb
|
@@ -87,7 +89,7 @@ files:
|
|
87
89
|
- lib/plumb/value_class.rb
|
88
90
|
- lib/plumb/version.rb
|
89
91
|
- lib/plumb/visitor_handlers.rb
|
90
|
-
homepage:
|
92
|
+
homepage: https://github.com/ismasan/plumb
|
91
93
|
licenses:
|
92
94
|
- MIT
|
93
95
|
metadata: {}
|