plumb 0.0.1 → 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/.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
|