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.
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