nrser 0.3.9 → 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nrser/char/alpha_numeric_sub.rb +9 -19
  3. data/lib/nrser/char/special.rb +5 -5
  4. data/lib/nrser/core_ext/array.rb +36 -13
  5. data/lib/nrser/core_ext/enumerable.rb +1 -0
  6. data/lib/nrser/core_ext/enumerable/find_map.rb +1 -1
  7. data/lib/nrser/core_ext/hash/bury.rb +3 -0
  8. data/lib/nrser/core_ext/hash/extract_values_at.rb +2 -2
  9. data/lib/nrser/core_ext/method/full_name.rb +1 -1
  10. data/lib/nrser/core_ext/module/method_objects.rb +1 -1
  11. data/lib/nrser/core_ext/module/source_locations.rb +27 -15
  12. data/lib/nrser/core_ext/object/lazy_var.rb +1 -1
  13. data/lib/nrser/core_ext/pathname.rb +67 -12
  14. data/lib/nrser/core_ext/pathname/subpath.rb +86 -0
  15. data/lib/nrser/core_ext/string.rb +28 -1
  16. data/lib/nrser/core_ext/symbol.rb +11 -12
  17. data/lib/nrser/errors/README.md +154 -0
  18. data/lib/nrser/errors/attr_error.rb +146 -53
  19. data/lib/nrser/errors/count_error.rb +61 -12
  20. data/lib/nrser/errors/nicer_error.rb +42 -71
  21. data/lib/nrser/errors/value_error.rb +53 -58
  22. data/lib/nrser/functions.rb +0 -2
  23. data/lib/nrser/functions/enumerable.rb +5 -17
  24. data/lib/nrser/functions/enumerable/associate.rb +14 -5
  25. data/lib/nrser/functions/enumerable/find_all_map.rb +1 -1
  26. data/lib/nrser/functions/enumerable/include_slice/array_include_slice.rb +1 -1
  27. data/lib/nrser/functions/hash/bury.rb +2 -12
  28. data/lib/nrser/functions/merge_by.rb +2 -2
  29. data/lib/nrser/functions/module/method_objects.rb +2 -2
  30. data/lib/nrser/functions/path.rb +185 -165
  31. data/lib/nrser/functions/path/normalized.rb +84 -0
  32. data/lib/nrser/functions/string.rb +4 -4
  33. data/lib/nrser/functions/text/README.md +4 -0
  34. data/lib/nrser/functions/text/format.rb +53 -0
  35. data/lib/nrser/functions/text/indentation.rb +6 -6
  36. data/lib/nrser/functions/text/word_wrap.rb +2 -2
  37. data/lib/nrser/functions/tree/map_leaves.rb +3 -3
  38. data/lib/nrser/functions/tree/map_tree.rb +2 -2
  39. data/lib/nrser/functions/tree/transform.rb +1 -18
  40. data/lib/nrser/gem_ext/README.md +4 -0
  41. data/lib/nrser/labs/README.md +8 -0
  42. data/lib/nrser/labs/config.rb +163 -0
  43. data/lib/nrser/labs/i8.rb +49 -159
  44. data/lib/nrser/labs/i8/struct.rb +167 -0
  45. data/lib/nrser/labs/i8/struct/hash.rb +140 -0
  46. data/lib/nrser/labs/i8/struct/vector.rb +149 -0
  47. data/lib/nrser/labs/i8/surjection.rb +211 -0
  48. data/lib/nrser/labs/lots/consumer.rb +19 -0
  49. data/lib/nrser/labs/lots/parser.rb +21 -1
  50. data/lib/nrser/labs/stash.rb +4 -4
  51. data/lib/nrser/log.rb +25 -21
  52. data/lib/nrser/log/appender/sync.rb +15 -11
  53. data/lib/nrser/log/formatters/color.rb +0 -3
  54. data/lib/nrser/log/formatters/mixin.rb +4 -4
  55. data/lib/nrser/log/logger.rb +54 -6
  56. data/lib/nrser/log/mixin.rb +2 -1
  57. data/lib/nrser/log/plugin.rb +6 -6
  58. data/lib/nrser/log/types.rb +46 -29
  59. data/lib/nrser/mean_streak.rb +0 -8
  60. data/lib/nrser/mean_streak/document.rb +1 -4
  61. data/lib/nrser/message.rb +3 -3
  62. data/lib/nrser/meta/README.md +4 -0
  63. data/lib/nrser/meta/lazy_attr.rb +2 -2
  64. data/lib/nrser/meta/source/location.rb +1 -1
  65. data/lib/nrser/props.rb +34 -3
  66. data/lib/nrser/props/class_methods.rb +2 -1
  67. data/lib/nrser/props/instance_methods.rb +9 -9
  68. data/lib/nrser/props/metadata.rb +4 -12
  69. data/lib/nrser/props/mutable/stash.rb +5 -2
  70. data/lib/nrser/props/prop.rb +10 -19
  71. data/lib/nrser/rspex.rb +1 -20
  72. data/lib/nrser/rspex/example_group/describe_attribute.rb +3 -0
  73. data/lib/nrser/rspex/example_group/describe_called_with.rb +9 -4
  74. data/lib/nrser/rspex/example_group/describe_case.rb +1 -0
  75. data/lib/nrser/rspex/example_group/describe_class.rb +2 -0
  76. data/lib/nrser/rspex/example_group/describe_group.rb +1 -1
  77. data/lib/nrser/rspex/example_group/describe_instance.rb +3 -1
  78. data/lib/nrser/rspex/example_group/describe_message.rb +1 -1
  79. data/lib/nrser/rspex/example_group/describe_method.rb +64 -30
  80. data/lib/nrser/rspex/example_group/describe_response_to.rb +1 -1
  81. data/lib/nrser/rspex/example_group/describe_section.rb +4 -1
  82. data/lib/nrser/rspex/example_group/describe_sent_to.rb +1 -1
  83. data/lib/nrser/rspex/example_group/describe_setup.rb +1 -0
  84. data/lib/nrser/rspex/example_group/describe_source_file.rb +1 -1
  85. data/lib/nrser/rspex/example_group/describe_spec_file.rb +4 -2
  86. data/lib/nrser/rspex/example_group/describe_when.rb +2 -1
  87. data/lib/nrser/rspex/example_group/describe_x.rb +5 -5
  88. data/lib/nrser/rspex/format.rb +0 -15
  89. data/lib/nrser/sugar/method_missing_forwarder.rb +3 -3
  90. data/lib/nrser/sys/env/path.rb +2 -28
  91. data/lib/nrser/types.rb +63 -12
  92. data/lib/nrser/types/README.md +76 -0
  93. data/lib/nrser/types/arrays.rb +192 -137
  94. data/lib/nrser/types/attributes.rb +269 -0
  95. data/lib/nrser/types/booleans.rb +134 -83
  96. data/lib/nrser/types/bounded.rb +110 -47
  97. data/lib/nrser/types/collections.rb +119 -0
  98. data/lib/nrser/types/combinators.rb +283 -196
  99. data/lib/nrser/types/doc/display_table.md +66 -0
  100. data/lib/nrser/types/eqiuvalent.rb +91 -0
  101. data/lib/nrser/types/errors/check_error.rb +5 -11
  102. data/lib/nrser/types/errors/from_string_error.rb +3 -3
  103. data/lib/nrser/types/factory.rb +287 -20
  104. data/lib/nrser/types/hashes.rb +227 -179
  105. data/lib/nrser/types/in.rb +73 -36
  106. data/lib/nrser/types/is.rb +67 -60
  107. data/lib/nrser/types/is_a.rb +141 -84
  108. data/lib/nrser/types/labels.rb +45 -16
  109. data/lib/nrser/types/maybe.rb +6 -3
  110. data/lib/nrser/types/nil.rb +64 -27
  111. data/lib/nrser/types/not.rb +92 -34
  112. data/lib/nrser/types/numbers.rb +224 -169
  113. data/lib/nrser/types/pairs.rb +113 -89
  114. data/lib/nrser/types/paths.rb +250 -137
  115. data/lib/nrser/types/responds.rb +167 -89
  116. data/lib/nrser/types/selector.rb +234 -0
  117. data/lib/nrser/types/shape.rb +136 -65
  118. data/lib/nrser/types/strings.rb +189 -63
  119. data/lib/nrser/types/symbols.rb +83 -33
  120. data/lib/nrser/types/top.rb +89 -0
  121. data/lib/nrser/types/tuples.rb +134 -98
  122. data/lib/nrser/types/type.rb +617 -505
  123. data/lib/nrser/types/when.rb +123 -98
  124. data/lib/nrser/types/where.rb +182 -91
  125. data/lib/nrser/version.rb +1 -1
  126. data/spec/lib/nrser/core_ext/pathname/subpath_spec.rb +22 -0
  127. data/spec/lib/nrser/errors/attr_error_spec.rb +68 -0
  128. data/spec/lib/nrser/errors/count_error_spec.rb +69 -0
  129. data/spec/lib/nrser/functions/path/normalize_path_spec.rb +35 -0
  130. data/spec/lib/nrser/functions/tree/map_tree_spec.rb +74 -96
  131. data/spec/lib/nrser/functions/tree/transform_spec.rb +11 -11
  132. data/spec/lib/nrser/labs/config_spec.rb +22 -0
  133. data/spec/lib/nrser/labs/i8/struct_spec.rb +39 -0
  134. data/spec/lib/nrser/types/display_spec.rb +50 -0
  135. data/spec/lib/nrser/types/paths_spec.rb +16 -10
  136. data/spec/lib/nrser/types/selector_spec.rb +125 -0
  137. data/spec/spec_helper.rb +4 -5
  138. metadata +105 -22
  139. data/lib/nrser/types/any.rb +0 -41
  140. data/lib/nrser/types/attrs.rb +0 -213
  141. data/lib/nrser/types/trees.rb +0 -42
@@ -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
- module NRSER::Types
25
- class Type
26
-
27
- # Constructor
28
- # =====================================================================
29
-
30
- # Instantiate a new `NRSER::Types::Type`.
31
- #
32
- # @param [nil | String] name:
33
- # Name that will be used when displaying the type, or `nil` to use a
34
- # default generated name.
35
- #
36
- # @param [nil | #call] from_s:
37
- # Callable that will be passed a {String} and should return an object
38
- # that satisfies the type if it possible to create one.
39
- #
40
- # The returned value *will* be checked against the type, so returning a
41
- # value that doesn't satisfy will result in a {TypeError} being raised
42
- # by {#from_s}.
43
- #
44
- # @param [nil | #call | #to_proc] to_data:
45
- # Optional callable (or object that responds to `#to_proc` so we can
46
- # get a callable) to call to turn type members into "data".
47
- #
48
- def initialize name: nil, from_s: nil, to_data: nil, from_data: nil
49
- @name = name
50
- @from_s = from_s
51
-
52
- @to_data = if to_data.nil?
53
- nil
54
- elsif to_data.respond_to?( :call )
55
- to_data
56
- elsif to_data.respond_to?( :to_proc )
57
- to_data.to_proc
58
- else
59
- raise TypeError.new binding.erb <<-ERB
60
- `to_data:` keyword arg must be `nil`, respond to `#call` or respond
61
- to `#to_proc`.
62
-
63
- Found value:
64
-
65
- <%= to_data.pretty_inspect %>
66
-
67
- (type <%= to_data.class %>)
68
-
69
- ERB
70
- end
71
-
72
- @from_data = if from_data.nil?
73
- nil
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
- # A string that gives our best concise description of the type's logic,
121
- # in particular exposing any composite types that it's made up of.
122
- #
123
- # Used as the {#name} when a custom one is not provided.
124
- #
125
- # Meant for inline display, so the result *should not* contain newlines.
126
- #
127
- # Realizing subclasses **should** override this method, as this
128
- # implementation only returns the class' name (and just the last segment,
129
- # for brevity's sake).
130
- #
131
- # @example Base implementation is not very interesting
132
- # MyType = Class.new NRSER::Types::Type
133
- # my_type = MyType.new
134
- # my_type.explain
135
- # # => "MyType"
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
- # @!endgroup Display Instance Methods # **********************************
144
-
145
-
146
- # @!group Validation Instance Methods
147
- # ------------------------------------------------------------------------
148
- #
149
- # The core of what a type does.
150
- #
151
-
152
- # See if a value satisfies the type.
153
- #
154
- # Realizing classes **must** implement this method.
155
- #
156
- # This implementation just defines the API; it always raises
157
- # {NRSER::AbstractMethodError}.
158
- #
159
- # @param [Object] value
160
- # Value to test for type satisfaction.
161
- #
162
- # @return [Boolean]
163
- # `true` if the `value` satisfies the type.
164
- #
165
- def test? value
166
- raise NRSER::AbstractMethodError.new( self, __method__ )
167
- end
168
-
169
- # Old name for {#test?}.
170
- #
171
- # @deprecated
172
- #
173
- # @param (see #test?)
174
- # @return (see #test?)
175
- # @raise (see #test?)
176
- #
177
- def test value; test? value; end
178
-
179
-
180
- # Check that a `value` satisfies the type.
181
- #
182
- # @see #test?
183
- #
184
- # @return [Object]
185
- # The value itself.
186
- #
187
- # @raise [NRSER::Types::CheckError]
188
- # If the value does not satisfy this type.
189
- #
190
- def check! value, &details
191
- # success case
192
- return value if test? value
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
- raise NRSER::Types::CheckError.new \
195
- value: value,
196
- type: self,
197
- details: details
213
+ s += " #{ ivars }" unless ivars.empty?
214
+ s += '>'
215
+ s
198
216
  end
199
-
200
- # Old name for {#check!} without the bang.
201
- def check *args, &block; check! *args, &block; end
202
-
203
- # @!endgroup Validation Instance Methods # *******************************
204
-
205
-
206
- # @!group Loading Values Instance Methods
207
- # ------------------------------------------------------------------------
208
- #
209
- # Types include facilities for loading values from representations and
210
- # encodings.
211
- #
212
- # This was initially driven by the desire to use types to
213
- # declare CLI parameter schemas, as way to dispatch with the often
214
- # limited and arbitrary support most "CLI frameworks" have for declaring
215
- # option types - things like "you can have an integer, and you can
216
- # have an array, but you can't have an array of integers".
217
- #
218
- # By using compossible types that can load values from strings we get a
219
- # system where you can easily declare whatever complex and granular types
220
- # you desire and have the machine automatically load and validate them,
221
- # as well as provide reasonable generated feedback when something doesn't
222
- # meet expectations, which has worked out quite well so far start to cut
223
- # down the amount of repetitive and error-prone "did I get what I need?
224
- # No, did I get exactly what I need?" bullshit in receiving data.
225
- #
226
- # This approach is now being expanded to "data" - an ill-formed concept
227
- # I've been brewing of "reasonable common and portable data
228
- # representation" and the {NRSER::Props} system, which has been coming
229
- # along as well.
230
- #
231
-
232
- # Test if the type knows how to load values from strings.
233
- #
234
- # Looks for the `@from_s` instance variable or a `#custom_from_s`
235
- # method.
236
- #
237
- # @note
238
- # When this method returns `true` it simply indicates that some method
239
- # of loading from strings exists - the load itself can of course still
240
- # fail.
241
- #
242
- # Realizing classes should only need to override this method to limited or
243
- # expand the scope relative to parameterized types.
244
- #
245
- # @return [Boolean]
246
- #
247
- def has_from_s?
248
- !@from_s.nil? ||
249
- # Need the `true` second arg to include protected methods
250
- respond_to?( :custom_from_s, true )
251
- end
252
-
253
-
254
- # Load a value of this type from a string representation by passing
255
- # `string` to the {@from_s} {Proc}.
256
- #
257
- # Checks the value {@from_s} returns with {#check!} before returning it, so
258
- # you know it satisfies this type.
259
- #
260
- # Realizing classes **should not** need to override this - they can define
261
- # a `#custom_from_s` instance method for it to use, allowing individual
262
- # types to still override that by providing a `from_s:` proc keyword
263
- # arg at construction. This also lets them avoid checking the returned
264
- # value, since we do so here.
265
- #
266
- # @param [String] string
267
- # String representation.
268
- #
269
- # @return [Object]
270
- # Value that has passed {#check!}.
271
- #
272
- # @raise [NoMethodError]
273
- # If this type doesn't know how to load values from strings.
274
- #
275
- # In basic types this happens when {NRSER::Types::Type#initialize} was
276
- # not provided a `from_s:` {Proc} argument.
277
- #
278
- # {NRSER::Types::Type} subclasses may override {#from_s} entirely,
279
- # divorcing it from the `from_s:` constructor argument and internal
280
- # {@from_s} instance variable (which is why {@from_s} is not publicly
281
- # exposed - it should not be assumed to dictate {#from_s} behavior
282
- # in general).
283
- #
284
- # @raise [TypeError]
285
- # If the value loaded does not pass {#check}.
286
- #
287
- def from_s string
288
- unless has_from_s?
289
- raise NoMethodError, "#from_s not defined for type #{ name }"
290
- end
291
-
292
- value = if @from_s
293
- @from_s.call string
294
- else
295
- custom_from_s string
296
- end
297
-
298
- check! value
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, &block; check! *args, &block; 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
- # Test if the type can load values from "data" - basic values and
303
- # collections like {Array} and {Hash} forming tree-like structures.
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
- # Try to load a value from "data" - basic values and
318
- # collections like {Array} and {Hash} forming tree-like structures.
319
- #
320
- # @param [*] data
321
- # Data to try to load from.
322
- #
323
- # @raise [NoMethodError]
324
- # If {#has_from_data?} returns `false`.
325
- #
326
- # @raise [NRSER::Types::CheckError]
327
- # If the load result does not satisfy the type (see {#check!}).
328
- #
329
- def from_data data
330
- unless has_from_data?
331
- raise NoMethodError, "#from_data not defined"
332
- end
333
-
334
- value = if @from_data
335
- @from_data.call data
336
- else
337
- custom_from_data data
338
- end
339
-
340
- check! value
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
- # @!endgroup Loading Values Instance Methods # ***************************
344
-
345
-
346
- # @!group Dumping Values Instance Methods
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
- # Overridden to customize behavior for the {#from_s}, {#from_data} and
404
- # {#to_data} methods - those methods are always defined, but we have
405
- # {#respond_to?} return `false` if they lack the underlying instance
406
- # variables needed to execute.
407
- #
408
- # @example
409
- # t1 = t.where { |value| true }
410
- # t1.respond_to? :from_s
411
- # # => false
412
- #
413
- # t2 = t.where( from_s: ->(s){ s.split ',' } ) { |value| true }
414
- # t2.respond_to? :from_s
415
- # # => true
416
- #
417
- # @param [Symbol | String] name
418
- # Method name to ask about.
419
- #
420
- # @param [Boolean] include_all
421
- # IDK, part of Ruby API that is passed up to `super`.
422
- #
423
- # @return [Boolean]
424
- #
425
- def respond_to? name, include_all = false
426
- case name.to_sym
427
- when :from_s
428
- has_from_s?
429
- when :from_data
430
- has_from_data?
431
- when :to_data
432
- has_to_data?
433
- else
434
- super name, include_all
435
- end
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
- # @!endgroup Language Integration Instance Methods # *********************
466
-
467
-
468
- # @!group Derivation Instance Methods
469
- # ------------------------------------------------------------------------
470
- #
471
- # Methods for deriving new types from `self`.
472
- #
473
-
474
- # Return a *union* type satisfied by values that satisfy either `self`
475
- # *or* and of `others`.
476
- #
477
- # @param [*] other
478
- # Values passed through {NRSER::Types.make} to create the other types.
479
- #
480
- # @return [NRSER::Types::Union]
481
- #
482
- def union *others
483
- require_relative './combinators'
484
-
485
- NRSER::Types.union self, *others
486
- end # #union
487
-
488
- alias_method :|, :union
489
- alias_method :or, :union
490
-
491
-
492
- # Return an *intersection* type satisfied by values that satisfy both
493
- # `self` *and* all of `others`.
494
- #
495
- # @param [Array] *others
496
- # Values passed through {NRSER::Types.make} to create the other types.
497
- #
498
- # @return [NRSER::Types::Intersection]
499
- #
500
- def intersection *others
501
- require_relative './combinators'
502
-
503
- NRSER::Types.intersection self, *others
504
- end # #intersection
505
-
506
- alias_method :&, :intersection
507
- alias_method :and, :intersection
508
-
509
-
510
- # Return an *exclusive or* type satisfied by values that satisfy either
511
- # `self` *or* `other` *but not both*.
512
- #
513
- # @param [*] other
514
- # Value passed through {NRSER::Types.make} to create the other type.
515
- #
516
- # @return [NRSER::Types::Intersection]
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
- alias_method :~, :not
539
-
540
- # @!endgroup Derivation Instance Methods # *******************************
541
-
542
-
543
- end # Type
544
- end # NRSER::Types
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