plumb 0.0.1 → 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/.rubocop.yml +2 -0
- data/README.md +558 -118
- data/examples/command_objects.rb +207 -0
- data/examples/concurrent_downloads.rb +107 -0
- data/examples/csv_stream.rb +97 -0
- data/examples/env_config.rb +122 -0
- data/examples/programmers.csv +201 -0
- data/examples/weekdays.rb +66 -0
- data/lib/plumb/array_class.rb +25 -19
- data/lib/plumb/build.rb +3 -0
- data/lib/plumb/hash_class.rb +42 -13
- data/lib/plumb/hash_map.rb +34 -0
- data/lib/plumb/interface_class.rb +6 -4
- data/lib/plumb/json_schema_visitor.rb +157 -71
- data/lib/plumb/match_class.rb +8 -6
- data/lib/plumb/metadata.rb +3 -0
- data/lib/plumb/metadata_visitor.rb +54 -40
- data/lib/plumb/policies.rb +81 -0
- data/lib/plumb/policy.rb +31 -0
- data/lib/plumb/schema.rb +39 -43
- data/lib/plumb/static_class.rb +4 -4
- data/lib/plumb/step.rb +6 -1
- data/lib/plumb/steppable.rb +47 -60
- data/lib/plumb/stream_class.rb +61 -0
- data/lib/plumb/tagged_hash.rb +12 -3
- data/lib/plumb/transform.rb +6 -1
- data/lib/plumb/tuple_class.rb +8 -5
- data/lib/plumb/types.rb +119 -69
- data/lib/plumb/value_class.rb +5 -2
- data/lib/plumb/version.rb +1 -1
- data/lib/plumb/visitor_handlers.rb +19 -10
- data/lib/plumb.rb +53 -1
- metadata +14 -6
- data/lib/plumb/rules.rb +0 -103
@@ -0,0 +1,201 @@
|
|
1
|
+
name,age,language
|
2
|
+
Krysten Tromp,57,C++
|
3
|
+
Irena Mayert III,21,ruby
|
4
|
+
Lucile Jacobi I,28,lisp
|
5
|
+
Warner Ullrich DC,64,rust
|
6
|
+
Refugio Cronin,13,F#
|
7
|
+
Emery Kris,84,ocaml
|
8
|
+
Sherise Wisoky,73,elm
|
9
|
+
Bridget DuBuque,75,elixir
|
10
|
+
Maynard Lubowitz V,58,C++
|
11
|
+
Rosamaria Heidenreich,61,fortran
|
12
|
+
Marla Fritsch,70,golang
|
13
|
+
Williams Little MD,48,golang
|
14
|
+
Bernard Wilderman,25,ocaml
|
15
|
+
Sen. Hiram Bayer,37,gleam
|
16
|
+
Ginger Mueller,60,elixir
|
17
|
+
Karyl Hegmann,63,ruby
|
18
|
+
Terrilyn Fadel,40,ocaml
|
19
|
+
Edward Abshire,14,gleam
|
20
|
+
Clyde Lesch,67,elixir
|
21
|
+
Ms. Loretta Botsford,40,ruby
|
22
|
+
Sen. Susann Gleichner,19,zig
|
23
|
+
Von Thompson,80,javascript
|
24
|
+
Carrol Kirlin II,43,ocaml
|
25
|
+
Samual Welch,17,elm
|
26
|
+
Samara Hegmann II,50,fortran
|
27
|
+
Tyron Bogan,65,rust
|
28
|
+
Juan Quitzon,57,zig
|
29
|
+
Bev Feest CPA,15,javascript
|
30
|
+
Walter Parisian,17,golang
|
31
|
+
The Hon. Zachary Funk,55,ruby
|
32
|
+
Jaimie Stoltenberg,77,rust
|
33
|
+
Jarrod Turcotte DC,68,elixir
|
34
|
+
Dr. Jason Jacobs,40,zig
|
35
|
+
Miss Neal Leffler,17,rust
|
36
|
+
Wilfredo Kemmer,30,C++
|
37
|
+
Fr. Leon Jerde,35,ruby
|
38
|
+
Mr. Ray Anderson,31,C
|
39
|
+
Jed Skiles,63,lua
|
40
|
+
Delena Raynor LLD,40,ruby
|
41
|
+
Pres. Dirk Kunde,74,rust
|
42
|
+
Franklyn Kuhic,55,ruby
|
43
|
+
Jon Deckow,68,golang
|
44
|
+
Anglea Beier LLD,17,C++
|
45
|
+
Jene Labadie,60,lisp
|
46
|
+
Ezequiel Wyman,50,F#
|
47
|
+
Jessie Medhurst,33,F#
|
48
|
+
Luke Bergnaum,57,ruby
|
49
|
+
Douglass Goodwin,83,zig
|
50
|
+
Garry Farrell,67,ocaml
|
51
|
+
Warren Harber III,23,rust
|
52
|
+
Britni Mohr,60,elixir
|
53
|
+
Terra Pouros DDS,60,F#
|
54
|
+
Pia Bernhard,37,fortran
|
55
|
+
Akiko Hoppe,14,ocaml
|
56
|
+
Samuel Muller,56,rust
|
57
|
+
Esperanza Kutch,67,C++
|
58
|
+
Hal Gislason,75,ruby
|
59
|
+
Prof. Jonell Turcotte,61,ruby
|
60
|
+
Lindsey Hermiston,29,ruby
|
61
|
+
Deadra Muller,51,fortran
|
62
|
+
Amb. Fern Crist,58,elm
|
63
|
+
Enola Grant,39,zig
|
64
|
+
Mickey Lueilwitz DVM,80,lua
|
65
|
+
Raymon Jacobi,34,golang
|
66
|
+
Jody Rowe V,27,golang
|
67
|
+
Lilliam Hyatt,36,zig
|
68
|
+
Fr. Larraine Klein,22,javascript
|
69
|
+
Derick Rutherford DC,41,C
|
70
|
+
Eugene Kiehn DVM,20,C
|
71
|
+
Dianna Kunze DDS,22,fortran
|
72
|
+
Denis Reilly,67,zig
|
73
|
+
Alejandrina Waelchi,70,rust
|
74
|
+
Odell Cassin,20,ocaml
|
75
|
+
Travis Daugherty,77,F#
|
76
|
+
Guillermo McCullough,15,ruby
|
77
|
+
Malena Nitzsche,12,lisp
|
78
|
+
Jess Gleason,61,ruby
|
79
|
+
Mr. Kenton Quitzon,77,C++
|
80
|
+
Sixta Torp,36,ruby
|
81
|
+
Teddy McClure,15,ruby
|
82
|
+
Alexandra Towne CPA,73,elixir
|
83
|
+
Harrison Hartmann CPA,66,lua
|
84
|
+
Gretta Kirlin Sr.,17,rust
|
85
|
+
Rev. Jordan Yost,36,ruby
|
86
|
+
Geraldo Yundt LLD,17,elixir
|
87
|
+
Emelia Pouros,31,fortran
|
88
|
+
Msgr. Coletta Crist,43,zig
|
89
|
+
Vernice Pfannerstill,31,zig
|
90
|
+
Rev. Marvel Mayer,55,ruby
|
91
|
+
Amb. Chanelle Ruecker,14,elixir
|
92
|
+
Sallie Roob PhD,75,gleam
|
93
|
+
Enoch Funk,33,golang
|
94
|
+
Alex Reichel,85,F#
|
95
|
+
Laure Boehm,24,julia
|
96
|
+
Valeri Dietrich,72,ruby
|
97
|
+
Tabatha Kerluke,58,julia
|
98
|
+
Bert Sauer,73,gleam
|
99
|
+
Morgan Heaney,39,julia
|
100
|
+
Lora Hackett,74,javascript
|
101
|
+
Rona Leuschke,72,rust
|
102
|
+
Mora Bode,19,elm
|
103
|
+
Daniell Hegmann,37,golang
|
104
|
+
Richie Waters CPA,23,javascript
|
105
|
+
Katharine Huel,36,C
|
106
|
+
Lauren Abbott,37,C
|
107
|
+
Daisey Cremin,62,ruby
|
108
|
+
Evelyn Koch,49,F#
|
109
|
+
Pres. Cassey Hammes,82,gleam
|
110
|
+
Msgr. Wilhelmina Bauch,18,C
|
111
|
+
Hilaria Emmerich,35,rust
|
112
|
+
Rev. Julieann Hickle,18,C
|
113
|
+
Pres. Corey Shields,14,ruby
|
114
|
+
Donetta Lehner PhD,34,ruby
|
115
|
+
Cyndi Schumm,83,ruby
|
116
|
+
Newton West,52,elm
|
117
|
+
Carleen Wisozk,42,F#
|
118
|
+
Alysha Marvin Ret.,35,lisp
|
119
|
+
Alex Balistreri,83,javascript
|
120
|
+
Zulma Parker PhD,45,ocaml
|
121
|
+
Reyes Bogisich Sr.,70,ocaml
|
122
|
+
Dario Batz,38,elm
|
123
|
+
Jacquelyn MacGyver,59,gleam
|
124
|
+
Gov. Zachary Schuster,64,ruby
|
125
|
+
Leonard Braun,65,ruby
|
126
|
+
Ivette Schuppe,30,ruby
|
127
|
+
Adrian Parisian,45,elm
|
128
|
+
Dr. Young Rohan,20,fortran
|
129
|
+
Dong Hansen,77,F#
|
130
|
+
Johnny Hammes,44,F#
|
131
|
+
Amb. Terence Cummerata,49,ruby
|
132
|
+
Taylor Nitzsche,40,F#
|
133
|
+
Mr. Bennett Price,46,C
|
134
|
+
Genaro Blick VM,23,fortran
|
135
|
+
Ashley Osinski,71,ruby
|
136
|
+
Jae D'Amore,50,elm
|
137
|
+
Katherine Ward,63,lisp
|
138
|
+
Anderson O'Conner,70,ocaml
|
139
|
+
Amb. Chrystal Schmitt,70,rust
|
140
|
+
Vaughn Larkin,52,F#
|
141
|
+
Jada Brown Jr.,53,golang
|
142
|
+
Pauletta Krajcik,72,gleam
|
143
|
+
Cinthia Morar,26,ruby
|
144
|
+
Loyce Windler,51,julia
|
145
|
+
Ms. Sharee Walker,84,ruby
|
146
|
+
Raymundo Kohler,14,fortran
|
147
|
+
Natalya Towne Sr.,72,C++
|
148
|
+
Rayford Kemmer,12,golang
|
149
|
+
Rev. Tisa Harber,44,elm
|
150
|
+
Jorge Schaden,72,lua
|
151
|
+
Pam Marquardt,12,lua
|
152
|
+
Xenia Wisoky,13,javascript
|
153
|
+
Audrea Purdy,75,lua
|
154
|
+
Joesph Russel DC,18,javascript
|
155
|
+
Marty Ratke III,38,rust
|
156
|
+
Jordon Bradtke,25,C
|
157
|
+
Titus Cronin Esq.,36,ruby
|
158
|
+
Ms. Lucio Vandervort,64,golang
|
159
|
+
Theo Nicolas,50,julia
|
160
|
+
Hazel Pouros,29,ruby
|
161
|
+
Rey Lubowitz,59,elixir
|
162
|
+
Ike Hessel,66,C
|
163
|
+
Alec Wilderman,42,ruby
|
164
|
+
Leesa Ledner,71,lisp
|
165
|
+
Jeneva Gutkowski,33,lua
|
166
|
+
Isaias MacGyver,32,F#
|
167
|
+
Daryl Senger I,36,ruby
|
168
|
+
Mr. Gonzalo Breitenberg,50,julia
|
169
|
+
Aretha Halvorson,17,ocaml
|
170
|
+
Ralph Wisoky,15,F#
|
171
|
+
Demarcus Stehr,80,javascript
|
172
|
+
Sheldon Lemke,47,golang
|
173
|
+
Kimiko Hermiston,20,golang
|
174
|
+
Manda Pfannerstill,20,F#
|
175
|
+
Haywood Rowe,85,F#
|
176
|
+
Roxanne Gerhold,29,ruby
|
177
|
+
Chanelle Beatty,70,ruby
|
178
|
+
Shirlee Hoeger,18,ruby
|
179
|
+
Beula Denesik,68,C++
|
180
|
+
Roberto Lang,59,gleam
|
181
|
+
Marcy Rau,77,javascript
|
182
|
+
Samira Hilll,51,F#
|
183
|
+
Clarissa Simonis,82,javascript
|
184
|
+
Elijah Cole,57,ocaml
|
185
|
+
Freida Wilkinson,34,lua
|
186
|
+
Polly Wehner,82,C++
|
187
|
+
Lindsey Ullrich,80,elixir
|
188
|
+
Freddie Auer,42,ruby
|
189
|
+
Luz Zemlak,37,lisp
|
190
|
+
Colin Emard,74,ruby
|
191
|
+
Dion Hudson,56,gleam
|
192
|
+
Daisy Crist,12,lua
|
193
|
+
Keturah Ortiz,64,ruby
|
194
|
+
Everette Shanahan Sr.,28,zig
|
195
|
+
Prof. Beckie Wiza,80,ruby
|
196
|
+
Msgr. Annabelle Howe,35,ocaml
|
197
|
+
Jeremy Effertz,22,julia
|
198
|
+
Dinorah Graham,38,elixir
|
199
|
+
Kate Bernier,35,ruby
|
200
|
+
Jerrold Ortiz,22,ruby
|
201
|
+
Bart Moen,74,ruby
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup(:examples)
|
5
|
+
require 'plumb'
|
6
|
+
|
7
|
+
# bundle exec examples/weekdays.rb
|
8
|
+
#
|
9
|
+
# Data types to represent and parse an array of days of the week.
|
10
|
+
# Input data can be an array of day names or numbers, ex.
|
11
|
+
# ['monday', 'tuesday', 'wednesday']
|
12
|
+
# [1, 2, 3]
|
13
|
+
#
|
14
|
+
# Or mixed:
|
15
|
+
# [1, 'Tuesday', 3]
|
16
|
+
#
|
17
|
+
# Validate that there aren't repeated days, ex. [1, 2, 4, 2]
|
18
|
+
# The output is an array of day numbers, ex. [1, 2, 3]
|
19
|
+
module Types
|
20
|
+
include Plumb::Types
|
21
|
+
|
22
|
+
DAYS = {
|
23
|
+
'monday' => 1,
|
24
|
+
'tuesday' => 2,
|
25
|
+
'wednesday' => 3,
|
26
|
+
'thursday' => 4,
|
27
|
+
'friday' => 5,
|
28
|
+
'saturday' => 6,
|
29
|
+
'sunday' => 7
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# Validate that a string is a valid day name, down-case it.
|
33
|
+
DayName = String
|
34
|
+
.transform(::String, &:downcase)
|
35
|
+
.options(DAYS.keys)
|
36
|
+
|
37
|
+
# Turn a day name into its number, or validate that a number is a valid day number.
|
38
|
+
DayNameOrNumber = DayName.transform(::Integer) { |v| DAYS[v] } | Integer.options(DAYS.values)
|
39
|
+
|
40
|
+
# An Array for days of the week, with no repeated days.
|
41
|
+
# Ex. [1, 2, 3, 4, 5, 6, 7], [1, 2, 4], ['monday', 'tuesday', 'wednesday', 7]
|
42
|
+
# Turn day names into numbers, and sort the array.
|
43
|
+
Week = Array[DayNameOrNumber]
|
44
|
+
.policy(size: 1..7)
|
45
|
+
.check('repeated days') { |days| days.uniq.size == days.size }
|
46
|
+
.transform(::Array, &:sort)
|
47
|
+
end
|
48
|
+
|
49
|
+
p Types::DayNameOrNumber.parse('monday') # => 1
|
50
|
+
p Types::DayNameOrNumber.parse(3) # => 3
|
51
|
+
p Types::DayName.parse('TueSday') # => "tuesday
|
52
|
+
p Types::Week.parse([3, 2, 1, 4, 5, 6, 7]) # => [1, 2, 3, 4, 5, 6, 7]
|
53
|
+
p Types::Week.parse([1, 'Tuesday', 3, 4, 5, 'saturday', 7]) # => [1, 2, 3, 4, 5, 6, 7]
|
54
|
+
|
55
|
+
# p Types::Week[[1, 1, 3, 4, 5, 6, 7]] # raises Plumb::TypeError: repeated days
|
56
|
+
#
|
57
|
+
# Or use these types as part of other composite types, ex.
|
58
|
+
#
|
59
|
+
# PartTimeJob = Types::Hash[
|
60
|
+
# role: Types::String.present,
|
61
|
+
# days_of_the_week: Types::Week
|
62
|
+
# ]
|
63
|
+
#
|
64
|
+
# result = PartTimeJob.resolve(role: 'Ruby dev', days_of_the_week: %w[Tuesday Wednesday])
|
65
|
+
# result.valid? # true
|
66
|
+
# result.value # { role: 'Ruby dev', days_of_the_week: [2, 3] }
|
data/lib/plumb/array_class.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'concurrent'
|
4
4
|
require 'plumb/steppable'
|
5
5
|
require 'plumb/result'
|
6
|
-
require 'plumb/
|
6
|
+
require 'plumb/stream_class'
|
7
7
|
|
8
8
|
module Plumb
|
9
9
|
class ArrayClass
|
@@ -12,15 +12,7 @@ module Plumb
|
|
12
12
|
attr_reader :element_type
|
13
13
|
|
14
14
|
def initialize(element_type: Types::Any)
|
15
|
-
@element_type =
|
16
|
-
when Steppable
|
17
|
-
element_type
|
18
|
-
when ::Hash
|
19
|
-
HashClass.new(element_type)
|
20
|
-
else
|
21
|
-
raise ArgumentError,
|
22
|
-
"element_type #{element_type.inspect} must be a Steppable"
|
23
|
-
end
|
15
|
+
@element_type = Steppable.wrap(element_type)
|
24
16
|
|
25
17
|
freeze
|
26
18
|
end
|
@@ -35,12 +27,22 @@ module Plumb
|
|
35
27
|
ConcurrentArrayClass.new(element_type:)
|
36
28
|
end
|
37
29
|
|
38
|
-
|
39
|
-
|
30
|
+
def stream
|
31
|
+
StreamClass.new(element_type:)
|
32
|
+
end
|
33
|
+
|
34
|
+
def filtered
|
35
|
+
MatchClass.new(::Array) >> Step.new(nil, "Array[#{element_type}].filtered") do |result|
|
36
|
+
arr = result.value.each.with_object([]) do |e, memo|
|
37
|
+
r = element_type.resolve(e)
|
38
|
+
memo << r.value if r.valid?
|
39
|
+
end
|
40
|
+
result.valid(arr)
|
41
|
+
end
|
40
42
|
end
|
41
43
|
|
42
44
|
def call(result)
|
43
|
-
return result.invalid(errors: 'is not an Array') unless result.value
|
45
|
+
return result.invalid(errors: 'is not an Array') unless ::Array === result.value
|
44
46
|
|
45
47
|
values, errors = map_array_elements(result.value)
|
46
48
|
return result.valid(values) unless errors.any?
|
@@ -50,6 +52,10 @@ module Plumb
|
|
50
52
|
|
51
53
|
private
|
52
54
|
|
55
|
+
def _inspect
|
56
|
+
%(Array[#{element_type}])
|
57
|
+
end
|
58
|
+
|
53
59
|
def map_array_elements(list)
|
54
60
|
# Reuse the same result object for each element
|
55
61
|
# to decrease object allocation.
|
@@ -73,12 +79,12 @@ module Plumb
|
|
73
79
|
errors = {}
|
74
80
|
|
75
81
|
values = list
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
.map { |e| Concurrent::Future.execute { element_type.resolve(e) } }
|
83
|
+
.map.with_index do |f, idx|
|
84
|
+
re = f.value
|
85
|
+
errors[idx] = f.reason if f.rejected?
|
86
|
+
re.value
|
87
|
+
end
|
82
88
|
|
83
89
|
[values, errors]
|
84
90
|
end
|
data/lib/plumb/build.rb
CHANGED
@@ -11,8 +11,11 @@ module Plumb
|
|
11
11
|
def initialize(type, factory_method: :new, &block)
|
12
12
|
@type = type
|
13
13
|
@block = block || ->(value) { type.send(factory_method, value) }
|
14
|
+
freeze
|
14
15
|
end
|
15
16
|
|
16
17
|
def call(result) = result.valid(@block.call(result.value))
|
18
|
+
|
19
|
+
private def _inspect = "Build[#{@type.inspect}]"
|
17
20
|
end
|
18
21
|
end
|
data/lib/plumb/hash_class.rb
CHANGED
@@ -12,8 +12,9 @@ module Plumb
|
|
12
12
|
|
13
13
|
attr_reader :_schema
|
14
14
|
|
15
|
-
def initialize(schema
|
15
|
+
def initialize(schema: BLANK_HASH, inclusive: false)
|
16
16
|
@_schema = wrap_keys_and_values(schema)
|
17
|
+
@inclusive = inclusive
|
17
18
|
freeze
|
18
19
|
end
|
19
20
|
|
@@ -28,12 +29,12 @@ module Plumb
|
|
28
29
|
def schema(*args)
|
29
30
|
case args
|
30
31
|
in [::Hash => hash]
|
31
|
-
|
32
|
-
in [
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
self.class.new(schema: _schema.merge(wrap_keys_and_values(hash)), inclusive: @inclusive)
|
33
|
+
in [key_type, value_type]
|
34
|
+
HashMap.new(Steppable.wrap(key_type), Steppable.wrap(value_type))
|
35
|
+
else
|
36
|
+
raise ::ArgumentError, "unexpected value to Types::Hash#schema #{args.inspect}"
|
37
|
+
end
|
37
38
|
end
|
38
39
|
|
39
40
|
alias [] schema
|
@@ -45,7 +46,7 @@ module Plumb
|
|
45
46
|
def +(other)
|
46
47
|
raise ArgumentError, "expected a HashClass, got #{other.class}" unless other.is_a?(HashClass)
|
47
48
|
|
48
|
-
self.class.new(merge_rightmost_keys(_schema, other._schema))
|
49
|
+
self.class.new(schema: merge_rightmost_keys(_schema, other._schema), inclusive: @inclusive)
|
49
50
|
end
|
50
51
|
|
51
52
|
def &(other)
|
@@ -56,21 +57,43 @@ module Plumb
|
|
56
57
|
memo[k] = other.at_key(k)
|
57
58
|
end
|
58
59
|
|
59
|
-
self.class.new(intersected)
|
60
|
+
self.class.new(schema: intersected, inclusive: @inclusive)
|
60
61
|
end
|
61
62
|
|
62
63
|
def tagged_by(key, *types)
|
63
64
|
TaggedHash.new(self, key, types)
|
64
65
|
end
|
65
66
|
|
67
|
+
def inclusive
|
68
|
+
self.class.new(schema: _schema, inclusive: true)
|
69
|
+
end
|
70
|
+
|
66
71
|
def at_key(a_key)
|
67
72
|
_schema[Key.wrap(a_key)]
|
68
73
|
end
|
69
74
|
|
70
75
|
def to_h = _schema
|
71
76
|
|
72
|
-
|
73
|
-
|
77
|
+
def filtered
|
78
|
+
op = lambda do |result|
|
79
|
+
return result.invalid(errors: 'must be a Hash') unless result.value.is_a?(::Hash)
|
80
|
+
return result unless _schema.any?
|
81
|
+
|
82
|
+
input = result.value
|
83
|
+
field_result = BLANK_RESULT.dup
|
84
|
+
output = _schema.each.with_object({}) do |(key, field), ret|
|
85
|
+
key_s = key.to_sym
|
86
|
+
if input.key?(key_s)
|
87
|
+
r = field.call(field_result.reset(input[key_s]))
|
88
|
+
ret[key_s] = r.value if r.valid?
|
89
|
+
elsif !key.optional?
|
90
|
+
r = field.call(BLANK_RESULT)
|
91
|
+
ret[key_s] = r.value if r.valid?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
result.valid(output)
|
95
|
+
end
|
96
|
+
Step.new(op, [_inspect, 'filtered'].join('.'))
|
74
97
|
end
|
75
98
|
|
76
99
|
def call(result)
|
@@ -80,7 +103,9 @@ module Plumb
|
|
80
103
|
input = result.value
|
81
104
|
errors = {}
|
82
105
|
field_result = BLANK_RESULT.dup
|
83
|
-
|
106
|
+
initial = {}
|
107
|
+
initial = initial.merge(input) if @inclusive
|
108
|
+
output = _schema.each.with_object(initial) do |(key, field), ret|
|
84
109
|
key_s = key.to_sym
|
85
110
|
if input.key?(key_s)
|
86
111
|
r = field.call(field_result.reset(input[key_s]))
|
@@ -98,6 +123,10 @@ module Plumb
|
|
98
123
|
|
99
124
|
private
|
100
125
|
|
126
|
+
def _inspect
|
127
|
+
%(Hash[#{_schema.map { |(k, v)| [k.inspect, v.inspect].join(': ') }.join(', ')}])
|
128
|
+
end
|
129
|
+
|
101
130
|
def wrap_keys_and_values(hash)
|
102
131
|
case hash
|
103
132
|
when ::Array
|
@@ -109,7 +138,7 @@ module Plumb
|
|
109
138
|
when Callable
|
110
139
|
hash
|
111
140
|
else # leaf values
|
112
|
-
|
141
|
+
Steppable.wrap(hash)
|
113
142
|
end
|
114
143
|
end
|
115
144
|
|
data/lib/plumb/hash_map.rb
CHANGED
@@ -15,6 +15,8 @@ module Plumb
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def call(result)
|
18
|
+
return result.invalid(errors: 'must be a Hash') unless result.value.is_a?(::Hash)
|
19
|
+
|
18
20
|
failed = result.value.lazy.filter_map do |key, value|
|
19
21
|
key_r = @key_type.resolve(key)
|
20
22
|
value_r = @value_type.resolve(value)
|
@@ -31,5 +33,37 @@ module Plumb
|
|
31
33
|
rescue StopIteration
|
32
34
|
result
|
33
35
|
end
|
36
|
+
|
37
|
+
def filtered
|
38
|
+
FilteredHashMap.new(key_type, value_type)
|
39
|
+
end
|
40
|
+
|
41
|
+
private def _inspect = "HashMap[#{@key_type.inspect}, #{@value_type.inspect}]"
|
42
|
+
|
43
|
+
class FilteredHashMap
|
44
|
+
include Steppable
|
45
|
+
|
46
|
+
attr_reader :key_type, :value_type
|
47
|
+
|
48
|
+
def initialize(key_type, value_type)
|
49
|
+
@key_type = key_type
|
50
|
+
@value_type = value_type
|
51
|
+
freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
def call(result)
|
55
|
+
result.invalid(errors: 'must be a Hash') unless result.value.is_a?(::Hash)
|
56
|
+
|
57
|
+
hash = result.value.each.with_object({}) do |(key, value), memo|
|
58
|
+
key_r = @key_type.resolve(key)
|
59
|
+
value_r = @value_type.resolve(value)
|
60
|
+
memo[key_r.value] = value_r.value if key_r.valid? && value_r.valid?
|
61
|
+
end
|
62
|
+
|
63
|
+
result.valid(hash)
|
64
|
+
end
|
65
|
+
|
66
|
+
private def _inspect = "HashMap[#{@key_type.inspect}, #{@value_type.inspect}].filtered"
|
67
|
+
end
|
34
68
|
end
|
35
69
|
end
|
@@ -16,10 +16,10 @@ module Plumb
|
|
16
16
|
def of(*args)
|
17
17
|
case args
|
18
18
|
in Array => symbols if symbols.all? { |s| s.is_a?(::Symbol) }
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
self.class.new(symbols)
|
20
|
+
else
|
21
|
+
raise ::ArgumentError, "unexpected value to Types::Interface#of #{args.inspect}"
|
22
|
+
end
|
23
23
|
end
|
24
24
|
|
25
25
|
alias [] of
|
@@ -31,5 +31,7 @@ module Plumb
|
|
31
31
|
|
32
32
|
result
|
33
33
|
end
|
34
|
+
|
35
|
+
private def _inspect = "Interface[#{method_names.join(', ')}]"
|
34
36
|
end
|
35
37
|
end
|