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
data/lib/nrser/types/type.rb
CHANGED
@@ -18,527 +18,639 @@ require_relative './errors/check_error'
|
|
18
18
|
require_relative './errors/from_string_error'
|
19
19
|
|
20
20
|
|
21
|
+
# Namespace
|
22
|
+
# ========================================================================
|
23
|
+
|
24
|
+
module NRSER
|
25
|
+
module Types
|
26
|
+
|
27
|
+
|
21
28
|
# Definitions
|
22
29
|
# =======================================================================
|
23
30
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
elsif from_data.respond_to?( :call )
|
75
|
-
from_data
|
76
|
-
elsif from_data.respond_to?( :to_proc )
|
77
|
-
from_data.to_proc
|
78
|
-
else
|
79
|
-
raise TypeError.new binding.erb <<-ERB
|
80
|
-
`to_data:` keyword arg must be `nil`, respond to `#call` or respond
|
81
|
-
to `#to_proc`.
|
82
|
-
|
83
|
-
Found value:
|
84
|
-
|
85
|
-
<%= from_data.pretty_inspect %>
|
86
|
-
|
87
|
-
(type <%= from_data.class %>)
|
88
|
-
|
89
|
-
ERB
|
90
|
-
end
|
91
|
-
end # #initialize
|
92
|
-
|
93
|
-
|
94
|
-
# Instance Methods
|
95
|
-
# ========================================================================
|
96
|
-
|
97
|
-
# @!group Display Instance Methods
|
98
|
-
# ------------------------------------------------------------------------
|
99
|
-
|
100
|
-
# What this type likes to be called (and displayed as by default).
|
101
|
-
#
|
102
|
-
# Custom names can be provided when constructing most types via the
|
103
|
-
# `name:` keyword, which allows thinking about composite and complicated
|
104
|
-
# types in simpler and application-specific terms.
|
105
|
-
#
|
106
|
-
# Realizing subclasses **should not** override this method - they should
|
107
|
-
# pass a `name:` keyword up to {#initialize}, which sets the `@name`
|
108
|
-
# instance variable that is then used here.
|
109
|
-
#
|
110
|
-
# If no name is provided to {#initialize}, this method will fall back to
|
111
|
-
# {#explain}.
|
112
|
-
#
|
113
|
-
# @return [String]
|
114
|
-
#
|
115
|
-
def name
|
116
|
-
@name || explain
|
31
|
+
class Type
|
32
|
+
|
33
|
+
# Constructor
|
34
|
+
# =====================================================================
|
35
|
+
|
36
|
+
# Instantiate a new `NRSER::Types::Type`.
|
37
|
+
#
|
38
|
+
# @param [nil | #to_s] name
|
39
|
+
# Name that will be used when displaying the type, or `nil` to use a
|
40
|
+
# default generated name.
|
41
|
+
#
|
42
|
+
# @param [nil | #call] from_s
|
43
|
+
# Callable that will be passed a {String} and should return an object
|
44
|
+
# that satisfies the type if it possible to create one.
|
45
|
+
#
|
46
|
+
# The returned value *will* be checked against the type, so returning a
|
47
|
+
# value that doesn't satisfy will result in a {TypeError} being raised
|
48
|
+
# by {#from_s}.
|
49
|
+
#
|
50
|
+
# @param [nil | #call | #to_proc] to_data
|
51
|
+
# Optional callable (or object that responds to `#to_proc` so we can
|
52
|
+
# get a callable) to call to turn type members into "data".
|
53
|
+
#
|
54
|
+
def initialize name: nil,
|
55
|
+
symbolic: nil,
|
56
|
+
from_s: nil,
|
57
|
+
to_data: nil,
|
58
|
+
from_data: nil
|
59
|
+
@name = name.nil? ? nil : name.to_s
|
60
|
+
@from_s = from_s
|
61
|
+
@symbolic = symbolic
|
62
|
+
|
63
|
+
@to_data = if to_data.nil?
|
64
|
+
nil
|
65
|
+
elsif to_data.respond_to?( :call )
|
66
|
+
to_data
|
67
|
+
elsif to_data.respond_to?( :to_proc )
|
68
|
+
to_data.to_proc
|
69
|
+
else
|
70
|
+
raise TypeError.new binding.erb <<-ERB
|
71
|
+
`to_data:` keyword arg must be `nil`, respond to `#call` or respond
|
72
|
+
to `#to_proc`.
|
73
|
+
|
74
|
+
Found value:
|
75
|
+
|
76
|
+
<%= to_data.pretty_inspect %>
|
77
|
+
|
78
|
+
(type <%= to_data.class %>)
|
79
|
+
|
80
|
+
ERB
|
117
81
|
end
|
118
82
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
# @return [String]
|
138
|
-
#
|
139
|
-
def explain
|
140
|
-
self.class.demod_name
|
83
|
+
@from_data = if from_data.nil?
|
84
|
+
nil
|
85
|
+
elsif from_data.respond_to?( :call )
|
86
|
+
from_data
|
87
|
+
elsif from_data.respond_to?( :to_proc )
|
88
|
+
from_data.to_proc
|
89
|
+
else
|
90
|
+
raise TypeError.new binding.erb <<-ERB
|
91
|
+
`to_data:` keyword arg must be `nil`, respond to `#call` or respond
|
92
|
+
to `#to_proc`.
|
93
|
+
|
94
|
+
Found value:
|
95
|
+
|
96
|
+
<%= from_data.pretty_inspect %>
|
97
|
+
|
98
|
+
(type <%= from_data.class %>)
|
99
|
+
|
100
|
+
ERB
|
141
101
|
end
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
102
|
+
end # #initialize
|
103
|
+
|
104
|
+
|
105
|
+
# Instance Methods
|
106
|
+
# ========================================================================
|
107
|
+
|
108
|
+
# @!group Display Instance Methods
|
109
|
+
# ------------------------------------------------------------------------
|
110
|
+
#
|
111
|
+
# Display ends up being pretty important with types, and also ends up
|
112
|
+
# complete mess very easily.
|
113
|
+
#
|
114
|
+
|
115
|
+
# Optional support for generated names to use when no name is explicitly
|
116
|
+
# provided at initialization instead of falling back to {#explain}.
|
117
|
+
#
|
118
|
+
# Realizing subclasses *SHOULD* override this method *IF* they are able to
|
119
|
+
# generate meaningful names.
|
120
|
+
#
|
121
|
+
# The {Type#default_name} implementation returns `nil` indicating it is not
|
122
|
+
# able to generate names.
|
123
|
+
#
|
124
|
+
# @return [nil]
|
125
|
+
# When a name can not be generated.
|
126
|
+
#
|
127
|
+
# @return [String]
|
128
|
+
# The generated name.
|
129
|
+
#
|
130
|
+
def default_name
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# The name is the type as you would write it out in documentation.
|
136
|
+
#
|
137
|
+
# Prefers an explicit `@name` set at initialization, falling back first
|
138
|
+
# to {#default_name}, then to {#explain}.
|
139
|
+
#
|
140
|
+
# @return [String]
|
141
|
+
#
|
142
|
+
def name
|
143
|
+
@name || default_name || explain
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# Optional support for generated symbolic names to use when no symbolic name
|
148
|
+
# is provided at initialization instead of falling back to {#name}.
|
149
|
+
#
|
150
|
+
# Realizing subclasses *SHOULD* override this method *IF* they are able to
|
151
|
+
# generate meaningful symbolic names.
|
152
|
+
#
|
153
|
+
# The {Type#default_symbolic} implementation returns `nil` indicating it is
|
154
|
+
# not able to generate names.
|
155
|
+
#
|
156
|
+
# @return [nil]
|
157
|
+
# When a symbolic name can not be generated.
|
158
|
+
#
|
159
|
+
# @return [String]
|
160
|
+
# The generated symbolic name.
|
161
|
+
#
|
162
|
+
def default_symbolic
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# A set theory-esque symbolic representation of the type, if available.
|
168
|
+
#
|
169
|
+
# Prefers an explicit `@symbolic` set at initialization, falling back to an
|
170
|
+
# explicit `@name`, then to {#default_symbolic} and finally {#name}.
|
171
|
+
#
|
172
|
+
# @return [String]
|
173
|
+
#
|
174
|
+
def symbolic
|
175
|
+
@symbolic || @name || default_symbolic || name
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# A verbose breakdown of the implementation internals of the type.
|
180
|
+
#
|
181
|
+
# Realizing classes *SHOULD* override this method with a precise
|
182
|
+
# implementation.
|
183
|
+
#
|
184
|
+
# The {Type#explain} implementation dumps all instance variables that
|
185
|
+
# appear to have accessor methods and whose names to not start with `_`.
|
186
|
+
#
|
187
|
+
# Check out the {file:lib/nrser/types/doc/display_table.md display table}
|
188
|
+
# for examples.
|
189
|
+
#
|
190
|
+
# @return [String]
|
191
|
+
#
|
192
|
+
def explain
|
193
|
+
@_explain ||= begin
|
194
|
+
s = "#{ self.class.demod_name }<"
|
195
|
+
|
196
|
+
ivars = instance_variables.
|
197
|
+
# each_with_object( {} ) { |var_name, hash|
|
198
|
+
each_with_object( [] ) { |var_name, array|
|
199
|
+
method_name = var_name.to_s[1..-1]
|
200
|
+
|
201
|
+
if method_name.start_with?( '_' ) ||
|
202
|
+
!respond_to?( method_name )
|
203
|
+
next
|
204
|
+
end
|
205
|
+
|
206
|
+
value = instance_variable_get var_name
|
207
|
+
|
208
|
+
unless value.nil?
|
209
|
+
array << "#{ var_name }=#{ value.inspect }"
|
210
|
+
end
|
211
|
+
}.join( ', ' )
|
193
212
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
details: details
|
213
|
+
s += " #{ ivars }" unless ivars.empty?
|
214
|
+
s += '>'
|
215
|
+
s
|
198
216
|
end
|
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
|
-
|
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
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
value
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
217
|
+
end # #explain
|
218
|
+
|
219
|
+
|
220
|
+
# How to display the type. Proxies to {#symbolic}.
|
221
|
+
#
|
222
|
+
# @return [String]
|
223
|
+
#
|
224
|
+
def to_s
|
225
|
+
symbolic
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
# The built-in `#inspect` method, aliased before we override it so it's
|
230
|
+
# available here if you should want it.
|
231
|
+
#
|
232
|
+
# See {#inspect} for rationale.
|
233
|
+
#
|
234
|
+
# @return [String]
|
235
|
+
#
|
236
|
+
alias_method :builtin_inspect, :inspect
|
237
|
+
|
238
|
+
|
239
|
+
# Overridden to point to {#to_s}.
|
240
|
+
#
|
241
|
+
# Due to their combinatoric nature, types can quickly become large data
|
242
|
+
# hierarchies, and the built-in {#inspect} will produce a massive dump
|
243
|
+
# that's distracting and hard to decipher.
|
244
|
+
#
|
245
|
+
# {#inspect} is readily used in tools like `pry` and `rspec`, significantly
|
246
|
+
# impacting their usefulness when working with types.
|
247
|
+
#
|
248
|
+
# As a solution, we alias the built-in `#inspect` as {#builtin_inspect},
|
249
|
+
# so it's available in situations where you really want all those gory
|
250
|
+
# details, and point {#inspect} to {#to_s}.
|
251
|
+
#
|
252
|
+
# @return [String]
|
253
|
+
#
|
254
|
+
def inspect
|
255
|
+
to_s
|
256
|
+
end
|
257
|
+
|
258
|
+
# @!endgroup Display Instance Methods # ************************************
|
259
|
+
|
260
|
+
|
261
|
+
# @!group Validation Instance Methods
|
262
|
+
# ------------------------------------------------------------------------
|
263
|
+
#
|
264
|
+
# The core of what a type does.
|
265
|
+
#
|
266
|
+
|
267
|
+
# See if a value satisfies the type.
|
268
|
+
#
|
269
|
+
# Realizing classes **must** implement this method.
|
270
|
+
#
|
271
|
+
# This implementation just defines the API; it always raises
|
272
|
+
# {NRSER::AbstractMethodError}.
|
273
|
+
#
|
274
|
+
# @param [Object] value
|
275
|
+
# Value to test for type satisfaction.
|
276
|
+
#
|
277
|
+
# @return [Boolean]
|
278
|
+
# `true` if the `value` satisfies the type.
|
279
|
+
#
|
280
|
+
def test? value
|
281
|
+
raise NRSER::AbstractMethodError.new( self, __method__ )
|
282
|
+
end
|
283
|
+
|
284
|
+
# Old name for {#test?}.
|
285
|
+
#
|
286
|
+
# @deprecated
|
287
|
+
#
|
288
|
+
# @param (see #test?)
|
289
|
+
# @return (see #test?)
|
290
|
+
# @raise (see #test?)
|
291
|
+
#
|
292
|
+
def test value; test? value; end
|
293
|
+
|
294
|
+
|
295
|
+
# Check that a `value` satisfies the type.
|
296
|
+
#
|
297
|
+
# @see #test?
|
298
|
+
#
|
299
|
+
# @return [Object]
|
300
|
+
# The value itself.
|
301
|
+
#
|
302
|
+
# @raise [NRSER::Types::CheckError]
|
303
|
+
# If the value does not satisfy this type.
|
304
|
+
#
|
305
|
+
def check! value, &details
|
306
|
+
# success case
|
307
|
+
return value if test? value
|
308
|
+
|
309
|
+
raise NRSER::Types::CheckError.new \
|
310
|
+
value: value,
|
311
|
+
type: self,
|
312
|
+
details: details
|
313
|
+
end
|
314
|
+
|
315
|
+
# Old name for {#check!} without the bang.
|
316
|
+
def check *args, █ check! *args, █ end
|
317
|
+
|
318
|
+
# @!endgroup Validation Instance Methods # *******************************
|
319
|
+
|
320
|
+
|
321
|
+
# @!group Loading Values Instance Methods
|
322
|
+
# ------------------------------------------------------------------------
|
323
|
+
#
|
324
|
+
# Types include facilities for loading values from representations and
|
325
|
+
# encodings.
|
326
|
+
#
|
327
|
+
# This was initially driven by the desire to use types to
|
328
|
+
# declare CLI parameter schemas, as way to dispatch with the often
|
329
|
+
# limited and arbitrary support most "CLI frameworks" have for declaring
|
330
|
+
# option types - things like "you can have an integer, and you can
|
331
|
+
# have an array, but you can't have an array of integers".
|
332
|
+
#
|
333
|
+
# By using compossible types that can load values from strings we get a
|
334
|
+
# system where you can easily declare whatever complex and granular types
|
335
|
+
# you desire and have the machine automatically load and validate them,
|
336
|
+
# as well as provide reasonable generated feedback when something doesn't
|
337
|
+
# meet expectations, which has worked out quite well so far start to cut
|
338
|
+
# down the amount of repetitive and error-prone "did I get what I need?
|
339
|
+
# No, did I get exactly what I need?" bullshit in receiving data.
|
340
|
+
#
|
341
|
+
# This approach is now being expanded to "data" - an ill-formed concept
|
342
|
+
# I've been brewing of "reasonable common and portable data
|
343
|
+
# representation" and the {NRSER::Props} system, which has been coming
|
344
|
+
# along as well.
|
345
|
+
#
|
346
|
+
|
347
|
+
# Test if the type knows how to load values from strings.
|
348
|
+
#
|
349
|
+
# Looks for the `@from_s` instance variable or a `#custom_from_s`
|
350
|
+
# method.
|
351
|
+
#
|
352
|
+
# @note
|
353
|
+
# When this method returns `true` it simply indicates that some method
|
354
|
+
# of loading from strings exists - the load itself can of course still
|
355
|
+
# fail.
|
356
|
+
#
|
357
|
+
# Realizing classes should only need to override this method to limited or
|
358
|
+
# expand the scope relative to parameterized types.
|
359
|
+
#
|
360
|
+
# @return [Boolean]
|
361
|
+
#
|
362
|
+
def has_from_s?
|
363
|
+
!@from_s.nil? ||
|
364
|
+
# Need the `true` second arg to include protected methods
|
365
|
+
respond_to?( :custom_from_s, true )
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
# Load a value of this type from a string representation by passing
|
370
|
+
# `string` to the {@from_s} {Proc}.
|
371
|
+
#
|
372
|
+
# Checks the value {@from_s} returns with {#check!} before returning it, so
|
373
|
+
# you know it satisfies this type.
|
374
|
+
#
|
375
|
+
# Realizing classes **should not** need to override this - they can define
|
376
|
+
# a `#custom_from_s` instance method for it to use, allowing individual
|
377
|
+
# types to still override that by providing a `from_s:` proc keyword
|
378
|
+
# arg at construction. This also lets them avoid checking the returned
|
379
|
+
# value, since we do so here.
|
380
|
+
#
|
381
|
+
# @param [String] string
|
382
|
+
# String representation.
|
383
|
+
#
|
384
|
+
# @return [Object]
|
385
|
+
# Value that has passed {#check!}.
|
386
|
+
#
|
387
|
+
# @raise [NoMethodError]
|
388
|
+
# If this type doesn't know how to load values from strings.
|
389
|
+
#
|
390
|
+
# In basic types this happens when {NRSER::Types::Type#initialize} was
|
391
|
+
# not provided a `from_s:` {Proc} argument.
|
392
|
+
#
|
393
|
+
# {NRSER::Types::Type} subclasses may override {#from_s} entirely,
|
394
|
+
# divorcing it from the `from_s:` constructor argument and internal
|
395
|
+
# {@from_s} instance variable (which is why {@from_s} is not publicly
|
396
|
+
# exposed - it should not be assumed to dictate {#from_s} behavior
|
397
|
+
# in general).
|
398
|
+
#
|
399
|
+
# @raise [TypeError]
|
400
|
+
# If the value loaded does not pass {#check}.
|
401
|
+
#
|
402
|
+
def from_s string
|
403
|
+
unless has_from_s?
|
404
|
+
raise NoMethodError, "#from_s not defined for type #{ name }"
|
299
405
|
end
|
300
406
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
# Realizing classes *may* need to override this to limited or expand
|
306
|
-
# responses relative to parameterized types.
|
307
|
-
#
|
308
|
-
# @return [Boolean]
|
309
|
-
#
|
310
|
-
def has_from_data?
|
311
|
-
!@from_data.nil? ||
|
312
|
-
# Need the `true` second arg to include protected methods
|
313
|
-
respond_to?( :custom_from_data, true )
|
407
|
+
value = if @from_s
|
408
|
+
@from_s.call string
|
409
|
+
else
|
410
|
+
custom_from_s string
|
314
411
|
end
|
315
412
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
413
|
+
check! value
|
414
|
+
end
|
415
|
+
|
416
|
+
|
417
|
+
# Test if the type can load values from "data" - basic values and
|
418
|
+
# collections like {Array} and {Hash} forming tree-like structures.
|
419
|
+
#
|
420
|
+
# Realizing classes *may* need to override this to limited or expand
|
421
|
+
# responses relative to parameterized types.
|
422
|
+
#
|
423
|
+
# @return [Boolean]
|
424
|
+
#
|
425
|
+
def has_from_data?
|
426
|
+
!@from_data.nil? ||
|
427
|
+
# Need the `true` second arg to include protected methods
|
428
|
+
respond_to?( :custom_from_data, true )
|
429
|
+
end
|
430
|
+
|
431
|
+
|
432
|
+
# Try to load a value from "data" - basic values and
|
433
|
+
# collections like {Array} and {Hash} forming tree-like structures.
|
434
|
+
#
|
435
|
+
# @param [Object] data
|
436
|
+
# Data to try to load from.
|
437
|
+
#
|
438
|
+
# @raise [NoMethodError]
|
439
|
+
# If {#has_from_data?} returns `false`.
|
440
|
+
#
|
441
|
+
# @raise [NRSER::Types::CheckError]
|
442
|
+
# If the load result does not satisfy the type (see {#check!}).
|
443
|
+
#
|
444
|
+
def from_data data
|
445
|
+
unless has_from_data?
|
446
|
+
raise NoMethodError, "#from_data not defined"
|
341
447
|
end
|
342
448
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
# ------------------------------------------------------------------------
|
348
|
-
|
349
|
-
# Test if the type has custom information about how to convert it's values
|
350
|
-
# into "data" - structures and values suitable for transportation and
|
351
|
-
# storage (JSON, etc.).
|
352
|
-
#
|
353
|
-
# If this method returns `true` then {#to_data} should succeed.
|
354
|
-
#
|
355
|
-
# @return [Boolean]
|
356
|
-
#
|
357
|
-
def has_to_data?
|
358
|
-
! @to_data.nil?
|
359
|
-
end # #has_to_data?
|
360
|
-
|
361
|
-
|
362
|
-
# Dumps a value of this type to "data" - structures and values suitable
|
363
|
-
# for transport and storage, such as dumping to JSON or YAML, etc.
|
364
|
-
#
|
365
|
-
# @param [Object] value
|
366
|
-
# Value of this type (though it is *not* checked).
|
367
|
-
#
|
368
|
-
# @return [Object]
|
369
|
-
# The data representation of the value.
|
370
|
-
#
|
371
|
-
def to_data value
|
372
|
-
if @to_data.nil?
|
373
|
-
raise NoMethodError, "#to_data not defined"
|
374
|
-
end
|
375
|
-
|
376
|
-
@to_data.call value
|
377
|
-
end # #to_data
|
378
|
-
|
379
|
-
# @!endgroup Dumping Values Instance Methods # ***************************
|
380
|
-
|
381
|
-
|
382
|
-
# @!group Language Integration Instance Methods
|
383
|
-
# ------------------------------------------------------------------------
|
384
|
-
|
385
|
-
# Proxies to {#name}.
|
386
|
-
#
|
387
|
-
# @return [String]
|
388
|
-
#
|
389
|
-
def to_s; name; end
|
390
|
-
|
391
|
-
|
392
|
-
# Hook into Ruby's *case subsumption* operator to allow usage in `case`
|
393
|
-
# statements! Forwards to {#test?}.
|
394
|
-
#
|
395
|
-
# @param value (see #test?)
|
396
|
-
# @return (see #test?)
|
397
|
-
#
|
398
|
-
def === value
|
399
|
-
test? value
|
449
|
+
value = if @from_data
|
450
|
+
@from_data.call data
|
451
|
+
else
|
452
|
+
custom_from_data data
|
400
453
|
end
|
401
454
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
end # #respond_to?
|
437
|
-
|
438
|
-
|
439
|
-
### Inspecting
|
440
|
-
#
|
441
|
-
# Due to their combinatoric nature, types can quickly become large data
|
442
|
-
# hierarchies, and the built-in {#inspect} will produce a massive dump
|
443
|
-
# that's distracting and hard to decipher.
|
444
|
-
#
|
445
|
-
# {#inspect} is readily used in tools like `pry` and `rspec`, significantly
|
446
|
-
# impacting their usefulness when working with types.
|
447
|
-
#
|
448
|
-
# As a solution, we alias the built-in `#inspect` as {#builtin_inspect},
|
449
|
-
# so it's available in situations where you really want all those gory
|
450
|
-
# details, and point {#inspect} to {#explain}.
|
451
|
-
#
|
452
|
-
|
453
|
-
alias_method :builtin_inspect, :inspect
|
454
|
-
def inspect
|
455
|
-
name = self.name
|
456
|
-
explain = self.explain
|
457
|
-
|
458
|
-
if name == explain
|
459
|
-
explain
|
460
|
-
else
|
461
|
-
"#{ name } := #{ explain }"
|
462
|
-
end
|
455
|
+
check! value
|
456
|
+
end
|
457
|
+
|
458
|
+
# @!endgroup Loading Values Instance Methods # ***************************
|
459
|
+
|
460
|
+
|
461
|
+
# @!group Dumping Values Instance Methods
|
462
|
+
# ------------------------------------------------------------------------
|
463
|
+
|
464
|
+
# Test if the type has custom information about how to convert it's values
|
465
|
+
# into "data" - structures and values suitable for transportation and
|
466
|
+
# storage (JSON, etc.).
|
467
|
+
#
|
468
|
+
# If this method returns `true` then {#to_data} should succeed.
|
469
|
+
#
|
470
|
+
# @return [Boolean]
|
471
|
+
#
|
472
|
+
def has_to_data?
|
473
|
+
! @to_data.nil?
|
474
|
+
end # #has_to_data?
|
475
|
+
|
476
|
+
|
477
|
+
# Dumps a value of this type to "data" - structures and values suitable
|
478
|
+
# for transport and storage, such as dumping to JSON or YAML, etc.
|
479
|
+
#
|
480
|
+
# @param [Object] value
|
481
|
+
# Value of this type (though it is *not* checked).
|
482
|
+
#
|
483
|
+
# @return [Object]
|
484
|
+
# The data representation of the value.
|
485
|
+
#
|
486
|
+
def to_data value
|
487
|
+
if @to_data.nil?
|
488
|
+
raise NoMethodError, "#to_data not defined"
|
463
489
|
end
|
464
490
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
#
|
518
|
-
def xor *others
|
519
|
-
require_relative './combinators'
|
520
|
-
|
521
|
-
NRSER::Types.xor self, *others
|
522
|
-
end # #^
|
523
|
-
|
524
|
-
alias_method :^, :xor
|
525
|
-
|
526
|
-
|
527
|
-
# Return a "negation" type satisfied by all values that do *not* satisfy
|
528
|
-
# `self`.
|
529
|
-
#
|
530
|
-
# @return [NRSER::Types::Not]
|
531
|
-
#
|
532
|
-
def not
|
533
|
-
require_relative './not'
|
534
|
-
|
535
|
-
NRSER::Types.not self
|
491
|
+
@to_data.call value
|
492
|
+
end # #to_data
|
493
|
+
|
494
|
+
# @!endgroup Dumping Values Instance Methods # ***************************
|
495
|
+
|
496
|
+
|
497
|
+
# @!group Language Integration Instance Methods
|
498
|
+
# ------------------------------------------------------------------------
|
499
|
+
|
500
|
+
# Hook into Ruby's *case subsumption* operator to allow usage in `case`
|
501
|
+
# statements! Forwards to {#test?}.
|
502
|
+
#
|
503
|
+
# @param value (see #test?)
|
504
|
+
# @return (see #test?)
|
505
|
+
#
|
506
|
+
def === value
|
507
|
+
test? value
|
508
|
+
end
|
509
|
+
|
510
|
+
|
511
|
+
# Overridden to customize behavior for the {#from_s}, {#from_data} and
|
512
|
+
# {#to_data} methods - those methods are always defined, but we have
|
513
|
+
# {#respond_to?} return `false` if they lack the underlying instance
|
514
|
+
# variables needed to execute.
|
515
|
+
#
|
516
|
+
# @example
|
517
|
+
# t1 = t.where { |value| true }
|
518
|
+
# t1.respond_to? :from_s
|
519
|
+
# # => false
|
520
|
+
#
|
521
|
+
# t2 = t.where( from_s: ->(s){ s.split ',' } ) { |value| true }
|
522
|
+
# t2.respond_to? :from_s
|
523
|
+
# # => true
|
524
|
+
#
|
525
|
+
# @param [Symbol | String] name
|
526
|
+
# Method name to ask about.
|
527
|
+
#
|
528
|
+
# @param [Boolean] include_all
|
529
|
+
# IDK, part of Ruby API that is passed up to `super`.
|
530
|
+
#
|
531
|
+
# @return [Boolean]
|
532
|
+
#
|
533
|
+
def respond_to? name, include_all = false
|
534
|
+
case name.to_sym
|
535
|
+
when :from_s
|
536
|
+
has_from_s?
|
537
|
+
when :from_data
|
538
|
+
has_from_data?
|
539
|
+
when :to_data
|
540
|
+
has_to_data?
|
541
|
+
else
|
542
|
+
super name, include_all
|
536
543
|
end
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
544
|
+
end # #respond_to?
|
545
|
+
|
546
|
+
|
547
|
+
# This small method is extremely useful - it lets you easily use types to
|
548
|
+
# in {Enumerable#select} and similar by returning a {Proc} that runs
|
549
|
+
# {#test?} on the values it receives.
|
550
|
+
#
|
551
|
+
# @example Select all non-empty strings and positive integers from an enum
|
552
|
+
# enum.select &(non_empty_str | pos_int)
|
553
|
+
#
|
554
|
+
# Pretty cool, right?
|
555
|
+
#
|
556
|
+
# @return [Proc<(Object)=>Boolean>]
|
557
|
+
#
|
558
|
+
def to_proc
|
559
|
+
->( value ) { test? value }
|
560
|
+
end
|
561
|
+
|
562
|
+
# @!endgroup Language Integration Instance Methods # *********************
|
563
|
+
|
564
|
+
|
565
|
+
# @!group Derivation Instance Methods
|
566
|
+
# ------------------------------------------------------------------------
|
567
|
+
#
|
568
|
+
# Methods for deriving new types from `self`.
|
569
|
+
#
|
570
|
+
|
571
|
+
# Return a *union* type satisfied by values that satisfy either `self`
|
572
|
+
# *or* and of `others`.
|
573
|
+
#
|
574
|
+
# @param [Array<TYPE>] others
|
575
|
+
# Values passed through {.make} to create the other types.
|
576
|
+
#
|
577
|
+
# @return [Union]
|
578
|
+
#
|
579
|
+
def union *others
|
580
|
+
require_relative './combinators'
|
581
|
+
|
582
|
+
NRSER::Types.union self, *others
|
583
|
+
end # #union
|
584
|
+
|
585
|
+
alias_method :|, :union
|
586
|
+
alias_method :or, :union
|
587
|
+
|
588
|
+
|
589
|
+
# Return an *intersection* type satisfied by values that satisfy both
|
590
|
+
# `self` *and* all of `others`.
|
591
|
+
#
|
592
|
+
# @param [Array] others
|
593
|
+
# Values passed through {NRSER::Types.make} to create the other types.
|
594
|
+
#
|
595
|
+
# @return [NRSER::Types::Intersection]
|
596
|
+
#
|
597
|
+
def intersection *others
|
598
|
+
require_relative './combinators'
|
599
|
+
|
600
|
+
NRSER::Types.intersection self, *others
|
601
|
+
end # #intersection
|
602
|
+
|
603
|
+
alias_method :&, :intersection
|
604
|
+
alias_method :and, :intersection
|
605
|
+
|
606
|
+
|
607
|
+
# Return an *exclusive or* type satisfied by values that satisfy either
|
608
|
+
# `self` *or* `other` *but not both*.
|
609
|
+
#
|
610
|
+
# @param [Array<TYPE>] others
|
611
|
+
# Value passed through {NRSER::Types.make} to create the other type.
|
612
|
+
#
|
613
|
+
# @return [NRSER::Types::Intersection]
|
614
|
+
#
|
615
|
+
def xor *others
|
616
|
+
require_relative './combinators'
|
617
|
+
|
618
|
+
NRSER::Types.xor self, *others
|
619
|
+
end # #^
|
620
|
+
|
621
|
+
alias_method :^, :xor
|
622
|
+
|
623
|
+
|
624
|
+
# Return a "negation" type satisfied by all values that do *not* satisfy
|
625
|
+
# `self`.
|
626
|
+
#
|
627
|
+
# @return [NRSER::Types::Not]
|
628
|
+
#
|
629
|
+
def not
|
630
|
+
require_relative './not'
|
631
|
+
|
632
|
+
NRSER::Types.not self
|
633
|
+
end
|
634
|
+
|
635
|
+
alias_method :~, :not
|
636
|
+
|
637
|
+
# NO NO NOOOOO, we don't want this shit... I for some incredibly stupid idea
|
638
|
+
# thought using unary ! to negate types was reasonable. And then at some
|
639
|
+
# point I suddenly realized it's *terrible* idea.
|
640
|
+
#
|
641
|
+
# I should **not** fuck with boolean negation. I don't think anyone ever
|
642
|
+
# should. It's *way* to built-in of an assumption that `!x -> true` if and
|
643
|
+
# **only if** `x` is `nil` or `false`. And it should stay that way.
|
644
|
+
#
|
645
|
+
# alias_method :!, :not
|
646
|
+
|
647
|
+
# @!endgroup Derivation Instance Methods # *******************************
|
648
|
+
|
649
|
+
end # class Type
|
650
|
+
|
651
|
+
|
652
|
+
# /Namespace
|
653
|
+
# ========================================================================
|
654
|
+
|
655
|
+
end # module Types
|
656
|
+
end # module NRSER
|