plumb 0.0.3 → 0.0.5
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/README.md +609 -57
- data/bench/compare_parametric_schema.rb +102 -0
- data/bench/compare_parametric_struct.rb +68 -0
- data/bench/parametric_schema.rb +229 -0
- data/bench/plumb_hash.rb +109 -0
- data/examples/concurrent_downloads.rb +3 -3
- data/examples/env_config.rb +2 -2
- data/examples/event_registry.rb +127 -0
- data/examples/weekdays.rb +1 -1
- data/lib/plumb/and.rb +4 -3
- data/lib/plumb/any_class.rb +4 -4
- data/lib/plumb/array_class.rb +8 -5
- data/lib/plumb/attributes.rb +268 -0
- data/lib/plumb/build.rb +4 -3
- data/lib/plumb/composable.rb +381 -0
- data/lib/plumb/decorator.rb +57 -0
- data/lib/plumb/deferred.rb +1 -1
- data/lib/plumb/hash_class.rb +19 -8
- data/lib/plumb/hash_map.rb +8 -6
- data/lib/plumb/interface_class.rb +6 -2
- data/lib/plumb/json_schema_visitor.rb +59 -32
- data/lib/plumb/match_class.rb +5 -4
- data/lib/plumb/metadata.rb +5 -1
- data/lib/plumb/metadata_visitor.rb +13 -42
- data/lib/plumb/not.rb +4 -3
- data/lib/plumb/or.rb +10 -4
- data/lib/plumb/pipeline.rb +27 -7
- data/lib/plumb/policy.rb +10 -3
- data/lib/plumb/schema.rb +11 -10
- data/lib/plumb/static_class.rb +4 -3
- data/lib/plumb/step.rb +4 -3
- data/lib/plumb/stream_class.rb +8 -7
- data/lib/plumb/tagged_hash.rb +11 -11
- data/lib/plumb/transform.rb +4 -3
- data/lib/plumb/tuple_class.rb +8 -8
- data/lib/plumb/type_registry.rb +5 -2
- data/lib/plumb/types.rb +30 -1
- data/lib/plumb/value_class.rb +4 -3
- data/lib/plumb/version.rb +1 -1
- data/lib/plumb/visitor_handlers.rb +6 -0
- data/lib/plumb.rb +11 -5
- metadata +10 -3
- data/lib/plumb/steppable.rb +0 -229
    
        data/lib/plumb/tagged_hash.rb
    CHANGED
    
    | @@ -1,29 +1,29 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'plumb/ | 
| 3 | 
            +
            require 'plumb/composable'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Plumb
         | 
| 6 6 | 
             
              class TaggedHash
         | 
| 7 | 
            -
                include  | 
| 7 | 
            +
                include Composable
         | 
| 8 8 |  | 
| 9 | 
            -
                attr_reader :key, : | 
| 9 | 
            +
                attr_reader :key, :children
         | 
| 10 10 |  | 
| 11 | 
            -
                def initialize(hash_type, key,  | 
| 11 | 
            +
                def initialize(hash_type, key, children)
         | 
| 12 12 | 
             
                  @hash_type = hash_type
         | 
| 13 13 | 
             
                  @key = Key.wrap(key)
         | 
| 14 | 
            -
                  @ | 
| 14 | 
            +
                  @children = children
         | 
| 15 15 |  | 
| 16 | 
            -
                  raise ArgumentError, 'all types must be HashClass' if @ | 
| 16 | 
            +
                  raise ArgumentError, 'all types must be HashClass' if @children.size.zero? || @children.any? do |t|
         | 
| 17 17 | 
             
                    !t.is_a?(HashClass)
         | 
| 18 18 | 
             
                  end
         | 
| 19 | 
            -
                  raise ArgumentError, "all types must define key #{@key}" unless @ | 
| 19 | 
            +
                  raise ArgumentError, "all types must define key #{@key}" unless @children.all? { |t| !!t.at_key(@key) }
         | 
| 20 20 |  | 
| 21 21 | 
             
                  # types are assumed to have literal values for the index field :key
         | 
| 22 | 
            -
                  @index = @ | 
| 22 | 
            +
                  @index = @children.each.with_object({}) do |t, memo|
         | 
| 23 23 | 
             
                    key_type = t.at_key(@key)
         | 
| 24 | 
            -
                    raise  | 
| 24 | 
            +
                    raise ParseError, "key type at :#{@key} #{key_type} must be a Match type" unless key_type.is_a?(MatchClass)
         | 
| 25 25 |  | 
| 26 | 
            -
                    memo[key_type. | 
| 26 | 
            +
                    memo[key_type.children[0]] = t
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| 29 29 | 
             
                  freeze
         | 
| @@ -41,6 +41,6 @@ module Plumb | |
| 41 41 |  | 
| 42 42 | 
             
                private
         | 
| 43 43 |  | 
| 44 | 
            -
                def _inspect = "TaggedHash[#{@key.inspect}, #{@ | 
| 44 | 
            +
                def _inspect = "TaggedHash[#{@key.inspect}, #{@children.map(&:inspect).join(', ')}]"
         | 
| 45 45 | 
             
              end
         | 
| 46 46 | 
             
            end
         | 
    
        data/lib/plumb/transform.rb
    CHANGED
    
    | @@ -1,16 +1,17 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'plumb/ | 
| 3 | 
            +
            require 'plumb/composable'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Plumb
         | 
| 6 6 | 
             
              class Transform
         | 
| 7 | 
            -
                include  | 
| 7 | 
            +
                include Composable
         | 
| 8 8 |  | 
| 9 | 
            -
                attr_reader : | 
| 9 | 
            +
                attr_reader :children
         | 
| 10 10 |  | 
| 11 11 | 
             
                def initialize(target_type, callable)
         | 
| 12 12 | 
             
                  @target_type = target_type
         | 
| 13 13 | 
             
                  @callable = callable || Plumb::NOOP
         | 
| 14 | 
            +
                  @children = [target_type].freeze
         | 
| 14 15 | 
             
                  freeze
         | 
| 15 16 | 
             
                end
         | 
| 16 17 |  | 
    
        data/lib/plumb/tuple_class.rb
    CHANGED
    
    | @@ -1,15 +1,15 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'plumb/ | 
| 3 | 
            +
            require 'plumb/composable'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Plumb
         | 
| 6 6 | 
             
              class TupleClass
         | 
| 7 | 
            -
                include  | 
| 7 | 
            +
                include Composable
         | 
| 8 8 |  | 
| 9 | 
            -
                attr_reader : | 
| 9 | 
            +
                attr_reader :children
         | 
| 10 10 |  | 
| 11 | 
            -
                def initialize(* | 
| 12 | 
            -
                  @ | 
| 11 | 
            +
                def initialize(*children)
         | 
| 12 | 
            +
                  @children = children.map { |t| Composable.wrap(t) }.freeze
         | 
| 13 13 | 
             
                  freeze
         | 
| 14 14 | 
             
                end
         | 
| 15 15 |  | 
| @@ -21,10 +21,10 @@ module Plumb | |
| 21 21 |  | 
| 22 22 | 
             
                def call(result)
         | 
| 23 23 | 
             
                  return result.invalid(errors: 'must be an Array') unless result.value.is_a?(::Array)
         | 
| 24 | 
            -
                  return result.invalid(errors: 'must have the same size') unless result.value.size == @ | 
| 24 | 
            +
                  return result.invalid(errors: 'must have the same size') unless result.value.size == @children.size
         | 
| 25 25 |  | 
| 26 26 | 
             
                  errors = {}
         | 
| 27 | 
            -
                  values = @ | 
| 27 | 
            +
                  values = @children.map.with_index do |type, idx|
         | 
| 28 28 | 
             
                    val = result.value[idx]
         | 
| 29 29 | 
             
                    r = type.resolve(val)
         | 
| 30 30 | 
             
                    errors[idx] = ["expected #{type.inspect}, got #{val.inspect}", r.errors].flatten unless r.valid?
         | 
| @@ -39,7 +39,7 @@ module Plumb | |
| 39 39 | 
             
                private
         | 
| 40 40 |  | 
| 41 41 | 
             
                def _inspect
         | 
| 42 | 
            -
                  "Tuple[#{@ | 
| 42 | 
            +
                  "Tuple[#{@children.map(&:inspect).join(', ')}]"
         | 
| 43 43 | 
             
                end
         | 
| 44 44 | 
             
              end
         | 
| 45 45 | 
             
            end
         | 
    
        data/lib/plumb/type_registry.rb
    CHANGED
    
    | @@ -7,7 +7,7 @@ module Plumb | |
| 7 7 | 
             
                  case obj
         | 
| 8 8 | 
             
                  when Module
         | 
| 9 9 | 
             
                    obj.extend TypeRegistry
         | 
| 10 | 
            -
                  when  | 
| 10 | 
            +
                  when Composable
         | 
| 11 11 | 
             
                    anc = [name, const_name].join('::')
         | 
| 12 12 | 
             
                    obj.freeze.name.set(anc)
         | 
| 13 13 | 
             
                  end
         | 
| @@ -17,16 +17,19 @@ module Plumb | |
| 17 17 | 
             
                  host.extend TypeRegistry
         | 
| 18 18 | 
             
                  constants(false).each do |const_name|
         | 
| 19 19 | 
             
                    const = const_get(const_name)
         | 
| 20 | 
            +
             | 
| 20 21 | 
             
                    anc = [host.name, const_name].join('::')
         | 
| 21 22 | 
             
                    case const
         | 
| 22 23 | 
             
                    when Module
         | 
| 24 | 
            +
                      next if const.is_a?(Class)
         | 
| 25 | 
            +
             | 
| 23 26 | 
             
                      child_mod = Module.new
         | 
| 24 27 | 
             
                      child_mod.define_singleton_method(:name) do
         | 
| 25 28 | 
             
                        anc
         | 
| 26 29 | 
             
                      end
         | 
| 27 30 | 
             
                      child_mod.send(:include, const)
         | 
| 28 31 | 
             
                      host.const_set(const_name, child_mod)
         | 
| 29 | 
            -
                    when  | 
| 32 | 
            +
                    when Composable
         | 
| 30 33 | 
             
                      type = const.dup
         | 
| 31 34 | 
             
                      type.freeze.name.set(anc)
         | 
| 32 35 | 
             
                      host.const_set(const_name, type)
         | 
    
        data/lib/plumb/types.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'bigdecimal'
         | 
| 4 | 
            +
            require 'uri'
         | 
| 5 | 
            +
            require 'date'
         | 
| 4 6 |  | 
| 5 7 | 
             
            module Plumb
         | 
| 6 8 | 
             
              # Define core policies
         | 
| @@ -101,7 +103,19 @@ module Plumb | |
| 101 103 | 
             
                             Types::Static[value]
         | 
| 102 104 | 
             
                           end
         | 
| 103 105 |  | 
| 104 | 
            -
                 | 
| 106 | 
            +
                (Types::Undefined >> val_type) | type
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              # Wrap a step execution in a rescue block.
         | 
| 110 | 
            +
              # Expect a specific exception class, and return an invalid result if it is raised.
         | 
| 111 | 
            +
              # Usage:
         | 
| 112 | 
            +
              #   type = Types::String.build(Date, :parse).policy(:rescue, Date::Error)
         | 
| 113 | 
            +
              policy :rescue do |type, exception_class|
         | 
| 114 | 
            +
                Step.new(nil, 'Rescue') do |result|
         | 
| 115 | 
            +
                  type.call(result)
         | 
| 116 | 
            +
                rescue exception_class => e
         | 
| 117 | 
            +
                  result.invalid(errors: e.message)
         | 
| 118 | 
            +
                end
         | 
| 105 119 | 
             
              end
         | 
| 106 120 |  | 
| 107 121 | 
             
              # Split a string into an array. Default separator is /\s*,\s*/
         | 
| @@ -145,6 +159,17 @@ module Plumb | |
| 145 159 | 
             
                Tuple = TupleClass.new
         | 
| 146 160 | 
             
                Hash = HashClass.new
         | 
| 147 161 | 
             
                Interface = InterfaceClass.new
         | 
| 162 | 
            +
                Email = String[URI::MailTo::EMAIL_REGEXP].as_node(:email)
         | 
| 163 | 
            +
                Date = Any[::Date]
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                module UUID
         | 
| 166 | 
            +
                  V4 = String[/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i].as_node(:uuid)
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                class Data
         | 
| 170 | 
            +
                  extend Composable
         | 
| 171 | 
            +
                  include Plumb::Attributes
         | 
| 172 | 
            +
                end
         | 
| 148 173 |  | 
| 149 174 | 
             
                module Lax
         | 
| 150 175 | 
             
                  NUMBER_EXPR = /^\d{1,3}(?:,\d{3})*(?:\.\d+)?$/
         | 
| @@ -185,6 +210,10 @@ module Plumb | |
| 185 210 | 
             
                  Boolean = True | False
         | 
| 186 211 |  | 
| 187 212 | 
             
                  Nil = Nil | (String[BLANK_STRING] >> nil)
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  # Accept a Date, or a string that can be parsed into a Date
         | 
| 215 | 
            +
                  # via Date.parse
         | 
| 216 | 
            +
                  Date = Date | (String >> Any.build(::Date, :parse).policy(:rescue, ::Date::Error))
         | 
| 188 217 | 
             
                end
         | 
| 189 218 | 
             
              end
         | 
| 190 219 | 
             
            end
         | 
    
        data/lib/plumb/value_class.rb
    CHANGED
    
    | @@ -1,15 +1,16 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'plumb/ | 
| 3 | 
            +
            require 'plumb/composable'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Plumb
         | 
| 6 6 | 
             
              class ValueClass
         | 
| 7 | 
            -
                include  | 
| 7 | 
            +
                include Composable
         | 
| 8 8 |  | 
| 9 | 
            -
                attr_reader : | 
| 9 | 
            +
                attr_reader :children
         | 
| 10 10 |  | 
| 11 11 | 
             
                def initialize(value = Undefined)
         | 
| 12 12 | 
             
                  @value = value
         | 
| 13 | 
            +
                  @children = [value].freeze
         | 
| 13 14 | 
             
                  freeze
         | 
| 14 15 | 
             
                end
         | 
| 15 16 |  | 
    
        data/lib/plumb/version.rb
    CHANGED
    
    
| @@ -39,5 +39,11 @@ module Plumb | |
| 39 39 | 
             
                def on_missing_handler(node, _props, method_name)
         | 
| 40 40 | 
             
                  raise "No handler for #{node.inspect} with :#{method_name}"
         | 
| 41 41 | 
             
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def visit_children(node, props = BLANK_HASH)
         | 
| 44 | 
            +
                  node.children.reduce(props) do |acc, child|
         | 
| 45 | 
            +
                    visit(child, acc)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 42 48 | 
             
              end
         | 
| 43 49 | 
             
            end
         | 
    
        data/lib/plumb.rb
    CHANGED
    
    | @@ -10,7 +10,7 @@ module Plumb | |
| 10 10 | 
             
              end
         | 
| 11 11 |  | 
| 12 12 | 
             
              # Register a policy with the given name and block.
         | 
| 13 | 
            -
              # Optionally define a method on the  | 
| 13 | 
            +
              # Optionally define a method on the Composable method to call the policy.
         | 
| 14 14 | 
             
              # Example:
         | 
| 15 15 | 
             
              #   Plumb.policy(:multiply_by, for_type: Integer, helper: true) do |step, factor, &block|
         | 
| 16 16 | 
             
              #     step.transform(Integer) { |number| number * factor }
         | 
| @@ -39,11 +39,11 @@ module Plumb | |
| 39 39 |  | 
| 40 40 | 
             
                return self unless helper
         | 
| 41 41 |  | 
| 42 | 
            -
                if  | 
| 43 | 
            -
                  raise Policies::MethodAlreadyDefinedError, "Method #{name} is already defined on  | 
| 42 | 
            +
                if Composable.instance_methods.include?(name)
         | 
| 43 | 
            +
                  raise Policies::MethodAlreadyDefinedError, "Method #{name} is already defined on Composable"
         | 
| 44 44 | 
             
                end
         | 
| 45 45 |  | 
| 46 | 
            -
                 | 
| 46 | 
            +
                Composable.define_method(name) do |arg = Undefined, &bl|
         | 
| 47 47 | 
             
                  if arg == Undefined
         | 
| 48 48 | 
             
                    policy(name, &bl)
         | 
| 49 49 | 
             
                  else
         | 
| @@ -53,11 +53,15 @@ module Plumb | |
| 53 53 |  | 
| 54 54 | 
             
                self
         | 
| 55 55 | 
             
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              def self.decorate(type, &block)
         | 
| 58 | 
            +
                Decorator.call(type, &block)
         | 
| 59 | 
            +
              end
         | 
| 56 60 | 
             
            end
         | 
| 57 61 |  | 
| 58 62 | 
             
            require 'plumb/result'
         | 
| 59 63 | 
             
            require 'plumb/type_registry'
         | 
| 60 | 
            -
            require 'plumb/ | 
| 64 | 
            +
            require 'plumb/composable'
         | 
| 61 65 | 
             
            require 'plumb/any_class'
         | 
| 62 66 | 
             
            require 'plumb/step'
         | 
| 63 67 | 
             
            require 'plumb/and'
         | 
| @@ -72,6 +76,8 @@ require 'plumb/array_class' | |
| 72 76 | 
             
            require 'plumb/stream_class'
         | 
| 73 77 | 
             
            require 'plumb/hash_class'
         | 
| 74 78 | 
             
            require 'plumb/interface_class'
         | 
| 79 | 
            +
            require 'plumb/attributes'
         | 
| 75 80 | 
             
            require 'plumb/types'
         | 
| 76 81 | 
             
            require 'plumb/json_schema_visitor'
         | 
| 77 82 | 
             
            require 'plumb/schema'
         | 
| 83 | 
            +
            require 'plumb/decorator'
         | 
    
        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. | 
| 4 | 
            +
              version: 0.0.5
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ismael Celis
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-08-30 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bigdecimal
         | 
| @@ -50,17 +50,25 @@ files: | |
| 50 50 | 
             
            - LICENSE.txt
         | 
| 51 51 | 
             
            - README.md
         | 
| 52 52 | 
             
            - Rakefile
         | 
| 53 | 
            +
            - bench/compare_parametric_schema.rb
         | 
| 54 | 
            +
            - bench/compare_parametric_struct.rb
         | 
| 55 | 
            +
            - bench/parametric_schema.rb
         | 
| 56 | 
            +
            - bench/plumb_hash.rb
         | 
| 53 57 | 
             
            - examples/command_objects.rb
         | 
| 54 58 | 
             
            - examples/concurrent_downloads.rb
         | 
| 55 59 | 
             
            - examples/csv_stream.rb
         | 
| 56 60 | 
             
            - examples/env_config.rb
         | 
| 61 | 
            +
            - examples/event_registry.rb
         | 
| 57 62 | 
             
            - examples/programmers.csv
         | 
| 58 63 | 
             
            - examples/weekdays.rb
         | 
| 59 64 | 
             
            - lib/plumb.rb
         | 
| 60 65 | 
             
            - lib/plumb/and.rb
         | 
| 61 66 | 
             
            - lib/plumb/any_class.rb
         | 
| 62 67 | 
             
            - lib/plumb/array_class.rb
         | 
| 68 | 
            +
            - lib/plumb/attributes.rb
         | 
| 63 69 | 
             
            - lib/plumb/build.rb
         | 
| 70 | 
            +
            - lib/plumb/composable.rb
         | 
| 71 | 
            +
            - lib/plumb/decorator.rb
         | 
| 64 72 | 
             
            - lib/plumb/deferred.rb
         | 
| 65 73 | 
             
            - lib/plumb/hash_class.rb
         | 
| 66 74 | 
             
            - lib/plumb/hash_map.rb
         | 
| @@ -79,7 +87,6 @@ files: | |
| 79 87 | 
             
            - lib/plumb/schema.rb
         | 
| 80 88 | 
             
            - lib/plumb/static_class.rb
         | 
| 81 89 | 
             
            - lib/plumb/step.rb
         | 
| 82 | 
            -
            - lib/plumb/steppable.rb
         | 
| 83 90 | 
             
            - lib/plumb/stream_class.rb
         | 
| 84 91 | 
             
            - lib/plumb/tagged_hash.rb
         | 
| 85 92 | 
             
            - lib/plumb/transform.rb
         | 
    
        data/lib/plumb/steppable.rb
    DELETED
    
    | @@ -1,229 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require 'plumb/metadata_visitor'
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            module Plumb
         | 
| 6 | 
            -
              class UndefinedClass
         | 
| 7 | 
            -
                def inspect
         | 
| 8 | 
            -
                  %(Undefined)
         | 
| 9 | 
            -
                end
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                def to_s = inspect
         | 
| 12 | 
            -
                def node_name = :undefined
         | 
| 13 | 
            -
                def empty? = true
         | 
| 14 | 
            -
              end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
              TypeError = Class.new(::TypeError)
         | 
| 17 | 
            -
              Undefined = UndefinedClass.new.freeze
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              BLANK_STRING = ''
         | 
| 20 | 
            -
              BLANK_ARRAY = [].freeze
         | 
| 21 | 
            -
              BLANK_HASH = {}.freeze
         | 
| 22 | 
            -
              BLANK_RESULT = Result.wrap(Undefined)
         | 
| 23 | 
            -
              NOOP = ->(result) { result }
         | 
| 24 | 
            -
             | 
| 25 | 
            -
              module Callable
         | 
| 26 | 
            -
                def metadata
         | 
| 27 | 
            -
                  MetadataVisitor.call(self)
         | 
| 28 | 
            -
                end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                def resolve(value = Undefined)
         | 
| 31 | 
            -
                  call(Result.wrap(value))
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                def parse(value = Undefined)
         | 
| 35 | 
            -
                  result = resolve(value)
         | 
| 36 | 
            -
                  raise TypeError, result.errors if result.invalid?
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  result.value
         | 
| 39 | 
            -
                end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                def call(result)
         | 
| 42 | 
            -
                  raise NotImplementedError, "Implement #call(Result) => Result in #{self.class}"
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
              end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
              module Steppable
         | 
| 47 | 
            -
                include Callable
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                def self.included(base)
         | 
| 50 | 
            -
                  nname = base.name.split('::').last
         | 
| 51 | 
            -
                  nname.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
         | 
| 52 | 
            -
                  nname.downcase!
         | 
| 53 | 
            -
                  nname.gsub!(/_class$/, '')
         | 
| 54 | 
            -
                  nname = nname.to_sym
         | 
| 55 | 
            -
                  base.define_method(:node_name) { nname }
         | 
| 56 | 
            -
                end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                def self.wrap(callable)
         | 
| 59 | 
            -
                  if callable.is_a?(Steppable)
         | 
| 60 | 
            -
                    callable
         | 
| 61 | 
            -
                  elsif callable.is_a?(::Hash)
         | 
| 62 | 
            -
                    HashClass.new(schema: callable)
         | 
| 63 | 
            -
                  elsif callable.respond_to?(:call)
         | 
| 64 | 
            -
                    Step.new(callable)
         | 
| 65 | 
            -
                  else
         | 
| 66 | 
            -
                    MatchClass.new(callable)
         | 
| 67 | 
            -
                  end
         | 
| 68 | 
            -
                end
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                attr_reader :name
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                class Name
         | 
| 73 | 
            -
                  def initialize(name)
         | 
| 74 | 
            -
                    @name = name
         | 
| 75 | 
            -
                  end
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                  def to_s = @name
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                  def set(n)
         | 
| 80 | 
            -
                    @name = n
         | 
| 81 | 
            -
                    self
         | 
| 82 | 
            -
                  end
         | 
| 83 | 
            -
                end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                def freeze
         | 
| 86 | 
            -
                  return self if frozen?
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                  @name = Name.new(_inspect)
         | 
| 89 | 
            -
                  super
         | 
| 90 | 
            -
                end
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                private def _inspect = self.class.name
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                def inspect = name.to_s
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                def node_name = self.class.name.split('::').last.to_sym
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                def defer(definition = nil, &block)
         | 
| 99 | 
            -
                  Deferred.new(definition || block)
         | 
| 100 | 
            -
                end
         | 
| 101 | 
            -
             | 
| 102 | 
            -
                def >>(other)
         | 
| 103 | 
            -
                  And.new(self, Steppable.wrap(other))
         | 
| 104 | 
            -
                end
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                def |(other)
         | 
| 107 | 
            -
                  Or.new(self, Steppable.wrap(other))
         | 
| 108 | 
            -
                end
         | 
| 109 | 
            -
             | 
| 110 | 
            -
                def transform(target_type, callable = nil, &block)
         | 
| 111 | 
            -
                  self >> Transform.new(target_type, callable || block)
         | 
| 112 | 
            -
                end
         | 
| 113 | 
            -
             | 
| 114 | 
            -
                def check(errors = 'did not pass the check', &block)
         | 
| 115 | 
            -
                  self >> MatchClass.new(block, error: errors, label: errors)
         | 
| 116 | 
            -
                end
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                def meta(data = {})
         | 
| 119 | 
            -
                  self >> Metadata.new(data)
         | 
| 120 | 
            -
                end
         | 
| 121 | 
            -
             | 
| 122 | 
            -
                def not(other = self)
         | 
| 123 | 
            -
                  Not.new(other)
         | 
| 124 | 
            -
                end
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                def invalid(errors: nil)
         | 
| 127 | 
            -
                  Not.new(self, errors:)
         | 
| 128 | 
            -
                end
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                def value(val)
         | 
| 131 | 
            -
                  self >> ValueClass.new(val)
         | 
| 132 | 
            -
                end
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                def match(*args)
         | 
| 135 | 
            -
                  self >> MatchClass.new(*args)
         | 
| 136 | 
            -
                end
         | 
| 137 | 
            -
             | 
| 138 | 
            -
                def [](val) = match(val)
         | 
| 139 | 
            -
             | 
| 140 | 
            -
                class Node
         | 
| 141 | 
            -
                  include Steppable
         | 
| 142 | 
            -
             | 
| 143 | 
            -
                  attr_reader :node_name, :type, :attributes
         | 
| 144 | 
            -
             | 
| 145 | 
            -
                  def initialize(node_name, type, attributes = BLANK_HASH)
         | 
| 146 | 
            -
                    @node_name = node_name
         | 
| 147 | 
            -
                    @type = type
         | 
| 148 | 
            -
                    @attributes = attributes
         | 
| 149 | 
            -
                    freeze
         | 
| 150 | 
            -
                  end
         | 
| 151 | 
            -
             | 
| 152 | 
            -
                  def call(result) = type.call(result)
         | 
| 153 | 
            -
                end
         | 
| 154 | 
            -
             | 
| 155 | 
            -
                def as_node(node_name, metadata = BLANK_HASH)
         | 
| 156 | 
            -
                  Node.new(node_name, self, metadata)
         | 
| 157 | 
            -
                end
         | 
| 158 | 
            -
             | 
| 159 | 
            -
                # Register a policy for this step.
         | 
| 160 | 
            -
                # Mode 1.a: #policy(:name, arg) a single policy with an argument
         | 
| 161 | 
            -
                # Mode 1.b: #policy(:name) a single policy without an argument
         | 
| 162 | 
            -
                # Mode 2: #policy(p1: value, p2: value) multiple policies with arguments
         | 
| 163 | 
            -
                # The latter mode will be expanded to multiple #policy calls.
         | 
| 164 | 
            -
                # @return [Step]
         | 
| 165 | 
            -
                def policy(*args, &blk)
         | 
| 166 | 
            -
                  case args
         | 
| 167 | 
            -
                  in [::Symbol => name, *rest] # #policy(:name, arg)
         | 
| 168 | 
            -
                    types = Array(metadata[:type]).uniq
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                    bargs = [self]
         | 
| 171 | 
            -
                    bargs << rest.first if rest.any?
         | 
| 172 | 
            -
                    block = Plumb.policies.get(types, name)
         | 
| 173 | 
            -
                    pol = block.call(*bargs, &blk)
         | 
| 174 | 
            -
             | 
| 175 | 
            -
                    Policy.new(name, rest.first, pol)
         | 
| 176 | 
            -
                  in [::Hash => opts] # #policy(p1: value, p2: value)
         | 
| 177 | 
            -
                    opts.reduce(self) { |step, (name, value)| step.policy(name, value) }
         | 
| 178 | 
            -
                  else
         | 
| 179 | 
            -
                    raise ArgumentError, "expected a symbol or hash, got #{args.inspect}"
         | 
| 180 | 
            -
                  end
         | 
| 181 | 
            -
                end
         | 
| 182 | 
            -
             | 
| 183 | 
            -
                def ===(other)
         | 
| 184 | 
            -
                  case other
         | 
| 185 | 
            -
                  when Steppable
         | 
| 186 | 
            -
                    other == self
         | 
| 187 | 
            -
                  else
         | 
| 188 | 
            -
                    resolve(other).valid?
         | 
| 189 | 
            -
                  end
         | 
| 190 | 
            -
                end
         | 
| 191 | 
            -
             | 
| 192 | 
            -
                def build(cns, factory_method = :new, &block)
         | 
| 193 | 
            -
                  self >> Build.new(cns, factory_method:, &block)
         | 
| 194 | 
            -
                end
         | 
| 195 | 
            -
             | 
| 196 | 
            -
                def pipeline(&block)
         | 
| 197 | 
            -
                  Pipeline.new(self, &block)
         | 
| 198 | 
            -
                end
         | 
| 199 | 
            -
             | 
| 200 | 
            -
                def to_s
         | 
| 201 | 
            -
                  inspect
         | 
| 202 | 
            -
                end
         | 
| 203 | 
            -
             | 
| 204 | 
            -
                # Build a step that will invoke one or more methods on the value.
         | 
| 205 | 
            -
                # Ex 1: Types::String.invoke(:downcase)
         | 
| 206 | 
            -
                # Ex 2: Types::Array.invoke(:[], 1)
         | 
| 207 | 
            -
                # Ex 3 chain of methods: Types::String.invoke([:downcase, :to_sym])
         | 
| 208 | 
            -
                # @return [Step]
         | 
| 209 | 
            -
                def invoke(*args, &block)
         | 
| 210 | 
            -
                  case args
         | 
| 211 | 
            -
                  in [::Symbol => method_name, *rest]
         | 
| 212 | 
            -
                    self >> Step.new(
         | 
| 213 | 
            -
                      ->(result) { result.valid(result.value.public_send(method_name, *rest, &block)) },
         | 
| 214 | 
            -
                      [method_name.inspect, rest.inspect].join(' ')
         | 
| 215 | 
            -
                    )
         | 
| 216 | 
            -
                  in [Array => methods] if methods.all? { |m| m.is_a?(Symbol) }
         | 
| 217 | 
            -
                    methods.reduce(self) { |step, method| step.invoke(method) }
         | 
| 218 | 
            -
                  else
         | 
| 219 | 
            -
                    raise ArgumentError, "expected a symbol or array of symbols, got #{args.inspect}"
         | 
| 220 | 
            -
                  end
         | 
| 221 | 
            -
                end
         | 
| 222 | 
            -
              end
         | 
| 223 | 
            -
            end
         | 
| 224 | 
            -
             | 
| 225 | 
            -
            require 'plumb/deferred'
         | 
| 226 | 
            -
            require 'plumb/transform'
         | 
| 227 | 
            -
            require 'plumb/policy'
         | 
| 228 | 
            -
            require 'plumb/build'
         | 
| 229 | 
            -
            require 'plumb/metadata'
         |