plumb 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: {}
|