plumb 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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] }
@@ -3,7 +3,7 @@
3
3
  require 'concurrent'
4
4
  require 'plumb/steppable'
5
5
  require 'plumb/result'
6
- require 'plumb/hash_class'
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 = case 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
- private def _inspect
39
- %(#{name}[#{element_type}])
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.is_a?(::Enumerable)
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
- .map { |e| Concurrent::Future.execute { element_type.resolve(e) } }
77
- .map.with_index do |f, idx|
78
- re = f.value
79
- errors[idx] = f.reason if f.rejected?
80
- re.value
81
- end
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
@@ -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
- self.class.new(_schema.merge(wrap_keys_and_values(hash)))
32
- in [Steppable => key_type, Steppable => value_type]
33
- HashMap.new(key_type, value_type)
34
- else
35
- raise ::ArgumentError, "unexpected value to Types::Hash#schema #{args.inspect}"
36
- end
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
- private def _inspect
73
- %(#{name}[#{_schema.map { |(k, v)| [k.inspect, v.inspect].join(':') }.join(' ')}])
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
- output = _schema.each.with_object({}) do |(key, field), ret|
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
- StaticClass.new(hash)
141
+ Steppable.wrap(hash)
113
142
  end
114
143
  end
115
144
 
@@ -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
- self.class.new(symbols)
20
- else
21
- raise ::ArgumentError, "unexpected value to Types::Interface#of #{args.inspect}"
22
- end
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