plumb 0.0.10 → 0.0.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61f58e5f4e5e3de9e6f830fc4f27ea9fb3771b50f3133781c2dcb0d5415a6d81
4
- data.tar.gz: 4f336457499ba6538fd3c0137a961e1ecac915aadd4b1438770d1ef075b9fef9
3
+ metadata.gz: 970d29a4515fd3e9938a93474cee93d37ee90e7b197c004d4a6e2fd9f36fef4a
4
+ data.tar.gz: ed7e89f47023ee9d1cdf53b3c39758ff69f0c0cb4399a4b972a4ad670f9c4cfd
5
5
  SHA512:
6
- metadata.gz: be801887ab34b217ac4d1c274bbfa58546a232566bb1cf43d051fcf0c39f1516fbcc6fea587cd5ce34455c869b957fe56b4c9e86b6fce8a8ac364457ba678bf7
7
- data.tar.gz: a5d010337c21e90439c54dc3e9a6a9e72aacf4c3bc89cf2d4c3c57c3bf245f8b7224ff8381d2f42e5377a2ffe5968eb718fc59ee5312165daaa4f9e85682cc1f
6
+ metadata.gz: 066e9e2d5e06c19286600e21bbe85da7304c87156241e7eae79c2dcc4abb30a370416dd4cfe343d66535521848b25c4e348a1bacc7f7aff71c5ab790c6afeae4
7
+ data.tar.gz: b6064b236ec5656b648bcc576c7ca0dbccfc172ec8615eba32e98a157ba2600ce282ab1e154b07a5be47999dcda2838698c639d51651b6e41e49f7dce6058d71
data/README.md CHANGED
@@ -328,6 +328,28 @@ type.resolve(['a', 'a', 'b']) # Valid
328
328
  type.resolve(['a', 'x', 'b']) # Failure
329
329
  ```
330
330
 
331
+ ### `#with`
332
+
333
+ The `#with` helper matches attributes of the object with values, using `#===`.
334
+
335
+ ```ruby
336
+ LimitedArray = Types::Array[String].with(size: 10)
337
+ LimitedString = Types::String.with(size: 10)
338
+ LimitedSet = Types::Any[Set].with(size: 10)
339
+ ```
340
+
341
+ The size is matched via `#===`, so ranges also work.
342
+
343
+ ```ruby
344
+ Password = Types::String.with(bytesize: 10..20)
345
+ ```
346
+
347
+ The helper accepts multiple attribute/valye pairs
348
+
349
+ ```ruby
350
+ JoeBloggs = Types::Any[User].with(first_name: 'Joe', last_name: 'Bloggs')
351
+ ```
352
+
331
353
  #### `#transform`
332
354
 
333
355
  Transform value. Requires specifying the resulting type of the value after transformation.
@@ -588,23 +610,7 @@ The opposite of `#options`, this policy validates that the value _is not_ includ
588
610
  Name = Types::String.policy(excluded_from: ['Joe', 'Joan'])
589
611
  ```
590
612
 
591
- #### `:size`
592
-
593
- Works for any value that responds to `#size` and validates that the value's size matches the argument.
594
-
595
- ```ruby
596
- LimitedArray = Types::Array[String].policy(size: 10)
597
- LimitedString = Types::String.policy(size: 10)
598
- LimitedSet = Types::Any[Set].policy(size: 10)
599
- ```
600
-
601
- The size is matched via `#===`, so ranges also work.
602
-
603
- ```ruby
604
- Password = Types::String.policy(size: 10..20)
605
- ```
606
-
607
- #### `:split` (strings only)
613
+ #### :split` (strings only)
608
614
 
609
615
  Splits string values by a separator (default: `,`).
610
616
 
@@ -67,7 +67,7 @@ module Types
67
67
  end
68
68
 
69
69
  ###################################
70
- # Program 1: idempoent download of images from the internet
70
+ # Program 1: idempotent download of images from the internet
71
71
  # If not present in the cache, images are downloaded and written to the cache.
72
72
  # Otherwise images are listed directly from the cache (files on disk).
73
73
  ###################################
@@ -10,7 +10,7 @@ module Types
10
10
  include Plumb::Types
11
11
 
12
12
  # Open a File
13
- # ex. file = FileStep.parse('./files/data.csv') # => File
13
+ # ex. file = OpenFile.parse('./files/data.csv') # => File
14
14
  OpenFile = String
15
15
  .check('no file for that path') { |s| ::File.exist?(s) }
16
16
  .build(::File)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plumb
4
+ class AttributeValueMatch
5
+ include Composable
6
+
7
+ attr_reader :type, :attr_name, :value
8
+
9
+ def initialize(type, attr_name, value)
10
+ @type = type
11
+ @attr_name = attr_name
12
+ @value = value
13
+ @error = "must have attribute #{attr_name} === #{value.inspect}"
14
+ freeze
15
+ end
16
+
17
+ def metadata = type.metadata
18
+
19
+ def call(result)
20
+ return result if value === result.value.public_send(attr_name)
21
+
22
+ result.invalid(errors: @error)
23
+ end
24
+ end
25
+ end
@@ -273,15 +273,16 @@ module Plumb
273
273
  class Node
274
274
  include Composable
275
275
 
276
- attr_reader :node_name, :type, :attributes
276
+ attr_reader :node_name, :type, :args
277
277
 
278
- def initialize(node_name, type, attributes = BLANK_HASH)
278
+ def initialize(node_name, type, args = BLANK_HASH)
279
279
  @node_name = node_name
280
280
  @type = type
281
- @attributes = attributes
281
+ @args = args
282
282
  freeze
283
283
  end
284
284
 
285
+ def metadata = type.metadata
285
286
  def call(result) = type.call(result)
286
287
  end
287
288
 
@@ -291,10 +292,22 @@ module Plumb
291
292
  # Ex. Types::Boolean is a compoition of Types::True | Types::False, but we want to treat it as a single node.
292
293
  #
293
294
  # @param node_name [Symbol]
294
- # @param metadata [Hash]
295
+ # @param args [Hash]
295
296
  # @return [Node]
296
- def as_node(node_name, metadata = BLANK_HASH)
297
- Node.new(node_name, self, metadata)
297
+ def as_node(node_name, args = BLANK_HASH)
298
+ Node.new(node_name, self, args)
299
+ end
300
+
301
+ # Check attributes of an object against values, using #===
302
+ # @example
303
+ # type = Types::Array.with(size: 1..10)
304
+ # type = Types::String.with(bytesize: 1..10)
305
+ #
306
+ # @param attrs [Hash]
307
+ def with(attrs)
308
+ attrs.reduce(self) do |t, (name, value)|
309
+ t >> AttributeValueMatch.new(t, name, value)
310
+ end
298
311
  end
299
312
 
300
313
  # Register a policy for this step.
@@ -425,6 +438,7 @@ module Plumb
425
438
  end
426
439
 
427
440
  require 'plumb/deferred'
441
+ require 'plumb/attribute_value_match'
428
442
  require 'plumb/transform'
429
443
  require 'plumb/policy'
430
444
  require 'plumb/build'
@@ -138,30 +138,41 @@ module Plumb
138
138
  end
139
139
  end
140
140
 
141
+ on(:attribute_value_match) do |node, props|
142
+ method_name = :"visit_with_#{node.attr_name}_attribute"
143
+ if respond_to?(method_name)
144
+ send(method_name, node, props)
145
+ else
146
+ props
147
+ end
148
+ end
149
+
141
150
  on(:options_policy) do |node, props|
142
151
  props.merge(ENUM => node.arg)
143
152
  end
144
153
 
145
- on(:size_policy) do |node, props|
146
- opts = {}
147
- case props[TYPE]
154
+ on(:with_size_attribute) do |node, props|
155
+ opts = visit(node.type)
156
+ value = node.value
157
+
158
+ case opts[TYPE]
148
159
  when 'array'
149
- case node.arg
160
+ case value
150
161
  when Range
151
- opts[MIN_ITEMS] = node.arg.min if node.arg.begin
152
- opts[MAX_ITEMS] = node.arg.max if node.arg.end
162
+ opts[MIN_ITEMS] = value.min if value.begin
163
+ opts[MAX_ITEMS] = value.max if value.end
153
164
  when Numeric
154
- opts[MIN_ITEMS] = node.arg
155
- opts[MAX_ITEMS] = node.arg
165
+ opts[MIN_ITEMS] = value
166
+ opts[MAX_ITEMS] = value
156
167
  end
157
168
  when 'string'
158
- case node.arg
169
+ case value
159
170
  when Range
160
- opts[MIN_LENGTH] = node.arg.min if node.arg.begin
161
- opts[MAX_LENGTH] = node.arg.max if node.arg.end
171
+ opts[MIN_LENGTH] = value.min if value.begin
172
+ opts[MAX_LENGTH] = value.max if value.end
162
173
  when Numeric
163
- opts[MIN_LENGTH] = node.arg
164
- opts[MAX_LENGTH] = node.arg
174
+ opts[MIN_LENGTH] = value
175
+ opts[MAX_LENGTH] = value
165
176
  end
166
177
  end
167
178
 
@@ -242,7 +253,11 @@ module Plumb
242
253
  opts = visit(element.class)
243
254
  if element.is_a?(::Numeric)
244
255
  opts[MINIMUM] = node.min if node.begin
245
- opts[MAXIMUM] = node.max if node.end
256
+ if node.end
257
+ max = node.end
258
+ max -= 1 if node.exclude_end?
259
+ opts[MAXIMUM] = max
260
+ end
246
261
  end
247
262
  props.merge(opts)
248
263
  end
data/lib/plumb/schema.rb CHANGED
@@ -178,6 +178,11 @@ module Plumb
178
178
  self
179
179
  end
180
180
 
181
+ def with(...)
182
+ @_type = @_type.with(...)
183
+ self
184
+ end
185
+
181
186
  def inspect
182
187
  "#{self.class}[#{@_type.inspect}]"
183
188
  end
data/lib/plumb/types.rb CHANGED
@@ -7,6 +7,7 @@ require 'time'
7
7
 
8
8
  module Plumb
9
9
  # Define core policies
10
+ #
10
11
  # Allowed options for an array type.
11
12
  # It validates that each element is in the options array.
12
13
  # Usage:
@@ -43,17 +44,6 @@ module Plumb
43
44
  end
44
45
  end
45
46
 
46
- # Validate #size against a number or any object that responds to #===.
47
- # This works with any type that repsonds to #size.
48
- # Usage:
49
- # type = Types::String.policy(size: 10)
50
- # type = Types::Integer.policy(size: 1..10)
51
- # type = Types::Array.policy(size: 1..)
52
- # type = Types::Any[Set].policy(size: 1..)
53
- policy :size, for_type: :size do |type, size|
54
- type.check("must be of size #{size}") { |v| size === v.size }
55
- end
56
-
57
47
  # Validate that an object is not #empty? nor #nil?
58
48
  # Usage:
59
49
  # Types::String.present
data/lib/plumb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plumb
4
- VERSION = '0.0.10'
4
+ VERSION = '0.0.11'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plumb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-16 00:00:00.000000000 Z
11
+ date: 2025-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -65,6 +65,7 @@ files:
65
65
  - lib/plumb/and.rb
66
66
  - lib/plumb/any_class.rb
67
67
  - lib/plumb/array_class.rb
68
+ - lib/plumb/attribute_value_match.rb
68
69
  - lib/plumb/attributes.rb
69
70
  - lib/plumb/build.rb
70
71
  - lib/plumb/composable.rb