Module: NRSER::Types
- Extended by:
- Factory
- Includes:
- Log::Mixin
- Defined in:
- lib/nrser/types.rb,
lib/nrser/types/in.rb,
lib/nrser/types/is.rb,
lib/nrser/types/nil.rb,
lib/nrser/types/not.rb,
lib/nrser/types/top.rb,
lib/nrser/types/is_a.rb,
lib/nrser/types/type.rb,
lib/nrser/types/when.rb,
lib/nrser/types/maybe.rb,
lib/nrser/types/pairs.rb,
lib/nrser/types/paths.rb,
lib/nrser/types/shape.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/factory.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/types/selector.rb,
lib/nrser/types/attributes.rb,
lib/nrser/types/eqiuvalent.rb,
lib/nrser/refinements/types.rb,
lib/nrser/types/collections.rb,
lib/nrser/types/combinators.rb
Overview
Stuff to help you define, test, check and match types in Ruby.
Read the documentation here.
Defined Under Namespace
Modules: Factory Classes: ArrayOfType, ArrayType, Attributes, Boolean, Bounded, CheckError, Combinator, Equivalent, False, FromStringError, HashOfType, HashType, Intersection, Is, IsA, Maybe, Not, Respond, Shape, Top, True, Tuple, Type, Union, When, Where, XOR
Constant Summary collapse
- L_PAREN =
Constants
'('
- R_PAREN =
‘❪’
')'
- RESPONDS_WITH =
‘❫’
'→'
- ASSOC =
‘->’
'=>'
- LEQ =
terrible, don’t use: ‘⇒’
'≤'
- GEQ =
'≥'
- COMPLEXES =
'ℂ'
- REALS =
'ℝ'
- INTEGERS =
'ℤ'
- RATIONALS =
'ℚ'
- UNION =
'∪'
- AND =
'&'
- NOT =
‘~’
'¬'
- COMPLEMENT =
'∖'
- TILDE_PATH_RE =
Regular expression to match “tilde” user-home-relative paths.
/\A\~(?:\/|\z)/
In Type Factories collapse
-
.In(group, **options) ⇒ Type
Type that tests value for membership in a group object via that object’s ‘#include?` method.
Identity Equality Type Factories collapse
-
.Is(**options) ⇒ Type
Satisfied by the exact value only (identity comparison via ‘#equal?`).
Nil Type Factories collapse
-
.Nil(**options) ⇒ Type
Type for ‘nil`; itself and only.
Negation Type Factories collapse
-
.Not(type, **options) ⇒ Not
Negates another type.
Class Instance Type Factories collapse
-
.IsA(module_, **options) ⇒ Type
Create a type.
-
.Type(**options) ⇒ IsA
A type whose members are Type instances themselves.
Type Factory Functions collapse
-
.Maybe(type, **options) ⇒ Type
Type satisfied by ‘nil` or the parametrized type.
Pairs Type Factories collapse
-
.ArrayPair(key: self.Top, value: self.Top, **options) ⇒ Type
Type for key/value pairs encoded as a Types.Tuple (Array) of length 2.
-
.HashPair(key: self.Top, value: self.Top, **options) ⇒ Type
Type whose members are single a key/value pairs encoded as Hash instances with a single entry (‘::Hash#length==1`).
-
.Pair(key: self.Top, value: self.Top, **options) ⇒ Type
A key/value pair, which can be encoded as an Array of length 2 or a Hash of length 1.
Path Type Factories collapse
-
.AbsDirPath(**options) ⇒ Type
Absolute Types.Path to a directory (both an Types.AbsPath and an Types.DirPath).
-
.AbsolutePath(**options) ⇒ Type
An absolute Types.Path.
-
.AbsPath(**options) ⇒ Type
A relative Types.Path, which is just a Types.Path that’s not Types.AbsPath or TildePath.
-
.DirPath(**options) ⇒ Type
A Types.Path that is a directory.
-
.FilePath(**options) ⇒ Object
A Types.Path that is a file (using File.file? to test).
-
.HomePath(**options) ⇒ Type
A path that starts with ‘~`, meaning it’s relative to a user’s home directory (to Ruby, see note below).
-
.NonEmptyPathname(**options) ⇒ Type
A Types.Pathname that isn’t empty.
- .NormalizedPath(**options) ⇒ Type
-
.Path(**options) ⇒ Type
A path is a Types.NonEmptyString or Types.NonEmptyPathname.
-
.Pathname(**options) ⇒ Type
Just a type for instances of Types.Pathname.
-
.POSIXPathSegment(**options) ⇒ Type
A POSIX path segment (directory, file name) - any Types.NonEmptyString that doesn’t have ‘/` in it.
Shape Type Factories collapse
Array Type Factories collapse
-
.Array(item_type = self.Top, **options) ⇒ NRSER::Types::Type
ArrayType / ArrayOfType factory function.
Hash Type Factories collapse
-
.Hash(keys: self.Top, values: self.Top, **options) ⇒ HashType
Type satisfied by Types.Hash instances with optional key and value types.
Label Type Factories collapse
-
.Label(**options) ⇒ Type
A label is a non-empty Types.String or Types.Symbol.
Bounded Type Factories collapse
-
.Bounded(**options) ⇒ Type
Create a Bounded type instance that matches values between ‘min` and `max` (inclusive).
Number Type Factories collapse
-
.Integer(**options) ⇒ Type
Instances of the built-in Types.Integer class.
-
.NegativeInteger(**options) ⇒ Type
Integer less than zero.
-
.NonNegativeInteger(**options) ⇒ Type
Positive integers and zero…
-
.NonPositiveInteger(**options) ⇒ Type
Negative integers and zero.
-
.Numeric(**options) ⇒ Type
The Ruby Types.Numeric type, which is the super-class of all number classes: Types.Integer, Float, Rational, Complex.
-
.PositiveInteger(**options) ⇒ Type
Integers greater than zero.
-
.UNIXPort(**options) ⇒ Type
A valid UNIX port number Types.Integer, which is a 16-bit unsigned integer that can ot be ‘0`.
-
.Unsigned16BitInteger(**options) ⇒ Type
Unsigned 16-bit Types.Integer type.
String Type Factories collapse
-
.Character(encoding: nil, **options) ⇒ Type
Types.String of length ‘1` (Ruby lacks a character class).
-
.EmptyString(encoding: nil, **options) ⇒ Type
Get a Type only satisfied by empty strings.
-
.NonEmptyString(encoding: nil, **options) ⇒ Type
Types.String of length ‘1` or more.
- .String(length: nil, encoding: nil, **options) ⇒ Type
-
.UTF8Character(**options) ⇒ Type
A type satisfied by UTF-8 encoded Types.Character.
-
.UTF8String(length: nil, **options) ⇒ Type
A type satisfied by UTF-8 encoded Types.String.
Symbol Type Factories collapse
-
.EmptySymbol(**options) ⇒ Type
Exactly ‘:”`.
-
.NonEmptySymbol(**options) ⇒ Type
A Types.Symbol that is not ‘:”`.
-
.Symbol(**options) ⇒ Type
Types.Symbol instances.
Boolean Type Factories collapse
- .Boolean(**options) ⇒ Type
-
.False(**options) ⇒ Type
A type whose only member is ‘false` and loads from common CLI and ENV var string representations (see False and False::STRINGS).
-
.True(**options) ⇒ Type
A type whose only member is ‘true` and loads from common CLI and ENV var string representations (see True and True::STRINGS).
Method Response Type Factories collapse
-
.Respond(to: , with: , publicly: true, **options) ⇒ Respond
Create a Respond type.
-
.RespondTo(method_name, **options) ⇒ Respond
Gets a Respond that admits values that ‘#respond_to?` a `method_name`.
Selector Type Factories collapse
-
.Selector(pairs, **options) ⇒ Shape
Factory to create Shape type instances that function as MongoDB-esque document query against lists of Ruby objects using the standard Enumerable#select and related methods.
Find Type Factories collapse
-
.Has(member, **options) ⇒ Type
Type that tests value for membership in a group object via that object’s ‘#include?` method.
-
.HasAny(*members, **options) ⇒ Type
Match values that have any of ‘members`.
Attributes Type Factories collapse
-
.Attributes(attrs, **options) ⇒ Object
Get a Type that checks the types of one or more attributes on values.
- .Length(**options) ⇒ Object
Equivalent Type Factories collapse
-
.Equivalent(value, **options) ⇒ Type
Satisfied by values that ‘value` is `#==` to (`{ x : value == x }`).
Collection Type Factories collapse
-
.Bag(**options) ⇒ Type
An Enumerable that does not respond to ‘#each_pair`.
-
.Map(**options) ⇒ Type
A “hash-like” Enumerable that responds to ‘#each_pair` and `#[]`.
-
.Tree(**options) ⇒ Type
Either a Types.Vector or Types.Map - Enumerable collections with indexed elements that work with the NRSER “tree” functions.
-
.Vector(**options) ⇒ Type
An “array-like” Enumerable that responds to ‘#each_index` and `#slice` / `#[]`.
Combinator Type Factories collapse
-
.Intersection(*types, **options) ⇒ Type
Match all of the types.
-
.Union(*types, **options) ⇒ Type
Match any of the types.
-
.XOR(*types, **options) ⇒ Type
Match one of the types only.
Class Method Summary collapse
- .check(value, type) ⇒ Object deprecated Deprecated.
-
.check!(value, type) ⇒ Object
Create a Type from ‘type` with Types.make and check that `value` satisfies it, raising if it doesn’t.
-
.from_repr(repr) ⇒ Object
make a type instance from a object representation that can come from a YAML or JSON declaration.
-
.make(value) ⇒ NRSER::Types::Type
Make a Type from a value.
-
.maker ⇒ Method
The Types.make method reference; for easy map and such.
-
.match(value, *clauses) ⇒ Object
My own shitty version of pattern matching!.
-
.parse_number(string) ⇒ Integer, Float
Parse a string into a number.
-
.test(value, type) ⇒ Boolean
Old question-less name for Types.test?.
-
.test?(value, type) ⇒ Boolean
Create a Type from ‘type` with Types.make and test if `value` satisfies it.
-
.Tuple(*types, **options) ⇒ Tuple
Get a Tuple type.
-
.When(value, **options) ⇒ When
Get a type parameterizing a ‘value` whose members are all objects `obj` such that `value === obj` (“case equality”).
Methods included from Factory
Methods included from Log::Mixin
Class Method Details
.AbsDirPath(**options) ⇒ Type
214 215 216 217 218 219 220 |
# File 'lib/nrser/types/paths.rb', line 214 def_type :AbsDirPath, &->( ** ) do self.Intersection \ self.AbsPath, self.DirPath, ** end |
.AbsolutePath(**options) ⇒ Type
An absolute Path.
115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/nrser/types/paths.rb', line 115 def_type :AbsolutePath, aliases: [ :AbsPath, :abs_path ], # TODO IDK how I feel about this... # from_s: ->( s ) { File.expand_path s }, &->( ** ) do self.Intersection \ self.Path, # Weirdly, there is no {File.absolute?}.. self.Attributes( to_pn: attrs( absolute?: true ) ), ** end |
.AbsPath(**options) ⇒ Type
179 180 181 182 183 184 185 186 |
# File 'lib/nrser/types/paths.rb', line 179 def_type :RelPath, &->( ** ) do self.Intersection \ self.Path, !self.AbsPath, !self.TildePath, ** end |
.Array(item_type = self.Top, **options) ⇒ NRSER::Types::Type
Make ‘list` into it’s own looser interface for “array-like” object API.
ArrayType / ArrayOfType factory function.
205 206 207 208 209 210 211 212 213 214 |
# File 'lib/nrser/types/arrays.rb', line 205 def_type :Array, parameterize: :item_type, aliases: [ :list ], &->( item_type = self.Top, ** ) do if item_type == self.Top ArrayType.new ** else ArrayOfType.new item_type, ** end end |
.ArrayPair(key: self.Top, value: self.Top, **options) ⇒ Type
45 46 47 48 49 50 51 52 53 |
# File 'lib/nrser/types/pairs.rb', line 45 def_type :ArrayPair, default_name: false, parameterize: [ :key, :value ], &->( key: self.Top, value: self.Top, ** ) do tuple \ key, value, ** end |
.Attributes(attrs, **options) ⇒ Object
Get a Type that checks the types of one or more attributes on values.
137 138 139 140 141 142 |
# File 'lib/nrser/types/attributes.rb', line 137 def_type :Attributes, parameterize: :attributes, aliases: [ :attrs, ], &->( attributes, ** ) do Attributes.new attributes, ** end |
.Bag(**options) ⇒ Type
An Enumerable that does not respond to ‘#each_pair`.
Meant to encompass Set, Array and the like without Hash and other associative containers.
Elements may or may not be indexed.
83 84 85 86 87 88 89 90 |
# File 'lib/nrser/types/collections.rb', line 83 def_type :Bag, &->( ** ) do intersection \ is_a( Enumerable ), self.not( respond_to( :each_pair ) ), name: name, ** end |
.Boolean(**options) ⇒ Type
146 147 148 149 150 |
# File 'lib/nrser/types/booleans.rb', line 146 def_type :Boolean, aliases: [ :bool ], &->( ** ) do union self.True, self.False, ** end |
.Bounded(**options) ⇒ Type
Create a Bounded type instance that matches values between ‘min` and `max` (inclusive).
103 104 105 106 107 |
# File 'lib/nrser/types/bounded.rb', line 103 def_type :Bounded, parameterize: [ :min, :max ], &->( min: nil, max: nil, ** ) do Bounded.new min: min, max: max, ** end |
.Character(encoding: nil, **options) ⇒ Type
String of length ‘1` (Ruby lacks a character class).
142 143 144 145 146 |
# File 'lib/nrser/types/strings.rb', line 142 def_type :Character, aliases: [ :char ], &->( encoding: nil, ** ) do self.String **, length: 1, encoding: encoding end |
.check(value, type) ⇒ Object
Old bang-less name for check!. We like out bangs around here.
129 130 131 132 133 134 135 |
# File 'lib/nrser/types.rb', line 129 def self.check value, type logger.deprecated \ method: __method__, alternative: "NRSER::Types.check!" check! value, type end |
.check!(value, type) ⇒ Object
116 117 118 |
# File 'lib/nrser/types.rb', line 116 def self.check! value, type make( type ).check! value end |
.DirPath(**options) ⇒ Type
A Path that is a directory. Requires checking the file system.
197 198 199 200 201 202 203 |
# File 'lib/nrser/types/paths.rb', line 197 def_type :DirPath, &->( ** ) do self.Intersection \ self.Path, self.Where( File.method :directory? ), ** end |
.EmptyString(encoding: nil, **options) ⇒ Type
Get a Type only satisfied by empty strings.
108 109 110 111 112 |
# File 'lib/nrser/types/strings.rb', line 108 def_type :EmptyString, aliases: [ :empty_str ], &->( encoding: nil, ** ) do self.String **, length: 0, encoding: encoding end |
.EmptySymbol(**options) ⇒ Type
Exactly ‘:”`.
Pretty much just exists for use in NonEmptySymbol, which pretty much just exists for use in Label, which actually has some use ;)
54 55 56 57 58 59 |
# File 'lib/nrser/types/symbols.rb', line 54 def_type :EmptySymbol, aliases: [ :empty_sym ], from_s: :to_sym.to_proc, &->( ** ) do self.Is :'', ** end |
.Equivalent(value, **options) ⇒ Type
Satisfied by values that ‘value` is `#==` to (`{ x : value == x }`).
77 78 79 80 81 82 |
# File 'lib/nrser/types/eqiuvalent.rb', line 77 def_type :Equivalent, aliases: [ :eq ], parameterize: :value, &->( value, ** ) do Equivalent.new value, ** end |
.False(**options) ⇒ Type
A type whose only member is ‘false` and loads from common CLI and ENV var string representations (see False and NRSER::Types::False::STRINGS).
132 133 134 135 |
# File 'lib/nrser/types/booleans.rb', line 132 def_type :False, &->( ** ) do False.new ** end |
.FilePath(**options) ⇒ Object
A Path that is a file (using File.file? to test).
231 232 233 234 235 236 237 |
# File 'lib/nrser/types/paths.rb', line 231 def_type :FilePath, &->( ** ) do self.Intersection \ self.Path, self.Where( File.method :file? ), ** end |
.from_repr(repr) ⇒ Object
make a type instance from a object representation that can come from a YAML or JSON declaration.
239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/nrser/types.rb', line 239 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 |
.Has(member, **options) ⇒ Type
The “find” factories got introduced to support Selector, and need improvement. They’re really just stop gaps at the moment, and have already been considerably changed a few times.
I want to eventually make selectors able to output SQL, MongoDB, etc. queries, which will require we get rid of the Where usage…
Type that tests value for membership in a group object via that object’s ‘#include?` method.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/nrser/types/selector.rb', line 172 def_type :Has, parameterize: :member, aliases: [ :has, :includes ], &->( member, ** ) do # Provide a some-what useful default name [:name] ||= "Has<#{ NRSER.smart_ellipsis member.inspect, 64 }>" member_type = make member where( ** ) { |value| value.respond_to?( :find ) && # value.find { |entry| member_type === entry } value.find( &member_type ) } end |
.HasAny(*members, **options) ⇒ Type
The “find” factories got introduced to support Selector, and need improvement. They’re really just stop gaps at the moment, and have already been considerably changed a few times.
I want to eventually make selectors able to output SQL, MongoDB, etc. queries, which will require we get rid of the Where usage…
Match values that have any of ‘members`.
213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/nrser/types/selector.rb', line 213 def_type :HasAny, parameterize: :members, aliases: [ :intersects ], &->( *members, ** ) do [:name] ||= \ "HasAny<#{ NRSER.smart_ellipsis members.inspect, 64 }>" member_types = members.map { |m| make m } where( ** ) { |group| member_types.any? { |member_type| group.find &member_type } } end |
.Hash(keys: self.Top, values: self.Top, **options) ⇒ HashType
Type satisfied by Hash instances with optional key and value types.
238 239 240 241 242 243 244 245 246 247 |
# File 'lib/nrser/types/hashes.rb', line 238 def_type :Hash, aliases: [ :dict, :hash_type ], parameterize: [ :keys, :values ], &->( keys: self.Top, values: self.Top, ** ) do if keys != self.Top || values != self.Top HashOfType.new keys: keys, values: values, ** else HashType.new ** end end |
.HashPair(key: self.Top, value: self.Top, **options) ⇒ Type
Type whose members are single a key/value pairs encoded as Hash instances with a single entry (‘::Hash#length==1`).
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/nrser/types/pairs.rb', line 72 def_type :HashPair, default_name: false, parameterize: [ :key, :value ], &->( key: self.Top, value: self.Top, ** ) do key = self.make key value = self.make value [:name] ||= "Hash<(#{ key.name }, #{ value.name })>" [:symbolic] ||= "(#{ key.symbolic }=>#{ value.symbolic })" intersection \ self.Hash( keys: key, values: value ), self.Length( 1 ), ** end |
.HomePath(**options) ⇒ Type
A path that starts with ‘~`, meaning it’s relative to a user’s home directory (to Ruby, see note below).
> ### Note: How Bash and Ruby Think Differently About Home Paths ### > > #### Ruby Always Tries to Go Home #### > > From my understanding and fiddling around Ruby considers any path that > starts with ‘~` a “home path” for the purpose of expanding, such as in > File.expand_path and Pathname#expand_path. > > You can see this clearly in the [rb_file_expand_path_internal][] C > function, which is where those expand methods end up. > > [rb_file_expand_path_internal]: github.com/ruby/ruby/blob/61bef8612afae25b912627e69699ddbef81adf93/file.c#L3486 > > #### Bash #### > > However > > However - Bash 4’s ‘cd` - on MacOSX, at least - treats `~some_user` as > being a home directory *only if* `some_user` exists… and you may have > a file or directory in the working dir named `~some_user` that it will > correctly fall back on if `some_user` does not exist. > > Paths are complicated, man.
161 162 163 164 165 166 167 |
# File 'lib/nrser/types/paths.rb', line 161 def_type :HomePath, &->( ** ) do self.Intersection \ self.Path, self.Respond( to: [ :start_with?, '~' ], with: self.True ), ** end |
.In(group, **options) ⇒ Type
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.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/nrser/types/in.rb', line 46 def_type :In, aliases: [ :member_of ], from_s: ->( s ) { s }, default_name: ->( group, ** ) { "In<#{ NRSER.smart_ellipsis group.inspect, 64 }>" }, parameterize: :group, &->( group, ** ) do unless group.respond_to? :include? raise NRSER::TypeError, "In `group` must respond to `:include?`", group: group end # TODO This is a step in the right direction (from anon {Proc}) but I # now think what we really want is # # where group, :include? # self.Where group.method( :include? ), ** end |
.Integer(**options) ⇒ Type
Instances of the built-in Integer class.
83 84 85 86 87 88 89 90 91 |
# File 'lib/nrser/types/numbers.rb', line 83 def_type :Integer, symbolic: 'ℤ', from_s: method( :parse_number ), aliases: [ :int, :integer, :signed ], &->( ** ) do IsA.new Integer, ** end |
.Intersection(*types, **options) ⇒ Type
Match all of the types
280 281 282 283 284 285 |
# File 'lib/nrser/types/combinators.rb', line 280 def_type :Intersection, aliases: [ :all_of, :and ], parameterize: [ :types ], &->( *types, ** ) do Intersection.new *types, ** end |
.Is(**options) ⇒ Type
71 72 73 74 75 |
# File 'lib/nrser/types/is.rb', line 71 def_type :Is, parameterize: :value, &->( value, ** ) do Is.new value, ** end |
.IsA(module_, **options) ⇒ Type
124 125 126 127 128 |
# File 'lib/nrser/types/is_a.rb', line 124 def_type :IsA, parameterize: :mod, &->( module_, ** ) do IsA.new module_, ** end |
.Label(**options) ⇒ Type
35 36 37 38 39 40 41 |
# File 'lib/nrser/types/labels.rb', line 35 def_type :Label, &->( ** ) do self.Union \ self.NonEmptyString, self.NonEmptySymbol, ** end |
.length(exact, options = {}) ⇒ NRSER::Types::Attributes .length(bounds, options = {}) ⇒ NRSER::Types::Attributes
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/nrser/types/attributes.rb', line 203 def_type :Length, # TODO This would need special attention if we ever started using the # `parameterize` data for anything... parameterize: :args, &->( *args ) do bounds = {} = 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] ) ) }, ** ) 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) = kwds.merge 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 = self.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 [:name] ||= "Length<#{ bounded_type.name }>" self.Attributes({ length: length_type }, ) end |
.make(value) ⇒ NRSER::Types::Type
81 82 83 84 85 86 87 88 89 |
# File 'lib/nrser/types.rb', line 81 def self.make value if value.nil? self.Nil elsif value.is_a? NRSER::Types::Type value else self.When value end end |
.maker ⇒ Method
The make method reference; for easy map and such.
96 97 98 |
# File 'lib/nrser/types.rb', line 96 def self.maker method :make end |
.Map(**options) ⇒ Type
A “hash-like” Enumerable that responds to ‘#each_pair` and `#[]`.
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/nrser/types/collections.rb', line 58 def_type :Map, aliases: [ :hash_like, :assoc ], &->( ** ) do intersection \ is_a( Enumerable ), respond_to( :each_pair ), respond_to( :[] ), name: name, ** end |
.match(value, *clauses) ⇒ Object
Doc this crap.
My own shitty version of pattern matching!
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 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/nrser/types.rb', line 179 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 |
.Maybe(type, **options) ⇒ Type
Type satisfied by ‘nil` or the parametrized type.
80 81 82 83 84 85 |
# File 'lib/nrser/types/maybe.rb', line 80 def_type :Maybe, parameterize: :type, default_name: false, &->( type, ** ) do Maybe.new type, ** end |
.NegativeInteger(**options) ⇒ Type
Integer less than zero.
125 126 127 128 129 130 131 132 133 134 |
# File 'lib/nrser/types/numbers.rb', line 125 def_type :NegativeInteger, symbolic: 'ℤ⁻', aliases: [ :neg_int, :negative_int ], &->( ** ) do intersection \ self.Integer, self.Bounded( max: -1 ), ** end |
.Nil(**options) ⇒ Type
Should we have a ‘#from_s` that converts the empty string to `nil`?
Kind-of seems like we would want that to be a different types so that you can have a Nil type that is distinct from the empty string in parsing, but also have a type that accepts the empty string and coverts it to ‘nil`?
Something like:
type = t.empty | t.non_empty_str
type.from_s ''
# => nil
type.from_s 'blah'
# => 'blah'
Type for ‘nil`; itself and only.
50 51 52 53 54 55 56 |
# File 'lib/nrser/types/nil.rb', line 50 def_type :Nil, aliases: [ :null ], # `.Nil?` would not make any sense... maybe: false, &->( ** ) do is nil, ** end |
.NonEmptyPathname(**options) ⇒ Type
A Pathname that isn’t empty. Because not emptiness is often important.
62 63 64 65 66 67 68 |
# File 'lib/nrser/types/paths.rb', line 62 def_type :NonEmptyPathname, &->( ** ) do self.Intersection \ self.Pathname, self.Attributes( to_s: self.NonEmptyString ), ** end |
.NonEmptyString(encoding: nil, **options) ⇒ Type
String of length ‘1` or more.
125 126 127 128 129 |
# File 'lib/nrser/types/strings.rb', line 125 def_type :NonEmptyString, aliases: [ :non_empty_str ], &->( encoding: nil, ** ) do self.String **, length: {min: 1}, encoding: encoding end |
.NonEmptySymbol(**options) ⇒ Type
A Symbol that is not ‘:”`.
70 71 72 73 74 75 76 77 |
# File 'lib/nrser/types/symbols.rb', line 70 def_type :NonEmptySymbol, aliases: [ :non_empty_sym ], &->( ** ) do self.Intersection \ self.Symbol, ~self.EmptySymbol, ** end |
.NonNegativeInteger(**options) ⇒ Type
Positive integers and zero… but it seems more efficient to define these as bounded instead of a union.
146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/nrser/types/numbers.rb', line 146 def_type :NonNegativeInteger, symbolic: 'ℕ⁰', aliases: [ :non_neg_int, :unsigned, :index, :non_negative_int, ], &->( ** ) do intersection \ self.Integer, self.Bounded( min: 0 ), ** end |
.NonPositiveInteger(**options) ⇒ Type
Negative integers and zero.
168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/nrser/types/numbers.rb', line 168 def_type :NonPositiveInteger, symbolic: '{0}∪ℤ⁻', aliases: [ :non_pos_int, :non_positive_int, :non_positive_integer ], &->( ** ) do intersection \ self.Integer, self.Bounded( max: 0 ), ** end |
.NormalizedPath(**options) ⇒ Type
Document NormalizedPath type factory.
248 249 250 251 252 253 254 255 |
# File 'lib/nrser/types/paths.rb', line 248 def_type :NormalizedPath, aliases: [ :NormPath, :norm_path ], &->( ** ) do self.Intersection \ self.Path, self.Where( NRSER.method :normalized_path? ), ** end |
.Not(type, **options) ⇒ Not
Negates another type.
87 88 89 90 91 92 |
# File 'lib/nrser/types/not.rb', line 87 def_type :Not, default_name: false, parameterize: :type, &->( type, ** ) do Not.new type, ** end |
.Numeric(**options) ⇒ Type
The Ruby Numeric type, which is the super-class of all number classes: Integer, Float, Rational, Complex.
In set theory notation this would either be expressed as either:
-
ℤ ∪ ℚ ∪ ℝ ∪ ℂ
-
ℂ
depending on how you want to thing about the embeddability of the sets within each other (ℤ is embeddable in ℚ, which is embeddable in ℝ, which is embeddable in ℂ).
However, I feel like (2) is not at all useful for expressing the type, and I feel like the default of just using the NRSER::Types::Type#name as the NRSER::Types::Type#symbolic is easier to read than (1), so this type does not provide a ‘symbolic:` keyword argument.
60 61 62 63 64 65 66 67 68 69 |
# File 'lib/nrser/types/numbers.rb', line 60 def_type :Numeric, aliases: [ :num, :number, :numeric ], # symbolic: [ INTEGERS, # RATIONALS, # REALS, # COMPLEXES ].join( " #{ UNION } " ), from_s: method( :parse_number ), &->( ** ) do IsA.new Numeric, ** end |
.Pair(key: self.Top, value: self.Top, **options) ⇒ Type
A key/value pair, which can be encoded as an Array of length 2 or a Hash of length 1.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/nrser/types/pairs.rb', line 105 def_type :Pair, default_name: false, parameterize: [ :key, :value ], &->( key: self.Top, value: self.Top, ** ) do key = self.make key value = self.make value [:name] ||= if key == self.Top && value == self.Top "Pair" else "Pair<#{ key.name }, #{ value.name }>" end [:symbolic] ||= "(#{ key.symbolic }, #{ value.symbolic })" union \ self.ArrayPair( key: key, value: value ), self.HashPair( key: key, value: value ), ** end |
.parse_number(string) ⇒ Integer, Float
Parse a string into a number.
26 27 28 29 30 |
# File 'lib/nrser/types/numbers.rb', line 26 def self.parse_number string float = Float string int = float.to_i if float == int then int else float end end |
.Path(**options) ⇒ Type
A path is a NonEmptyString or NonEmptyPathname.
79 80 81 82 83 84 85 |
# File 'lib/nrser/types/paths.rb', line 79 def_type :Path, &->( ** ) do self.Union \ self.NonEmptyString, self.NonEmptyPathname, ** end |
.Pathname(**options) ⇒ Type
Just a type for instances of Pathname.
46 47 48 49 50 51 |
# File 'lib/nrser/types/paths.rb', line 46 def_type :Pathname, to_data: :to_s, from_s: ->( string ) { Pathname.new string } \ do |**| is_a Pathname, ** end |
.PositiveInteger(**options) ⇒ Type
Integers greater than zero.
105 106 107 108 109 110 111 112 113 114 |
# File 'lib/nrser/types/numbers.rb', line 105 def_type :PositiveInteger, symbolic: 'ℤ⁺', aliases: [ :pos_int, :positive_int ], &->( ** ) do intersection \ self.Integer, bounded( min: 1 ), ** end |
.POSIXPathSegment(**options) ⇒ Type
A POSIX path segment (directory, file name) - any NonEmptyString that doesn’t have ‘/` in it.
97 98 99 100 101 102 103 104 |
# File 'lib/nrser/types/paths.rb', line 97 def_type :POSIXPathSegment, aliases: [ :path_segment, :path_seg ], &->( ** ) do self.Intersection \ self.NonEmptyString, self.Respond( to: [:include?, '/'], with: false ), ** end |
.Respond(to: , with: , publicly: true, **options) ⇒ Respond
Create a Respond type.
140 141 142 143 144 145 |
# File 'lib/nrser/types/responds.rb', line 140 def_type :Respond, default_name: false, parameterize: [ :to, :with, :publicly ], &->( to:, with:, publicly: true, ** ) do Respond.new to: to, with: with, publicly: publicly, ** end |
.RespondTo(method_name, **options) ⇒ Respond
Gets a Respond that admits values that ‘#respond_to?` a `method_name`.
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/nrser/types/responds.rb', line 159 def_type :RespondTo, default_name: ->( method_name, ** ) { "RespondTo<#{ method_name }>" }, parameterize: :method_name, # TODO I'm not sure how this worked before, but defining `.respond_to?` # def fucks things up... # maybe: false, &->( method_name, ** ) do respond to: [:respond_to?, method_name], with: self.True end |
.Selector(pairs, **options) ⇒ Shape
Factory to create Shape type instances that function as MongoDB-esque document query against lists of Ruby objects using the standard Enumerable#select and related methods.
Selectors are in the very early and experimental stage, but it’s something I’ve been thinking about for a while now that suddenly just sort-of fell into place.
Eventually I want to be able to use these same selectors on SQL, MongoDB, ActiveRecord, etc.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/nrser/types/selector.rb', line 119 def_type :Selector, aliases: [ :query, :[] ], # I don't think we need the `?` methods for Selector? maybe: false, parameterize: :pairs, &->( pairs, ** ) do shape \ pairs.transform_values { |value| if value.is_a?( Type ) value else value_type = self.When value self.or( value_type, (bag & has( value_type )), name: "{#{ value.inspect }}" ) end }, ** end |
.Shape(pairs, **options) ⇒ Shape
137 138 139 140 141 |
# File 'lib/nrser/types/shape.rb', line 137 def_type :Shape, parameterize: true, &->( pairs, ** ) do Shape.new pairs, ** end |
.String(length: nil, encoding: nil, **options) ⇒ Type
Get a Type whose members IsA String, along with some other optional common attribute checks (String#length and String#encoding).
If ‘encoding:` is specified and no `from_s:` is provided, will add a Type#form_s that attempts to transcode strings that are not already in the target encoding (via a simple `String#encode( encoding )`).
If you for some reason don’t want NRSER::Types::Type#from_s to try to transcode, just provide a ‘from_s:` Proc that doesn’t do it - ‘->( s ) { s }` to just use whatever tha cat drags in.
If ‘from_s` is otherwise not provided, adds the obvious identity function.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/nrser/types/strings.rb', line 58 def_type :String, aliases: [ :str ], &->( length: nil, encoding: nil, ** ) do if [ length, encoding ].all?( &:nil? ) # Give 'er the obvious `#from_s` if she don't already have one [:from_s] ||= ->( s ) { s } IsA.new ::String, ** else types = [ IsA.new( ::String ) ] types << self.Length( length ) if length if encoding # If we didn't get a `from_s`, provide one that will try to transcode to # `encoding` (unless it's already there) [:from_s] ||= ->( string ) { if string.encoding == encoding string else string.encode encoding end } types << self.Attributes( encoding: encoding ) else # We don't need to handle encoding, so set the obvious `#from_s` if # one was not provided [:from_s] ||= ->( s ) { s } end self.Intersection *types, ** end end |
.Symbol(**options) ⇒ Type
Symbol instances. Load from strings as you would expect String#to_sym.
35 36 37 38 39 40 |
# File 'lib/nrser/types/symbols.rb', line 35 def_type :Symbol, aliases: [ :sym ], from_s: :to_sym.to_proc, &->( ** ) do self.IsA ::Symbol, ** end |
.test(value, type) ⇒ Boolean
Old question-less name for test?. We like our marks around here.
162 163 164 165 166 167 168 |
# File 'lib/nrser/types.rb', line 162 def self.test value, type logger.deprecated \ method: __method__, alternative: "NRSER::Types.test?" test? value, type end |
.test?(value, type) ⇒ Boolean
151 152 153 |
# File 'lib/nrser/types.rb', line 151 def self.test? value, type make(type).test value end |
.Tree(**options) ⇒ Type
Either a Vector or Map - Enumerable collections with indexed elements that work with the NRSER “tree” functions.
102 103 104 105 106 107 108 109 |
# File 'lib/nrser/types/collections.rb', line 102 def_type :Tree, &->( ** ) do union \ array_like, hash_like, name: name, ** end |
.True(**options) ⇒ Type
A type whose only member is ‘true` and loads from common CLI and ENV var string representations (see True and NRSER::Types::True::STRINGS).
117 118 119 120 |
# File 'lib/nrser/types/booleans.rb', line 117 def_type :True, &->( ** ) do True.new ** end |
.Tuple(*types, **options) ⇒ Tuple
Get a Tuple type.
141 142 143 144 145 146 |
# File 'lib/nrser/types/tuples.rb', line 141 def_type :Tuple, default_name: false, parameterize: :types, &->( *types, ** ) do Tuple.new *types, ** end |
.Type(**options) ⇒ IsA
This is where the methods that load types from data and strings that are used by CLI apps to spec params and the like should go!
A type whose members are Type instances themselves.
143 144 145 146 |
# File 'lib/nrser/types/is_a.rb', line 143 def_type :Type, &->( ** ) do IsA NRSER::Types::Type, ** end |
.Union(*types, **options) ⇒ Type
Match any of the types.
260 261 262 263 264 265 |
# File 'lib/nrser/types/combinators.rb', line 260 def_type :Union, aliases: [ :one_of, :or ], parameterize: [ :types ], &->( *types, ** ) do Union.new *types, ** end |
.UNIXPort(**options) ⇒ Type
A valid UNIX port number Integer, which is a 16-bit unsigned integer that can ot be ‘0`.
213 214 215 216 217 218 219 220 |
# File 'lib/nrser/types/numbers.rb', line 213 def_type :UNIXPort, aliases: [ :port, ], &->( ** ) do intersection \ self.Integer, self.Bounded( min: 1, max: (2**16 - 1) ), ** end |
.Unsigned16BitInteger(**options) ⇒ Type
Unsigned 16-bit Integer type.
192 193 194 195 196 197 198 199 200 201 |
# File 'lib/nrser/types/numbers.rb', line 192 def_type :Unsigned16BitInteger, symbolic: 'uint16', aliases: [ :uint16, :ushort ], &->( ** ) do intersection \ self.Integer, self.Bounded( min: 0, max: ((2 ** 16) - 1) ), ** end |
.UTF8Character(**options) ⇒ Type
A type satisfied by UTF-8 encoded Character.
181 182 183 184 185 186 187 188 |
# File 'lib/nrser/types/strings.rb', line 181 def_type :UTF8Character, # NOTE "UTF8Character".underscore -> "utf8_character" aliases: [ :utf_8_character, :utf8_char, :utf_8_char ], &->( ** ) do self.Character **, encoding: Encoding::UTF_8 end |
.UTF8String(length: nil, **options) ⇒ Type
A type satisfied by UTF-8 encoded String.
159 160 161 162 163 164 165 166 167 168 |
# File 'lib/nrser/types/strings.rb', line 159 def_type :UTF8String, # NOTE "UTF8String".underscore -> "utf8_string" aliases: [ :utf_8_string, :utf8, :utf_8, :utf8_str, :utf_8_str ], &->( length: nil, ** ) do self.String **, length: length, encoding: Encoding::UTF_8 end |
.Vector(**options) ⇒ Type
An “array-like” Enumerable that responds to ‘#each_index` and `#slice` / `#[]`.
37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/nrser/types/collections.rb', line 37 def_type :Vector, aliases: [ :array_like ], &->( ** ) do intersection \ is_a( Enumerable ), respond_to( :each_index ), respond_to( :slice ), respond_to( :[] ), name: name, ** end |
.When(value, **options) ⇒ When
Get a type parameterizing a ‘value` whose members are all objects `obj` such that `value === obj` (“case equality”).
137 138 139 140 141 142 |
# File 'lib/nrser/types/when.rb', line 137 def_type :When, parameterize: :value, default_name: false, &->( value, ** ) do When.new value, ** end |