Module: NRSER::Types

Extended by:
Factory
Includes:
Log::Mixin
Defined in:
lib/nrser/types/not.rb,
lib/nrser/types/in.rb,
lib/nrser/types/is.rb,
lib/nrser/types/any.rb,
lib/nrser/types/nil.rb,
lib/nrser/types/is_a.rb,
lib/nrser/types/type.rb,
lib/nrser/types/when.rb,
lib/nrser/types/attrs.rb,
lib/nrser/types/maybe.rb,
lib/nrser/types/pairs.rb,
lib/nrser/types/paths.rb,
lib/nrser/types/shape.rb,
lib/nrser/types/trees.rb,
lib/nrser/types/where.rb,
lib/nrser/types/arrays.rb,
lib/nrser/types/hashes.rb,
lib/nrser/types/labels.rb,
lib/nrser/types/tuples.rb,
lib/nrser/types/bounded.rb,
lib/nrser/types/numbers.rb,
lib/nrser/types/strings.rb,
lib/nrser/types/symbols.rb,
lib/nrser/types/booleans.rb,
lib/nrser/types/responds.rb,
lib/nrser/refinements/types.rb,
lib/nrser/types/combinators.rb,
lib/nrser/types.rb

Overview

Stuff to help you define, test, check and match types in Ruby.

Defined Under Namespace

Modules: Factory Classes: AnyType, ArrayOfType, ArrayType, AttrsType, BooleanType, Bounded, CheckError, Combinator, FalseType, FromStringError, HashOfType, HashType, Intersection, Is, IsA, Maybe, Not, Respond, Shape, TrueType, TupleType, Type, Union, When, Where, XOR

Constant Summary collapse

L_PAREN =

Constants

'('
R_PAREN =

‘❪’

')'
RESPONDS_WITH =

‘❫’

''
ASSOC =
'=>'

Type Factory Functions collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Factory

def_factory

Methods included from Log::Mixin

included, #logger, #logger=

Class Method Details

.check!(value, type) ⇒ Object

Create a Type from ‘type` with make and check that `value` satisfies it, raising if it doesn’t.

Parameters:

  • value (*)

    Value to type check.

  • type (*)

    Type to check value against.

Returns:

  • The ‘value` parameter.

Raises:



103
104
105
# File 'lib/nrser/types.rb', line 103

def self.check! value, type
  make( type ).check! value
end

.from_repr(repr) ⇒ Object

make a type instance from a object representation that can come from a YAML or JSON declaration.



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/nrser/types.rb', line 189

def self.from_repr repr
  match repr, {
    str => ->(string) {
      NRSER::Types.method(string.downcase).call
    },
    
    Hash => ->(hash) {
      raise NotImplementedError, "Haven't gotten to it yet!"
    },
  }
end

.length(exact, options = {}) ⇒ NRSER::Types::Attrs .length(bounds, options = {}) ⇒ NRSER::Types::Attrs

Overloads:

  • .length(exact, options = {}) ⇒ NRSER::Types::Attrs

    Get a length attribute type that specifies an ‘exact` value.

    Examples:

    only_type = NRSER::Types.length 1
    
    only_type.test []
    # => false
    
    only_type.test [:x]
    # => true
    
    only_type.test [:x, :y]
    # => false

    Parameters:

    • exact (Integer)

      Exact non-negative integer that the length must be to satisfy the type created.

    • options (Hash) (defaults to: {})

      Options hash passed up to Type constructor.

    Returns:

    • (NRSER::Types::Attrs)

      Type satisfied by a ‘#length` attribute that is exactly `exact`.

  • .length(bounds, options = {}) ⇒ NRSER::Types::Attrs

    Get a length attribute type satisfied by values within a ‘:min` and `:max` (inclusive).

    Examples:

    three_to_five = NRSER::Types.length( {min: 3, max: 5}, name: '3-5' )
    three_to_five.test [1, 2]               # => false
    three_to_five.test [1, 2, 3]            # => true
    three_to_five.test [1, 2, 3, 4]         # => true
    three_to_five.test [1, 2, 3, 4, 5]      # => true
    three_to_five.test [1, 2, 3, 4, 5, 6]   # => false

    Parameters:

    • bounds (Hash)
    • options (Hash) (defaults to: {})

      Options hash passed up to Type constructor.

    Options Hash (bounds):

    • :min (Integer)

      An optional minimum value that the ‘#length` should not be less than.

    • :max (Integer)

      An optional maximum value that the ‘#length` should not be more than.

    • :length (Integer)

      An optional value for both the minimum and maximum.

    Returns:

    • (NRSER::Types::Attrs)

      Type satisfied by a ‘#length` attribute between the `:min` and `:max` (inclusive).



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/nrser/types/attrs.rb', line 158

def self.length *args
  bounds = {}
  options = if args[1].is_a?( Hash ) then args[1] else {} end
  
  case args[0]
  when ::Integer
    # It's just a length
    return attrs(
      { length: is( non_neg_int.check!( args[0] ) ) },
      **options
    )
    
    bounds[:min] = bounds[:max] = non_neg_int.check args[0]
    
  when ::Hash
    # It's keyword args
    kwds = args[0].sym_keys
    
    # Pull any :min and :max in the keywords
    bounds[:min] = kwds.delete :min
    bounds[:max] = kwds.delete :max
    
    # But override with :length if we got it
    if length = kwds.delete(:length)
      bounds[:min] = length
      bounds[:max] = length
    end
    
    # (Reverse) merge anything else into the options (options hash values
    # take precedence)
    options = kwds.merge options
    
  else
    raise ArgumentError, <<-END.squish
      arg must be positive integer or option hash, found:
      #{ args[0].inspect } of type #{ args[0].class }
    END
    
  end
  
  bounded_type = bounded bounds
  
  length_type = if !bounded_type.min.nil? && bounded_type.min >= 0
    # We don't need the non-neg check
    bounded_type
  else
    # We do need the non-neg check
    intersection(non_neg_int, bounded_type)
  end
  
  options[:name] ||= "Length<#{ bounded_type.name }>"
  
  attrs({ length: length_type }, options)
end

.make(value) ⇒ NRSER::Types::Type

Make a Type from a value.

If the ‘value` argument is…

  • a Type, it is returned.

  • a Class, a new IsA matching that class is returned.

    This allows things like

    NRSER::Types.check 's', String
    NRSER::Types.match 's', String, ->(s) { ... }
    
  • anything else, a new Is matching that value is returned.

Parameters:

Returns:



68
69
70
71
72
73
74
75
76
# File 'lib/nrser/types.rb', line 68

def self.make value
  if value.nil?
    self.nil
  elsif value.is_a? NRSER::Types::Type
    value
  else
    self.when value
  end
end

.makerMethod

The make method reference; for easy map and such.

Returns:



83
84
85
# File 'lib/nrser/types.rb', line 83

def self.maker
  method :make
end

.match(value, *clauses) ⇒ Object

TODO:

Switch match to use ‘===`! Should allow us to avoid making types for everything?

Raises:



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/nrser/types.rb', line 129

def self.match value, *clauses
  if clauses.empty?
    raise ArgumentError.new NRSER.dedent <<-END
      Must supply either a single {type => expression} hash argument or a
      even amount of arguments representing (type, expression) pairs after
      `value`.
      
      #{ NRSER::Version.doc_url 'NRSER/Types#match-class_method' }
    END
  end
  
  enum = if clauses.length == 1 && clauses.first.respond_to?(:each_pair)
    clauses.first.each_pair
  else
    unless clauses.length % 2 == 0
      raise TypeError.new NRSER.dedent <<-END
        When passing a list of clauses, it must be an even length
        representing (type, expression) pairs.
        
        Found an argument list with length #{ clauses.length }:
        
        #{ clauses }
      END
    end
    
    clauses.each_slice(2)
  end
  
  enum.each { |type, expression|
    if test? value, type
      # OK, we matched! Is the corresponding expression callable?
      if expression.respond_to? :call
        # It is; invoke and return result.
        if expression.arity == 0
          return expression.call
        else
          return expression.call value
        end
      else
        # It's not; assume it's a value and return it.
        return expression
      end
    end
  }
  
  raise TypeError, <<-END.dedent
    Could not match value
    
        #{ value.inspect }
    
    to any of types
    
        #{ enum.map {|type, expression| "\n    #{ type.inspect }"}.join '' }
    
  END
end

.parse_number(string) ⇒ Integer, Float

Parse a string into a number.

Returns:

  • (Integer)

    If the string represents a whole integer.

  • (Float)

    If the string represents a decimal number.



16
17
18
19
20
# File 'lib/nrser/types/numbers.rb', line 16

def self.parse_number string
  float = Float string
  int = float.to_i
  if float == int then int else float end
end

.test?(value, type) ⇒ Boolean

Create a Type from ‘type` with make and test if `value` satisfies it.

Returns:

  • (Boolean)

    ‘true` if `value` satisfies `type`.



117
118
119
# File 'lib/nrser/types.rb', line 117

def self.test? value, type
  make(type).test value
end

Instance Method Details

#arrayNRSER::Types::Type

TODO:

Make ‘list` into it’s own looser interface for “array-like” object API.

ArrayType / ArrayOfType factory function.

Parameters:

  • item_type (Type | Object)

    Optional type of items.

Returns:



157
158
159
160
161
162
163
164
165
166
# File 'lib/nrser/types/arrays.rb', line 157

def_factory(
  :array,
  aliases: [:list],
) do |item_type = any, **options|
  if item_type == any
    ArrayType.new **options
  else
    ArrayOfType.new item_type, **options
  end
end

#dir_pathNRSER::Types::Type

A path that is a directory.

Parameters:

Returns:



111
112
113
114
115
116
117
118
# File 'lib/nrser/types/paths.rb', line 111

def_factory :dir_path do |name: 'DirPath', **options|
  intersection \
    path,
    # TODO  How to change this from {.where}?
    where { |path| File.directory? path },
    name: name,
    **options
end

#hash_typeNRSER::Types::HASH, NRSER::Types::Type

TODO:

Make ‘map` into it’s own looser interface for “hash-like” object API.

Type satisfied by Hash instances.

Parameters:

Returns:

  • (NRSER::Types::HASH)

    If ‘args` are empty.

  • (NRSER::Types::Type)

    Newly constructed hash type from ‘args`.



197
198
199
200
201
202
203
204
205
206
# File 'lib/nrser/types/hashes.rb', line 197

def_factory(
  :hash_type,
  aliases: [ :dict, :hash_, :map ]
) do |**kwds|
  if kwds.key?( :keys ) || kwds.key?( :values )
    HashOfType.new **kwds
  else
    HashType.new **kwds
  end
end

#inNRSER::Types::Type

TODO:

I think I want to get rid of where… which would elevate this to it’s own class as a “fundamental” concept (I guess)… not so sure, really. The idea of membership is pretty wide-spread and important, but it’s a bit a vague and inconsistently implemented things.

Type that tests value for membership in a group object via that object’s ‘#include?` method.

Parameters:

  • group (#include?)

    ‘#include?` will be called on this value to determine type membership.

Returns:



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/nrser/types/in.rb', line 20

def_factory(
  :in,
  aliases: [ :member_of ],
) do |group, **options|
  unless group.respond_to? :include?
    raise NRSER::ArgumentError,
      "In `group` must respond to `:include?`",
      group: group
  end
  
  # Provide a some-what useful default name
  options[:name] ||= "In<#{ NRSER.smart_ellipsis group.inspect, 64 }>"
  
  # Unless a `from_s` is provided, just use the identity
  options[:from_s] ||= ->( s ) { s }
  
  where( **options ) { |value| group.include? value }
end

#maybeType

Type satisfied by ‘nil` or the parametrized type.

Parameters:

  • type (Type)

    The type values must be if they are not ‘nil`.

Returns:



80
81
82
# File 'lib/nrser/types/maybe.rb', line 80

def_factory :maybe do |type, **options|
  Maybe.new type, **options
end

#pathNRSER::Types::Type

A path is a non-empty String or Pathname.

Parameters:

  • **options

    see NRSER::Types::Type#initialize

Returns:



54
55
56
57
58
59
60
# File 'lib/nrser/types/paths.rb', line 54

def_factory :path do |name: 'Path', **options|
  one_of \
    non_empty_str,
    non_empty_pathname,
    name: name,
    **options
end

#tupleNRSER::Types::Type

Get a tuple type.

Parameters:

Returns:



113
114
115
# File 'lib/nrser/types/tuples.rb', line 113

def_factory :tuple do |*types, **options|
  TupleType.new *types, **options
end