nrser 0.3.9 → 0.3.10
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/lib/nrser/char/alpha_numeric_sub.rb +9 -19
- data/lib/nrser/char/special.rb +5 -5
- data/lib/nrser/core_ext/array.rb +36 -13
- data/lib/nrser/core_ext/enumerable.rb +1 -0
- data/lib/nrser/core_ext/enumerable/find_map.rb +1 -1
- data/lib/nrser/core_ext/hash/bury.rb +3 -0
- data/lib/nrser/core_ext/hash/extract_values_at.rb +2 -2
- data/lib/nrser/core_ext/method/full_name.rb +1 -1
- data/lib/nrser/core_ext/module/method_objects.rb +1 -1
- data/lib/nrser/core_ext/module/source_locations.rb +27 -15
- data/lib/nrser/core_ext/object/lazy_var.rb +1 -1
- data/lib/nrser/core_ext/pathname.rb +67 -12
- data/lib/nrser/core_ext/pathname/subpath.rb +86 -0
- data/lib/nrser/core_ext/string.rb +28 -1
- data/lib/nrser/core_ext/symbol.rb +11 -12
- data/lib/nrser/errors/README.md +154 -0
- data/lib/nrser/errors/attr_error.rb +146 -53
- data/lib/nrser/errors/count_error.rb +61 -12
- data/lib/nrser/errors/nicer_error.rb +42 -71
- data/lib/nrser/errors/value_error.rb +53 -58
- data/lib/nrser/functions.rb +0 -2
- data/lib/nrser/functions/enumerable.rb +5 -17
- data/lib/nrser/functions/enumerable/associate.rb +14 -5
- data/lib/nrser/functions/enumerable/find_all_map.rb +1 -1
- data/lib/nrser/functions/enumerable/include_slice/array_include_slice.rb +1 -1
- data/lib/nrser/functions/hash/bury.rb +2 -12
- data/lib/nrser/functions/merge_by.rb +2 -2
- data/lib/nrser/functions/module/method_objects.rb +2 -2
- data/lib/nrser/functions/path.rb +185 -165
- data/lib/nrser/functions/path/normalized.rb +84 -0
- data/lib/nrser/functions/string.rb +4 -4
- data/lib/nrser/functions/text/README.md +4 -0
- data/lib/nrser/functions/text/format.rb +53 -0
- data/lib/nrser/functions/text/indentation.rb +6 -6
- data/lib/nrser/functions/text/word_wrap.rb +2 -2
- data/lib/nrser/functions/tree/map_leaves.rb +3 -3
- data/lib/nrser/functions/tree/map_tree.rb +2 -2
- data/lib/nrser/functions/tree/transform.rb +1 -18
- data/lib/nrser/gem_ext/README.md +4 -0
- data/lib/nrser/labs/README.md +8 -0
- data/lib/nrser/labs/config.rb +163 -0
- data/lib/nrser/labs/i8.rb +49 -159
- data/lib/nrser/labs/i8/struct.rb +167 -0
- data/lib/nrser/labs/i8/struct/hash.rb +140 -0
- data/lib/nrser/labs/i8/struct/vector.rb +149 -0
- data/lib/nrser/labs/i8/surjection.rb +211 -0
- data/lib/nrser/labs/lots/consumer.rb +19 -0
- data/lib/nrser/labs/lots/parser.rb +21 -1
- data/lib/nrser/labs/stash.rb +4 -4
- data/lib/nrser/log.rb +25 -21
- data/lib/nrser/log/appender/sync.rb +15 -11
- data/lib/nrser/log/formatters/color.rb +0 -3
- data/lib/nrser/log/formatters/mixin.rb +4 -4
- data/lib/nrser/log/logger.rb +54 -6
- data/lib/nrser/log/mixin.rb +2 -1
- data/lib/nrser/log/plugin.rb +6 -6
- data/lib/nrser/log/types.rb +46 -29
- data/lib/nrser/mean_streak.rb +0 -8
- data/lib/nrser/mean_streak/document.rb +1 -4
- data/lib/nrser/message.rb +3 -3
- data/lib/nrser/meta/README.md +4 -0
- data/lib/nrser/meta/lazy_attr.rb +2 -2
- data/lib/nrser/meta/source/location.rb +1 -1
- data/lib/nrser/props.rb +34 -3
- data/lib/nrser/props/class_methods.rb +2 -1
- data/lib/nrser/props/instance_methods.rb +9 -9
- data/lib/nrser/props/metadata.rb +4 -12
- data/lib/nrser/props/mutable/stash.rb +5 -2
- data/lib/nrser/props/prop.rb +10 -19
- data/lib/nrser/rspex.rb +1 -20
- data/lib/nrser/rspex/example_group/describe_attribute.rb +3 -0
- data/lib/nrser/rspex/example_group/describe_called_with.rb +9 -4
- data/lib/nrser/rspex/example_group/describe_case.rb +1 -0
- data/lib/nrser/rspex/example_group/describe_class.rb +2 -0
- data/lib/nrser/rspex/example_group/describe_group.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_instance.rb +3 -1
- data/lib/nrser/rspex/example_group/describe_message.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_method.rb +64 -30
- data/lib/nrser/rspex/example_group/describe_response_to.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_section.rb +4 -1
- data/lib/nrser/rspex/example_group/describe_sent_to.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_setup.rb +1 -0
- data/lib/nrser/rspex/example_group/describe_source_file.rb +1 -1
- data/lib/nrser/rspex/example_group/describe_spec_file.rb +4 -2
- data/lib/nrser/rspex/example_group/describe_when.rb +2 -1
- data/lib/nrser/rspex/example_group/describe_x.rb +5 -5
- data/lib/nrser/rspex/format.rb +0 -15
- data/lib/nrser/sugar/method_missing_forwarder.rb +3 -3
- data/lib/nrser/sys/env/path.rb +2 -28
- data/lib/nrser/types.rb +63 -12
- data/lib/nrser/types/README.md +76 -0
- data/lib/nrser/types/arrays.rb +192 -137
- data/lib/nrser/types/attributes.rb +269 -0
- data/lib/nrser/types/booleans.rb +134 -83
- data/lib/nrser/types/bounded.rb +110 -47
- data/lib/nrser/types/collections.rb +119 -0
- data/lib/nrser/types/combinators.rb +283 -196
- data/lib/nrser/types/doc/display_table.md +66 -0
- data/lib/nrser/types/eqiuvalent.rb +91 -0
- data/lib/nrser/types/errors/check_error.rb +5 -11
- data/lib/nrser/types/errors/from_string_error.rb +3 -3
- data/lib/nrser/types/factory.rb +287 -20
- data/lib/nrser/types/hashes.rb +227 -179
- data/lib/nrser/types/in.rb +73 -36
- data/lib/nrser/types/is.rb +67 -60
- data/lib/nrser/types/is_a.rb +141 -84
- data/lib/nrser/types/labels.rb +45 -16
- data/lib/nrser/types/maybe.rb +6 -3
- data/lib/nrser/types/nil.rb +64 -27
- data/lib/nrser/types/not.rb +92 -34
- data/lib/nrser/types/numbers.rb +224 -169
- data/lib/nrser/types/pairs.rb +113 -89
- data/lib/nrser/types/paths.rb +250 -137
- data/lib/nrser/types/responds.rb +167 -89
- data/lib/nrser/types/selector.rb +234 -0
- data/lib/nrser/types/shape.rb +136 -65
- data/lib/nrser/types/strings.rb +189 -63
- data/lib/nrser/types/symbols.rb +83 -33
- data/lib/nrser/types/top.rb +89 -0
- data/lib/nrser/types/tuples.rb +134 -98
- data/lib/nrser/types/type.rb +617 -505
- data/lib/nrser/types/when.rb +123 -98
- data/lib/nrser/types/where.rb +182 -91
- data/lib/nrser/version.rb +1 -1
- data/spec/lib/nrser/core_ext/pathname/subpath_spec.rb +22 -0
- data/spec/lib/nrser/errors/attr_error_spec.rb +68 -0
- data/spec/lib/nrser/errors/count_error_spec.rb +69 -0
- data/spec/lib/nrser/functions/path/normalize_path_spec.rb +35 -0
- data/spec/lib/nrser/functions/tree/map_tree_spec.rb +74 -96
- data/spec/lib/nrser/functions/tree/transform_spec.rb +11 -11
- data/spec/lib/nrser/labs/config_spec.rb +22 -0
- data/spec/lib/nrser/labs/i8/struct_spec.rb +39 -0
- data/spec/lib/nrser/types/display_spec.rb +50 -0
- data/spec/lib/nrser/types/paths_spec.rb +16 -10
- data/spec/lib/nrser/types/selector_spec.rb +125 -0
- data/spec/spec_helper.rb +4 -5
- metadata +105 -22
- data/lib/nrser/types/any.rb +0 -41
- data/lib/nrser/types/attrs.rb +0 -213
- data/lib/nrser/types/trees.rb +0 -42
@@ -20,7 +20,7 @@ module NRSER::RSpex::ExampleGroup
|
|
20
20
|
#
|
21
21
|
# @param *description (see #describe_x)
|
22
22
|
#
|
23
|
-
# @param [Hash<Symbol, Object>]
|
23
|
+
# @param [Hash<Symbol, Object>] metadata
|
24
24
|
# RSpec metadata to set for the example group.
|
25
25
|
#
|
26
26
|
# See the `metadata` keyword argument to {#describe_x}.
|
@@ -38,10 +38,10 @@ module NRSER::RSpex::ExampleGroup
|
|
38
38
|
# 2. Built description would need to be conditional on what metadata
|
39
39
|
# was found.
|
40
40
|
#
|
41
|
-
# @param [String] description
|
41
|
+
# @param [String] description
|
42
42
|
# A description of the spec file to add to the RSpec description.
|
43
43
|
#
|
44
|
-
# @param [String] spec_path
|
44
|
+
# @param [String] spec_path
|
45
45
|
# The path to the spec file (just feed it `__FILE__`).
|
46
46
|
#
|
47
47
|
# Probably possible to extract this somehow without having to provide it?
|
@@ -85,5 +85,7 @@ module NRSER::RSpex::ExampleGroup
|
|
85
85
|
&describe_x_body
|
86
86
|
|
87
87
|
end # #describe_spec_file
|
88
|
+
|
89
|
+
alias_method :SPEC_FILE, :describe_spec_file
|
88
90
|
|
89
91
|
end # module NRSER::RSpex::ExampleGroup
|
@@ -9,7 +9,7 @@ module NRSER::RSpex::ExampleGroup
|
|
9
9
|
#
|
10
10
|
# @param *description (see #describe_x)
|
11
11
|
#
|
12
|
-
# @param [Hash<Symbol, Object>]
|
12
|
+
# @param [Hash<Symbol, Object>] bindings
|
13
13
|
# See the `bindings` keyword arg in {#describe_x}.
|
14
14
|
#
|
15
15
|
# @param &body (see #describe_x)
|
@@ -30,6 +30,7 @@ module NRSER::RSpex::ExampleGroup
|
|
30
30
|
# Short names (need `_` pre 'cause of `when` Ruby keyword, and suffix fucks
|
31
31
|
# up auto-indent in Atom/VSCode)
|
32
32
|
alias_method :_when, :describe_when
|
33
|
+
alias_method :WHEN, :describe_when
|
33
34
|
|
34
35
|
|
35
36
|
end # module NRSER::RSpex::ExampleGroup
|
@@ -11,18 +11,18 @@ module NRSER::RSpex::ExampleGroup
|
|
11
11
|
# if you want the RSpex functionality but absolutely have to set some
|
12
12
|
# metadata key that we use for something else.
|
13
13
|
#
|
14
|
-
# @param [Array]
|
14
|
+
# @param [Array] description
|
15
15
|
# Optional list of elements that compose the custom description.
|
16
16
|
#
|
17
17
|
# Will be passed to {NRSER::RSpex::Format.description} to produce the
|
18
18
|
# string value that is in turn passed to {RSpec.describe}.
|
19
19
|
#
|
20
|
-
# @param [Symbol] type
|
20
|
+
# @param [Symbol] type
|
21
21
|
# The RSpex "type" of the example group, which is used to determine the
|
22
22
|
# prefix of the final description and is assigned to the `:type` metadata
|
23
23
|
# key.
|
24
24
|
#
|
25
|
-
# @param [Hash<Symbol, Object>] metadata
|
25
|
+
# @param [Hash<Symbol, Object>] metadata
|
26
26
|
# [RSpec metadata][] to add to the new example group.
|
27
27
|
#
|
28
28
|
# In addition to the keys RSpec will reject, we prohibit `:type` *unless*
|
@@ -33,7 +33,7 @@ module NRSER::RSpex::ExampleGroup
|
|
33
33
|
#
|
34
34
|
# [RSpec metadata]: https://relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata
|
35
35
|
#
|
36
|
-
# @param [Hash<Symbol, Object>] bindings
|
36
|
+
# @param [Hash<Symbol, Object>] bindings
|
37
37
|
# Name to value pairs to bind in the new example group.
|
38
38
|
#
|
39
39
|
# All values will be bound at the example group and example levels -
|
@@ -41,7 +41,7 @@ module NRSER::RSpex::ExampleGroup
|
|
41
41
|
# group level, while they will be automatically unwrapped at the
|
42
42
|
# example level (as the requisite context is available there).
|
43
43
|
#
|
44
|
-
# @param [Boolean] bind_subject
|
44
|
+
# @param [Boolean] bind_subject
|
45
45
|
# When `true` (and there is a `subject_block`) bind the `subject` inside
|
46
46
|
# the new example group.
|
47
47
|
#
|
data/lib/nrser/rspex/format.rb
CHANGED
@@ -114,14 +114,6 @@ module NRSER::RSpex::Format
|
|
114
114
|
end
|
115
115
|
|
116
116
|
|
117
|
-
# @todo Document format_type method.
|
118
|
-
#
|
119
|
-
# @param [type] arg_name
|
120
|
-
# @todo Add name param description.
|
121
|
-
#
|
122
|
-
# @return [return_type]
|
123
|
-
# @todo Document return value.
|
124
|
-
#
|
125
117
|
def self.prepend_type type, description
|
126
118
|
return description if type.nil?
|
127
119
|
|
@@ -160,13 +152,6 @@ module NRSER::RSpex::Format
|
|
160
152
|
end
|
161
153
|
|
162
154
|
|
163
|
-
# @todo Document format method.
|
164
|
-
#
|
165
|
-
# @param [type] arg_name
|
166
|
-
# @todo Add name param description.
|
167
|
-
#
|
168
|
-
# @return [String]
|
169
|
-
#
|
170
155
|
def self.description *parts, type: nil
|
171
156
|
parts.
|
172
157
|
flat_map { |part|
|
@@ -21,7 +21,7 @@ class NRSER::MethodMissingForwarder < BasicObject
|
|
21
21
|
# Instantiate a new `NRSER::MethodMissingForwarder` holding the forwarding
|
22
22
|
# block.
|
23
23
|
#
|
24
|
-
# @param [Proc<(symbol:Symbol, *args, &block)>]
|
24
|
+
# @param [Proc<(symbol:Symbol, *args, &block)>] forwarder
|
25
25
|
# Block that will receive all calls to {#method_missing}.
|
26
26
|
#
|
27
27
|
def initialize &forwarder
|
@@ -37,10 +37,10 @@ class NRSER::MethodMissingForwarder < BasicObject
|
|
37
37
|
# @param [Symbol] symbol
|
38
38
|
# The name of the method that was called.
|
39
39
|
#
|
40
|
-
# @param [Array]
|
40
|
+
# @param [Array] args
|
41
41
|
# Any parameters the missing method was called with.
|
42
42
|
#
|
43
|
-
# @param [Proc?]
|
43
|
+
# @param [Proc?] block
|
44
44
|
# The block the method was called with, if any.
|
45
45
|
#
|
46
46
|
def method_missing symbol, *args, &block
|
data/lib/nrser/sys/env/path.rb
CHANGED
@@ -92,7 +92,7 @@ class NRSER::Sys::Env::Path
|
|
92
92
|
# @param [String] path
|
93
93
|
# Path to test against.
|
94
94
|
#
|
95
|
-
# @param [Array<String | Proc<String=>Boolean> | Regexp>]
|
95
|
+
# @param [Array<String | Proc<String=>Boolean> | Regexp>] patterns
|
96
96
|
# Patterns to test:
|
97
97
|
#
|
98
98
|
# - `String` - test if it and `path` are equal (`==`)
|
@@ -126,14 +126,6 @@ class NRSER::Sys::Env::Path
|
|
126
126
|
end # .matches_pattern?
|
127
127
|
|
128
128
|
|
129
|
-
# @todo Document from_env method.
|
130
|
-
#
|
131
|
-
# @param [type] arg_name
|
132
|
-
# @todo Add name param description.
|
133
|
-
#
|
134
|
-
# @return [return_type]
|
135
|
-
# @todo Document return value.
|
136
|
-
#
|
137
129
|
def self.from_ENV env_key
|
138
130
|
new ENV[env_key.to_s], env_key: env_key
|
139
131
|
end # .from_env
|
@@ -260,7 +252,7 @@ class NRSER::Sys::Env::Path
|
|
260
252
|
#
|
261
253
|
# @see http://www.rubydoc.info/gems/hamster/Hamster/Vector#each-instance_method
|
262
254
|
#
|
263
|
-
# @param [nil | Proc<(String)=>*>]
|
255
|
+
# @param [nil | Proc<(String)=>*>] block
|
264
256
|
# When present, block will be called once for each string path in this
|
265
257
|
# object. First path is most prominent, down to least last.
|
266
258
|
#
|
@@ -302,23 +294,5 @@ class NRSER::Sys::Env::Path
|
|
302
294
|
@value.to_a
|
303
295
|
end
|
304
296
|
|
305
|
-
protected
|
306
|
-
# ========================================================================
|
307
|
-
|
308
|
-
# Internal method to remove paths that have already been normalized.
|
309
|
-
#
|
310
|
-
# @param [type] arg_name
|
311
|
-
# @todo Add name param description.
|
312
|
-
#
|
313
|
-
# @return [return_type]
|
314
|
-
# @todo Document return value.
|
315
|
-
#
|
316
|
-
def remove_paths paths
|
317
|
-
# method body...
|
318
|
-
end # #remove_paths
|
319
|
-
|
320
|
-
|
321
|
-
# end protected
|
322
|
-
|
323
297
|
|
324
298
|
end # class NRSER::Env::Path
|
data/lib/nrser/types.rb
CHANGED
@@ -15,7 +15,7 @@ require 'nrser/log'
|
|
15
15
|
|
16
16
|
# Stuff to help you define, test, check and match types in Ruby.
|
17
17
|
#
|
18
|
-
# {
|
18
|
+
# Read the documentation {file:lib/nrser/types/README.md here}.
|
19
19
|
#
|
20
20
|
module NRSER::Types
|
21
21
|
|
@@ -29,7 +29,10 @@ module NRSER::Types
|
|
29
29
|
# Mixins
|
30
30
|
# ========================================================================
|
31
31
|
|
32
|
+
# Add `.def_type` to define type factories
|
32
33
|
extend Factory
|
34
|
+
|
35
|
+
# Add `.logger` and `#logger`.
|
33
36
|
include NRSER::Log::Mixin
|
34
37
|
|
35
38
|
|
@@ -38,8 +41,18 @@ module NRSER::Types
|
|
38
41
|
|
39
42
|
L_PAREN = '(' # '❪'
|
40
43
|
R_PAREN = ')' # '❫'
|
41
|
-
RESPONDS_WITH = '→'
|
42
|
-
ASSOC = '=>'
|
44
|
+
RESPONDS_WITH = '→' # '->'
|
45
|
+
ASSOC = '=>' # terrible, don't use: '⇒'
|
46
|
+
LEQ = '≤'
|
47
|
+
GEQ = '≥'
|
48
|
+
COMPLEXES = 'ℂ'
|
49
|
+
REALS = 'ℝ'
|
50
|
+
INTEGERS = 'ℤ'
|
51
|
+
RATIONALS = 'ℚ'
|
52
|
+
UNION = '∪'
|
53
|
+
AND = '&'
|
54
|
+
NOT = '¬' # '~'
|
55
|
+
COMPLEMENT = '∖'
|
43
56
|
|
44
57
|
|
45
58
|
# Module Methods
|
@@ -67,11 +80,11 @@ module NRSER::Types
|
|
67
80
|
#
|
68
81
|
def self.make value
|
69
82
|
if value.nil?
|
70
|
-
self.
|
83
|
+
self.Nil
|
71
84
|
elsif value.is_a? NRSER::Types::Type
|
72
85
|
value
|
73
86
|
else
|
74
|
-
self.
|
87
|
+
self.When value
|
75
88
|
end
|
76
89
|
end
|
77
90
|
|
@@ -104,13 +117,34 @@ module NRSER::Types
|
|
104
117
|
make( type ).check! value
|
105
118
|
end
|
106
119
|
|
107
|
-
|
108
|
-
|
120
|
+
|
121
|
+
# Old bang-less name for {.check!}. We like out bangs around here.
|
122
|
+
#
|
123
|
+
# @deprecated
|
124
|
+
#
|
125
|
+
# @param (see .check!)
|
126
|
+
# @return (see .check!)
|
127
|
+
# @raise (see .check!)
|
128
|
+
#
|
129
|
+
def self.check value, type
|
130
|
+
logger.deprecated \
|
131
|
+
method: __method__,
|
132
|
+
alternative: "NRSER::Types.check!"
|
133
|
+
|
134
|
+
check! value, type
|
135
|
+
end
|
109
136
|
|
110
137
|
|
111
138
|
# Create a {NRSER::Types::Type} from `type` with {.make} and test if
|
112
139
|
# `value` satisfies it.
|
113
140
|
#
|
141
|
+
# @param [Object] value
|
142
|
+
# Value to test for membership.
|
143
|
+
#
|
144
|
+
# @param [TYPE] type
|
145
|
+
# Type to see if value satisfies. Passed through {.make} to make sure it's
|
146
|
+
# a {Type} first.
|
147
|
+
#
|
114
148
|
# @return [Boolean]
|
115
149
|
# `true` if `value` satisfies `type`.
|
116
150
|
#
|
@@ -118,13 +152,29 @@ module NRSER::Types
|
|
118
152
|
make(type).test value
|
119
153
|
end
|
120
154
|
|
155
|
+
|
156
|
+
# Old question-less name for {.test?}. We like our marks around here.
|
157
|
+
#
|
158
|
+
# @param (see .test?)
|
159
|
+
# @return (see .test?)
|
160
|
+
# @raise (see .test?)
|
161
|
+
#
|
162
|
+
def self.test value, type
|
163
|
+
logger.deprecated \
|
164
|
+
method: __method__,
|
165
|
+
alternative: "NRSER::Types.test?"
|
166
|
+
|
167
|
+
test? value, type
|
168
|
+
end # .test
|
169
|
+
|
121
170
|
# Old name
|
122
171
|
singleton_class.send :alias_method, :test, :test?
|
123
172
|
|
124
173
|
|
174
|
+
# My own shitty version of pattern matching!
|
175
|
+
#
|
125
176
|
# @todo
|
126
|
-
#
|
127
|
-
# making types for everything?
|
177
|
+
# Doc this crap.
|
128
178
|
#
|
129
179
|
def self.match value, *clauses
|
130
180
|
if clauses.empty?
|
@@ -213,11 +263,11 @@ require_relative './types/is_a'
|
|
213
263
|
require_relative './types/where'
|
214
264
|
require_relative './types/combinators'
|
215
265
|
require_relative './types/maybe'
|
216
|
-
require_relative './types/
|
266
|
+
require_relative './types/attributes'
|
217
267
|
require_relative './types/in'
|
218
268
|
|
219
269
|
require_relative './types/when'
|
220
|
-
require_relative './types/
|
270
|
+
require_relative './types/top'
|
221
271
|
require_relative './types/booleans'
|
222
272
|
|
223
273
|
# Requires `booleans`
|
@@ -232,5 +282,6 @@ require_relative './types/hashes'
|
|
232
282
|
require_relative './types/paths'
|
233
283
|
require_relative './types/tuples'
|
234
284
|
require_relative './types/pairs'
|
235
|
-
require_relative './types/
|
285
|
+
require_relative './types/collections'
|
236
286
|
require_relative './types/shape'
|
287
|
+
require_relative './types/selector'
|
@@ -0,0 +1,76 @@
|
|
1
|
+
About NRSER::Types
|
2
|
+
========================================================================
|
3
|
+
|
4
|
+
Ah, Neil's Shitty Type System (NSTS).
|
5
|
+
|
6
|
+
Kinda like [tcomb][], but for for Ruby, and worse.
|
7
|
+
|
8
|
+
Anyways I needed this because I needed some re-usable way for things to make sure they were in some reasonable state at runtime. And it kind-of works for that so far.
|
9
|
+
|
10
|
+
[tcomb]: https://github.com/gcanti/tcomb
|
11
|
+
|
12
|
+
|
13
|
+
Structure & Design
|
14
|
+
------------------------------------------------------------------------
|
15
|
+
|
16
|
+
At the bottom of everything, there are *type classes*... which are Ruby classes
|
17
|
+
that descend from {NRSER::Types::Type}. We call instances of these classes
|
18
|
+
*types*.
|
19
|
+
|
20
|
+
Types essentially do one thing: they have a {NRSER::Types::Type#test?} method
|
21
|
+
that accepts a single arguments and returns a boolean. If the type returns
|
22
|
+
`true` when `#test?` is called with an object, then that object is a member of
|
23
|
+
that type. That's it.
|
24
|
+
|
25
|
+
Some of those classes, like the universal type {NRSER::Types::Top} - of which
|
26
|
+
every object is a member ({NRSER::Types::Top#test?} always returns `true`) - are
|
27
|
+
essentially singletons, but many type classes parameterize over values that
|
28
|
+
instances hold as variables.
|
29
|
+
|
30
|
+
Types are designed to fundamentally immutable once constructed, through the
|
31
|
+
standard practice of storing state in instance variables without write
|
32
|
+
functions (of course, use of methods like `#instance_variable_set` will still
|
33
|
+
mutate types - please don't do this, since any future caching will likely
|
34
|
+
reuse instances).
|
35
|
+
|
36
|
+
API is basically a collection of module class methods attached to
|
37
|
+
{NRSER::Types}, like {NRSER::Types.str} or {NRSER::Types.non_neg_int}.
|
38
|
+
|
39
|
+
|
40
|
+
### Type Maker Method Names
|
41
|
+
|
42
|
+
I generally went with "short" type names, IDK, to keep things short, and maybe to conflict a bit less with other common names in Ruby.
|
43
|
+
|
44
|
+
Some examples:
|
45
|
+
|
46
|
+
- `str`
|
47
|
+
- `bool`
|
48
|
+
- `non_neg_int`
|
49
|
+
|
50
|
+
You get the idea. Many have their longer names added as aliases, which I like... would rather have it "just work" with what you type than have to remember what exactly we chose to call stuff.
|
51
|
+
|
52
|
+
|
53
|
+
### What to Expect
|
54
|
+
|
55
|
+
These method should all return {NRSER::Types::Type} instances, though many will use a refined subclass of {NRSER::Types::Type} like {NRSER::Types::Bounded} or whatever.
|
56
|
+
|
57
|
+
I picked this functional approach over a more I guess standard object-oriented architecture - where every type would be it's own class and you would instantiate them - because it's along the line of how [tcomb][] is designed and I think it works pretty well there. It also provides a lot of flexibility as far as combinators and such: we will return a {NRSER::Types::Type} that meets your needs, making no further contract, which allows us a lot more freedom to muck around in the back end.
|
58
|
+
|
59
|
+
|
60
|
+
### Them Options
|
61
|
+
|
62
|
+
The {NRSER::Types} module methods should all accept a `options` hash as the last argument that will eventually find it's way up to {NRSER::Types::Type#initialize}. Many should accept other arguments as it makes sense.
|
63
|
+
|
64
|
+
In cases where the type maker method wants a keyword hash of it's own, I think I've been separating them out into two arguments, like:
|
65
|
+
|
66
|
+
def some_type kwds, options = {}
|
67
|
+
...
|
68
|
+
end
|
69
|
+
|
70
|
+
which is a bit of a pain 'cause you gotta use parens if you want to pass options as well as keywords:
|
71
|
+
|
72
|
+
some_type( {x: 1, y: 3}, name: 'SomeType' )
|
73
|
+
|
74
|
+
but you don't run into any conflicts between options and keywords or whatever else, and it's nice to just pick a style and stick with it rather than have to do one or two different 'cause of a conflict.
|
75
|
+
|
76
|
+
|
data/lib/nrser/types/arrays.rb
CHANGED
@@ -8,161 +8,216 @@
|
|
8
8
|
# -----------------------------------------------------------------------
|
9
9
|
|
10
10
|
require_relative './is_a'
|
11
|
+
require_relative './top'
|
12
|
+
|
13
|
+
|
14
|
+
# Namespace
|
15
|
+
# ========================================================================
|
16
|
+
|
17
|
+
module NRSER
|
18
|
+
module Types
|
11
19
|
|
12
20
|
|
13
21
|
# Definitions
|
14
22
|
# =======================================================================
|
23
|
+
|
24
|
+
# Arrays!
|
25
|
+
#
|
26
|
+
# @note
|
27
|
+
# Construct {ArrayType} types using the {.Array} factory.
|
28
|
+
#
|
29
|
+
# @todo
|
30
|
+
# Just call this Array?!
|
31
|
+
#
|
32
|
+
# Combine with arrays of a type?!
|
33
|
+
#
|
34
|
+
class ArrayType < IsA
|
35
|
+
# Default value to split strings with in {#from_s} if the string provided
|
36
|
+
# does is not recognized as an encoding format (as of writing, JSON is
|
37
|
+
# the only format we attempt to detect).
|
38
|
+
#
|
39
|
+
# Splits
|
40
|
+
DEFAULT_SPLIT_WITH = /\,\s*/m
|
15
41
|
|
16
|
-
|
42
|
+
def initialize split_with: DEFAULT_SPLIT_WITH, **options
|
43
|
+
super ::Array, **options
|
44
|
+
@split_with = split_with
|
45
|
+
end
|
17
46
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
def item_type; NRSER::Types.any; end
|
33
|
-
|
34
|
-
|
35
|
-
# Called on an array of string items that have been split
|
36
|
-
# from a single string by {#from_s} to convert each individual item before
|
37
|
-
# {#check} is called on the value.
|
38
|
-
#
|
39
|
-
# {NRSER::Types::ArrayType} implementation is a no-op that just returns
|
40
|
-
# `items` - this method is in place for subclasses to override.
|
41
|
-
#
|
42
|
-
# @param [Array<String>] items
|
43
|
-
#
|
44
|
-
# @return [Array]
|
45
|
-
#
|
46
|
-
def items_from_strings items
|
47
|
-
items
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
def custom_from_s string
|
52
|
-
# Does it looks like a JSON array?
|
53
|
-
if NRSER.looks_like_json_array? string
|
54
|
-
# It does! Load it
|
55
|
-
begin
|
56
|
-
return JSON.load( string )
|
57
|
-
rescue
|
58
|
-
# pass - if we failed to load as JSON, it may just not be JSON, and
|
59
|
-
# we can try the split approach below.
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Split it with the splitter and check that
|
64
|
-
items_from_strings( string.split( @split_with ) )
|
47
|
+
|
48
|
+
def item_type; NRSER::Types.Top; end
|
49
|
+
|
50
|
+
|
51
|
+
# @!group Display Instance Methods
|
52
|
+
# ------------------------------------------------------------------------
|
53
|
+
|
54
|
+
def default_name
|
55
|
+
if item_type == NRSER::Types.Top
|
56
|
+
'Array'
|
57
|
+
else
|
58
|
+
"Array<#{ item_type.name }>"
|
65
59
|
end
|
66
|
-
|
67
|
-
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def default_symbolic
|
64
|
+
"[#{ item_type.symbolic }]"
|
65
|
+
end
|
66
|
+
|
67
|
+
# @!endgroup Display Instance Methods # ************************************
|
68
68
|
|
69
69
|
|
70
|
-
#
|
70
|
+
# Called on an array of string items that have been split
|
71
|
+
# from a single string by {#from_s} to convert each individual item before
|
72
|
+
# {#check} is called on the value.
|
71
73
|
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
# receiving the entry type stuff (which it handles differently).
|
74
|
+
# {NRSER::Types::ArrayType} implementation is a no-op that just returns
|
75
|
+
# `items` - this method is in place for subclasses to override.
|
75
76
|
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
@item_type = NRSER::Types.make item_type
|
96
|
-
end # #initialize
|
97
|
-
|
98
|
-
|
99
|
-
# Instance Methods
|
100
|
-
# ======================================================================
|
101
|
-
|
102
|
-
def explain
|
103
|
-
"#{ super() }<#{ item_type.explain }>"
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
def test? value
|
108
|
-
# Check the super method first, which will test if `value` is an Array
|
109
|
-
# instance, and return `false` if it's not.
|
110
|
-
return false unless super( value )
|
111
|
-
|
112
|
-
# Otherwise test all the items
|
113
|
-
value.all? &@item_type.method( :test )
|
114
|
-
end
|
115
|
-
|
116
|
-
|
117
|
-
# {ArrayOfType} can convert values from strings if it's {#item_type}
|
118
|
-
# can convert values from strings.
|
119
|
-
#
|
120
|
-
# @return [Boolean]
|
121
|
-
#
|
122
|
-
def has_from_s?
|
123
|
-
@from_s || @item_type.has_from_s?
|
124
|
-
end
|
125
|
-
|
126
|
-
|
127
|
-
def items_from_strings items
|
128
|
-
items.map &@item_type.method( :from_s )
|
129
|
-
end
|
130
|
-
|
131
|
-
|
132
|
-
# @todo
|
133
|
-
# I'm not even sure why this is implemented... was it used somewhere?
|
134
|
-
#
|
135
|
-
# It doesn't seems too well thought out... seems like the reality of
|
136
|
-
# comparing types is much more complicated?
|
137
|
-
#
|
138
|
-
def == other
|
139
|
-
equal?(other) || (
|
140
|
-
other.class == self.class && @item_type == other.item_type
|
141
|
-
)
|
77
|
+
# @param [Array<String>] items
|
78
|
+
#
|
79
|
+
# @return [Array]
|
80
|
+
#
|
81
|
+
def items_from_strings items
|
82
|
+
items
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def custom_from_s string
|
87
|
+
# Does it looks like a JSON array?
|
88
|
+
if NRSER.looks_like_json_array? string
|
89
|
+
# It does! Load it
|
90
|
+
begin
|
91
|
+
return JSON.load( string )
|
92
|
+
rescue
|
93
|
+
# pass - if we failed to load as JSON, it may just not be JSON, and
|
94
|
+
# we can try the split approach below.
|
95
|
+
end
|
142
96
|
end
|
143
97
|
|
144
|
-
|
98
|
+
# Split it with the splitter and check that
|
99
|
+
items_from_strings( string.split( @split_with ) )
|
100
|
+
end
|
101
|
+
|
102
|
+
end # ArrayType
|
103
|
+
|
104
|
+
|
105
|
+
# Type for arrays where every entry satisfies a specific type.
|
106
|
+
#
|
107
|
+
# Broken out from {ArrayType} so that {TupleType} can inherit from
|
108
|
+
# {ArrayType} and get share it's string handling functionality without
|
109
|
+
# receiving the entry type stuff (which it handles differently).
|
110
|
+
#
|
111
|
+
class ArrayOfType < ArrayType
|
145
112
|
|
113
|
+
# Attributes
|
114
|
+
# ======================================================================
|
146
115
|
|
147
|
-
#
|
148
|
-
#
|
149
|
-
# @param [Type | Object] item_type
|
150
|
-
# Optional type of items.
|
116
|
+
# Type that all items must satisfy for an array to be a member of this
|
117
|
+
# type.
|
151
118
|
#
|
152
119
|
# @return [NRSER::Types::Type]
|
120
|
+
#
|
121
|
+
attr_reader :item_type
|
122
|
+
|
123
|
+
|
124
|
+
# Constructor
|
125
|
+
# ======================================================================
|
126
|
+
|
127
|
+
# Instantiate a new `ArrayOfType`.
|
128
|
+
def initialize item_type, **options
|
129
|
+
super **options
|
130
|
+
@item_type = NRSER::Types.make item_type
|
131
|
+
end # #initialize
|
132
|
+
|
133
|
+
|
134
|
+
# Instance Methods
|
135
|
+
# ======================================================================
|
136
|
+
|
137
|
+
# @!group Display Instance Methods
|
138
|
+
# ------------------------------------------------------------------------
|
139
|
+
|
140
|
+
def explain
|
141
|
+
"Array<#{ item_type.explain }>"
|
142
|
+
end
|
143
|
+
|
144
|
+
# @!endgroup Display Instance Methods # ************************************
|
145
|
+
|
146
|
+
|
147
|
+
def test? value
|
148
|
+
# Check the super method first, which will test if `value` is an Array
|
149
|
+
# instance, and return `false` if it's not.
|
150
|
+
return false unless super( value )
|
151
|
+
|
152
|
+
# Otherwise test all the items
|
153
|
+
value.all? &@item_type.method( :test )
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# {ArrayOfType} can convert values from strings if it's {#item_type}
|
158
|
+
# can convert values from strings.
|
153
159
|
#
|
160
|
+
# @return [Boolean]
|
161
|
+
#
|
162
|
+
def has_from_s?
|
163
|
+
@from_s || @item_type.has_from_s?
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
def items_from_strings items
|
168
|
+
items.map &@item_type.method( :from_s )
|
169
|
+
end
|
170
|
+
|
171
|
+
|
154
172
|
# @todo
|
155
|
-
#
|
173
|
+
# I'm not even sure why this is implemented... was it used somewhere?
|
174
|
+
#
|
175
|
+
# It doesn't seems too well thought out... seems like the reality of
|
176
|
+
# comparing types is much more complicated?
|
156
177
|
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
ArrayType.new **options
|
163
|
-
else
|
164
|
-
ArrayOfType.new item_type, **options
|
165
|
-
end
|
166
|
-
end # #array
|
178
|
+
def == other
|
179
|
+
equal?(other) || (
|
180
|
+
other.class == self.class && @item_type == other.item_type
|
181
|
+
)
|
182
|
+
end
|
167
183
|
|
168
|
-
end #
|
184
|
+
end # class ArrayOfType
|
185
|
+
|
186
|
+
|
187
|
+
# @!group Array Type Factories
|
188
|
+
# ----------------------------------------------------------------------------
|
189
|
+
|
190
|
+
# @!method self.Array item_type = self.Top, **options
|
191
|
+
# {NRSER::Types::ArrayType} / {NRSER::Types::ArrayOfType} factory function.
|
192
|
+
#
|
193
|
+
# @param [Type | Object] item_type
|
194
|
+
# Optional type of items. If this is not a {Type}, one will be created from
|
195
|
+
# it via {NRSER::Types.make}.
|
196
|
+
#
|
197
|
+
# @param [Hash] options
|
198
|
+
# Passed to {Type#initialize}.
|
199
|
+
#
|
200
|
+
# @return [NRSER::Types::Type]
|
201
|
+
#
|
202
|
+
# @todo
|
203
|
+
# Make `list` into it's own looser interface for "array-like" object API.
|
204
|
+
#
|
205
|
+
def_type :Array,
|
206
|
+
parameterize: :item_type,
|
207
|
+
aliases: [ :list ],
|
208
|
+
&->( item_type = self.Top, **options ) do
|
209
|
+
if item_type == self.Top
|
210
|
+
ArrayType.new **options
|
211
|
+
else
|
212
|
+
ArrayOfType.new item_type, **options
|
213
|
+
end
|
214
|
+
end # .Array
|
215
|
+
|
216
|
+
# @!endgroup Array Type Factories # ******************************************
|
217
|
+
|
218
|
+
|
219
|
+
# /Namespace
|
220
|
+
# ========================================================================
|
221
|
+
|
222
|
+
end # module Types
|
223
|
+
end # module NRSER
|