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.
@@ -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