nrser 0.1.1 → 0.1.2
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/env/path.rb +325 -0
- data/lib/nrser/env.rb +12 -0
- data/lib/nrser/errors/attr_error.rb +73 -0
- data/lib/nrser/errors/count_error.rb +19 -0
- data/lib/nrser/errors/nicer_error.rb +110 -0
- data/lib/nrser/errors/value_error.rb +72 -0
- data/lib/nrser/errors.rb +23 -6
- data/lib/nrser/functions/enumerable.rb +9 -2
- data/lib/nrser/functions/path.rb +3 -1
- data/lib/nrser/functions/string.rb +28 -11
- data/lib/nrser/functions/text/indentation.rb +16 -4
- data/lib/nrser/mean_streak/document.rb +151 -0
- data/lib/nrser/mean_streak.rb +95 -0
- data/lib/nrser/refinements/array.rb +6 -0
- data/lib/nrser/refinements/string.rb +6 -0
- data/lib/nrser/rspex/example_group/describe_instance.rb +24 -0
- data/lib/nrser/rspex/example_group/describe_instance_method.rb +20 -0
- data/lib/nrser/rspex/example_group/describe_setup.rb +22 -0
- data/lib/nrser/rspex/example_group/describe_spec_file.rb +127 -0
- data/lib/nrser/rspex/example_group/describe_use_case.rb +18 -0
- data/lib/nrser/rspex/example_group/describe_when.rb +25 -0
- data/lib/nrser/rspex/example_group/describe_x.rb +100 -0
- data/lib/nrser/rspex/example_group.rb +270 -0
- data/lib/nrser/rspex/format.rb +174 -0
- data/lib/nrser/rspex.rb +31 -395
- data/lib/nrser/types/paths.rb +2 -3
- data/lib/nrser/types.rb +5 -1
- data/lib/nrser/version.rb +1 -1
- data/lib/nrser.rb +3 -1
- data/spec/nrser/env/path/insert_spec.rb +65 -0
- data/spec/nrser/env/path_spec.rb +12 -0
- metadata +99 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 736ee433c6078e250b89ebbc65455ff157888b6e
|
4
|
+
data.tar.gz: a99e836e91a873ea4b06767376a6c38e30c2fe5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffed557cf2652d5d9b9af71c4eb65cda37a0a165cacb7f419d9e6717cbb810ab224e3a6710e6e762c72948707e7424180faafd6acd2ab3a310aa2a326422d794
|
7
|
+
data.tar.gz: 3b5307fc1dca415cb54a56ed32799b472c625c8a8d33d2dcef98e74e6ae85e21916cf2df5a211de3c4251c5e5828e5cb842942cf490cd10da8c5ad535ad8693b
|
@@ -0,0 +1,325 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Requirements
|
4
|
+
# =======================================================================
|
5
|
+
|
6
|
+
# Stdlib
|
7
|
+
# -----------------------------------------------------------------------
|
8
|
+
|
9
|
+
# Deps
|
10
|
+
# -----------------------------------------------------------------------
|
11
|
+
|
12
|
+
# Project / Package
|
13
|
+
# -----------------------------------------------------------------------
|
14
|
+
|
15
|
+
|
16
|
+
# Refinements
|
17
|
+
# =======================================================================
|
18
|
+
|
19
|
+
using NRSER
|
20
|
+
|
21
|
+
|
22
|
+
# Declarations
|
23
|
+
# =======================================================================
|
24
|
+
|
25
|
+
|
26
|
+
# Definitions
|
27
|
+
# =======================================================================
|
28
|
+
|
29
|
+
# @todo document NRSER::Env::Path class.
|
30
|
+
class NRSER::Env::Path
|
31
|
+
include Enumerable
|
32
|
+
include NRSER::Ext::Enumerable
|
33
|
+
|
34
|
+
# Constants
|
35
|
+
# ======================================================================
|
36
|
+
|
37
|
+
# Character used to separate path entries in string format.
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
#
|
41
|
+
SEPARATOR = ':'
|
42
|
+
|
43
|
+
|
44
|
+
# Class Methods
|
45
|
+
# ======================================================================
|
46
|
+
|
47
|
+
# @todo Document normalize method.
|
48
|
+
#
|
49
|
+
# @param [nil | String | #each_index] source
|
50
|
+
# Path source.
|
51
|
+
#
|
52
|
+
# @return [return_type]
|
53
|
+
# @todo Document return value.
|
54
|
+
#
|
55
|
+
def self.normalize source
|
56
|
+
paths = if source.nil?
|
57
|
+
[]
|
58
|
+
|
59
|
+
elsif source.is_a?( String )
|
60
|
+
source.to_s.split SEPARATOR
|
61
|
+
|
62
|
+
elsif NRSER.array_like?( source )
|
63
|
+
# Flatten it if supported
|
64
|
+
source = source.flatten if source.respond_to?( :flatten )
|
65
|
+
|
66
|
+
# Stringify each segment, split them and concat results
|
67
|
+
source.flat_map { |entry| entry.to_s.split SEPARATOR }
|
68
|
+
|
69
|
+
else
|
70
|
+
raise ArgumentError.new binding.erb <<-END
|
71
|
+
Expected a string or an "array-like" source, found:
|
72
|
+
|
73
|
+
<%= source.pretty_inspect %>
|
74
|
+
|
75
|
+
END
|
76
|
+
end
|
77
|
+
|
78
|
+
Hamster::Vector.new paths.
|
79
|
+
# Get rid of empty paths
|
80
|
+
reject( &:empty? ).
|
81
|
+
# Get rid of duplicates
|
82
|
+
uniq.
|
83
|
+
# Freeze all the strings
|
84
|
+
map( &:freeze )
|
85
|
+
end # .normalize
|
86
|
+
|
87
|
+
|
88
|
+
# See if a `path` matches any of `patterns`.
|
89
|
+
#
|
90
|
+
# Short-circuits as soon as a match is found (so patterns may not all be
|
91
|
+
# tested).
|
92
|
+
#
|
93
|
+
# @param [String] path
|
94
|
+
# Path to test against.
|
95
|
+
#
|
96
|
+
# @param [Array<String | Proc<String=>Boolean> | Regexp>] *patterns
|
97
|
+
# Patterns to test:
|
98
|
+
#
|
99
|
+
# - `String` - test if it and `path` are equal (`==`)
|
100
|
+
#
|
101
|
+
# - `Proc<String=>Boolean>` - call with `path` and evaluate result as
|
102
|
+
# Boolean.
|
103
|
+
#
|
104
|
+
# - `Regexp` - test if it matches `path` (`=~`)
|
105
|
+
#
|
106
|
+
# @return [Boolean]
|
107
|
+
# `true` if *any* of `patterns` match `path`.
|
108
|
+
#
|
109
|
+
def self.matches_pattern? path, *patterns
|
110
|
+
patterns.any? do |pattern|
|
111
|
+
case pattern
|
112
|
+
when String
|
113
|
+
path == pattern
|
114
|
+
when Proc
|
115
|
+
pattern.call path
|
116
|
+
when Regexp
|
117
|
+
path =~ pattern
|
118
|
+
else
|
119
|
+
raise TypeError.new binding.erb <<-END
|
120
|
+
Each `*patterns` arg should be String, Proc or Regexp, found:
|
121
|
+
|
122
|
+
<%= pattern.pretty_inspect %>
|
123
|
+
|
124
|
+
END
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end # .matches_pattern?
|
128
|
+
|
129
|
+
|
130
|
+
# @todo Document from_env method.
|
131
|
+
#
|
132
|
+
# @param [type] arg_name
|
133
|
+
# @todo Add name param description.
|
134
|
+
#
|
135
|
+
# @return [return_type]
|
136
|
+
# @todo Document return value.
|
137
|
+
#
|
138
|
+
def self.from_ENV env_key
|
139
|
+
new ENV[env_key.to_s], env_key: env_key
|
140
|
+
end # .from_env
|
141
|
+
|
142
|
+
|
143
|
+
# Attributes
|
144
|
+
# ======================================================================
|
145
|
+
|
146
|
+
# Key for the value in `ENV` that the object represents. This is set when
|
147
|
+
# loading from `ENV` and used to know where to write when saving back to it.
|
148
|
+
#
|
149
|
+
# @return [nil | String]
|
150
|
+
#
|
151
|
+
attr_reader :env_key
|
152
|
+
|
153
|
+
|
154
|
+
# The object that was originally provided at construction.
|
155
|
+
#
|
156
|
+
# @return [nil | String | #each_index]
|
157
|
+
#
|
158
|
+
attr_reader :source
|
159
|
+
|
160
|
+
|
161
|
+
# The actual internal list of paths.
|
162
|
+
#
|
163
|
+
# @return [Hamster::Vector<String>]
|
164
|
+
#
|
165
|
+
attr_reader :value
|
166
|
+
|
167
|
+
|
168
|
+
# Constructor
|
169
|
+
# ======================================================================
|
170
|
+
|
171
|
+
# Instantiate a new `NRSER::Env::Path`.
|
172
|
+
def initialize source, env_key: nil
|
173
|
+
@env_key = env_key.to_s.freeze
|
174
|
+
@source = source.dup.freeze
|
175
|
+
@value = self.class.normalize source
|
176
|
+
end # #initialize
|
177
|
+
|
178
|
+
|
179
|
+
# Instance Methods
|
180
|
+
# ======================================================================
|
181
|
+
|
182
|
+
#
|
183
|
+
# @param source (see .normalize)
|
184
|
+
# @return [self]
|
185
|
+
#
|
186
|
+
def prepend source
|
187
|
+
# Normalize the new source to a flat array of strings
|
188
|
+
paths = self.class.normalize source
|
189
|
+
|
190
|
+
# The new value is the normalized paths followed by the current paths
|
191
|
+
# with the new ones removed (de-duplication)
|
192
|
+
@value = (paths + @value).uniq
|
193
|
+
|
194
|
+
# Return self for chain-ability
|
195
|
+
self
|
196
|
+
end
|
197
|
+
|
198
|
+
alias_method :unshift, :prepend
|
199
|
+
alias_method :>>, :prepend
|
200
|
+
|
201
|
+
|
202
|
+
#
|
203
|
+
# @param source (see .normalize)
|
204
|
+
# @return [self]
|
205
|
+
#
|
206
|
+
def append source
|
207
|
+
# Normalize the new source to a flat array of strings
|
208
|
+
paths = self.class.normalize source
|
209
|
+
|
210
|
+
# The new value is the current paths with the new paths appended, with
|
211
|
+
# any paths in the current path removed from the new ones (de-duplication)
|
212
|
+
@value = (@value + paths).uniq
|
213
|
+
|
214
|
+
# Return self for chain-ability
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
alias_method :push, :prepend
|
219
|
+
alias_method :<<, :prepend
|
220
|
+
|
221
|
+
|
222
|
+
def insert source, before: nil, after: nil
|
223
|
+
paths = self.class.normalize source
|
224
|
+
|
225
|
+
before_index = if before
|
226
|
+
@value.find_index do |path|
|
227
|
+
self.class.matches_pattern? path, *before
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
after_index = if after
|
232
|
+
index = @value.rindex { |path| self.class.matches_pattern? path, *after }
|
233
|
+
index += 1 if index
|
234
|
+
end
|
235
|
+
|
236
|
+
insert_index = if after_index && before_index
|
237
|
+
# Make sure the conditions don't conflict with each other
|
238
|
+
if after_index > before_index
|
239
|
+
raise "Conflicting bounds!"
|
240
|
+
end
|
241
|
+
|
242
|
+
# Insert as far "down" the path as allowed
|
243
|
+
[before_index, after_index].max
|
244
|
+
else
|
245
|
+
# Use the one that is not `nil`, or insert at the end if they both are
|
246
|
+
before_index || after_index || @value.length
|
247
|
+
end
|
248
|
+
|
249
|
+
@value = @value.insert( insert_index, *paths ).uniq
|
250
|
+
|
251
|
+
self
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
# Language Interop
|
256
|
+
# ============================================================================
|
257
|
+
|
258
|
+
# Support for {Enumerable} mixin. Yields each path in order.
|
259
|
+
#
|
260
|
+
# Specifically, proxies to {Hamster::Vector#each}
|
261
|
+
#
|
262
|
+
# @see http://www.rubydoc.info/gems/hamster/Hamster/Vector#each-instance_method
|
263
|
+
#
|
264
|
+
# @param [nil | Proc<(String)=>*>] &block
|
265
|
+
# When present, block will be called once for each string path in this
|
266
|
+
# object. First path is most prominent, down to least last.
|
267
|
+
#
|
268
|
+
# @return [self]
|
269
|
+
# When `&block` is provided.
|
270
|
+
#
|
271
|
+
# @return [Enumerator]
|
272
|
+
# When `&block` is omitted.
|
273
|
+
#
|
274
|
+
def each &block
|
275
|
+
if block
|
276
|
+
# Proxy for yielding
|
277
|
+
@value.each &block
|
278
|
+
|
279
|
+
# Return self for chain-ability
|
280
|
+
self
|
281
|
+
else
|
282
|
+
# Return the {Enumerator} from the vector
|
283
|
+
@value.each
|
284
|
+
end
|
285
|
+
end # #each
|
286
|
+
|
287
|
+
|
288
|
+
# The paths joined with ':'.
|
289
|
+
#
|
290
|
+
# @return [String]
|
291
|
+
#
|
292
|
+
def to_s
|
293
|
+
@value.join SEPARATOR
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
# The string paths in a new stdlib `Array`. Mutating this array will have no
|
298
|
+
# effect on the {NRSER::Env::Path} data.
|
299
|
+
#
|
300
|
+
# @return [Array<String>]
|
301
|
+
#
|
302
|
+
def to_a
|
303
|
+
@value.to_a
|
304
|
+
end
|
305
|
+
|
306
|
+
protected
|
307
|
+
# ========================================================================
|
308
|
+
|
309
|
+
# Internal method to remove paths that have already been normalized.
|
310
|
+
#
|
311
|
+
# @param [type] arg_name
|
312
|
+
# @todo Add name param description.
|
313
|
+
#
|
314
|
+
# @return [return_type]
|
315
|
+
# @todo Document return value.
|
316
|
+
#
|
317
|
+
def remove_paths paths
|
318
|
+
# method body...
|
319
|
+
end # #remove_paths
|
320
|
+
|
321
|
+
|
322
|
+
# end protected
|
323
|
+
|
324
|
+
|
325
|
+
end # class NRSER::Env::Path
|
data/lib/nrser/env.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Declarations
|
4
|
+
# =======================================================================
|
5
|
+
|
6
|
+
module NRSER::Env; end
|
7
|
+
|
8
|
+
|
9
|
+
# Post-Processing
|
10
|
+
# =======================================================================
|
11
|
+
|
12
|
+
require_relative './env/path'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Raised when we expected `#count` to be something it's not.
|
4
|
+
#
|
5
|
+
# Extends {NRSER::ValueError}, and the {#value} must be the instance that
|
6
|
+
#
|
7
|
+
class NRSER::AttrError < NRSER::ValueError
|
8
|
+
|
9
|
+
# Name of attribute that has invalid value.
|
10
|
+
#
|
11
|
+
# @return [Symbol]
|
12
|
+
#
|
13
|
+
attr_reader :symbol
|
14
|
+
|
15
|
+
|
16
|
+
# Actual invalid value of the subject's attribute.
|
17
|
+
#
|
18
|
+
# If not provided at construction, will be retrieved by sending {#symbol}
|
19
|
+
# to {#subject} in {#initialize}.
|
20
|
+
#
|
21
|
+
# @return [Object]
|
22
|
+
#
|
23
|
+
attr_reader :actual
|
24
|
+
|
25
|
+
|
26
|
+
# An optional expected value to use in {#build_message}.
|
27
|
+
#
|
28
|
+
# @return [Object]
|
29
|
+
#
|
30
|
+
attr_reader :expected
|
31
|
+
|
32
|
+
|
33
|
+
# @param [Object] subject:
|
34
|
+
# The object that has the invalid attribute value.
|
35
|
+
#
|
36
|
+
def initialize message = nil, symbol:, subject:, **options
|
37
|
+
@symbol = symbol.to_sym
|
38
|
+
|
39
|
+
@actual = if options.key?( :actual )
|
40
|
+
options[:actual]
|
41
|
+
else
|
42
|
+
value.send @symbol
|
43
|
+
end
|
44
|
+
|
45
|
+
@has_expected = options.key? :expected
|
46
|
+
@expected = options[:expected]
|
47
|
+
|
48
|
+
super message, subject: subject
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_expected?
|
52
|
+
@has_expected
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_message
|
56
|
+
headline = if has_expected?
|
57
|
+
"#{ subject.class } object has invalid ##{ symbol }: " +
|
58
|
+
"expected #{ expected }, found #{ actual }"
|
59
|
+
else
|
60
|
+
"#{ subject.class } object has invalid ##{ symbol } (found #{ actual })"
|
61
|
+
end
|
62
|
+
|
63
|
+
binding.erb <<-END
|
64
|
+
<%= headline %>
|
65
|
+
|
66
|
+
Subject:
|
67
|
+
|
68
|
+
<%= subject.pretty_inspect %>
|
69
|
+
|
70
|
+
END
|
71
|
+
end
|
72
|
+
|
73
|
+
end # class NRSER::CountError
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Raised when we expected `#count` to be something it's not.
|
4
|
+
#
|
5
|
+
# Extends {NRSER::ValueError}, and the {#value} must be the instance that
|
6
|
+
#
|
7
|
+
class NRSER::CountError < NRSER::AttrError
|
8
|
+
def initialize message = nil, subject:, expected:, count: nil
|
9
|
+
super message,
|
10
|
+
subject: subject,
|
11
|
+
symbol: :count,
|
12
|
+
actual: (count || subject.count),
|
13
|
+
expected: expected
|
14
|
+
end
|
15
|
+
|
16
|
+
def count
|
17
|
+
actual
|
18
|
+
end
|
19
|
+
end # class NRSER::CountError
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Requirements
|
4
|
+
# =======================================================================
|
5
|
+
|
6
|
+
# Stdlib
|
7
|
+
# -----------------------------------------------------------------------
|
8
|
+
|
9
|
+
# Deps
|
10
|
+
# -----------------------------------------------------------------------
|
11
|
+
|
12
|
+
# Project / Package
|
13
|
+
# -----------------------------------------------------------------------
|
14
|
+
|
15
|
+
|
16
|
+
# Definitions
|
17
|
+
# =======================================================================
|
18
|
+
|
19
|
+
# A mixin for {Exception} and utilities to make errors nicer.
|
20
|
+
#
|
21
|
+
module NRSER::NicerError
|
22
|
+
|
23
|
+
|
24
|
+
# TODO document `context` attribute.
|
25
|
+
#
|
26
|
+
# @return [Hash<Symbol, V>]
|
27
|
+
#
|
28
|
+
attr_reader :context
|
29
|
+
|
30
|
+
|
31
|
+
# @todo Document render_message method.
|
32
|
+
#
|
33
|
+
# @param [type] arg_name
|
34
|
+
# @todo Add name param description.
|
35
|
+
#
|
36
|
+
# @return [return_type]
|
37
|
+
# @todo Document return value.
|
38
|
+
#
|
39
|
+
def self.render_message message,
|
40
|
+
context: {},
|
41
|
+
add_context: true,
|
42
|
+
&get_extended_message
|
43
|
+
|
44
|
+
# 1. Figure out if `message` is just the "short message" (single line)
|
45
|
+
# or if it's "old-style" where it's just the whole thing.
|
46
|
+
|
47
|
+
message_lines = message.lines
|
48
|
+
|
49
|
+
if message_lines.length > 1
|
50
|
+
message_lines = NRSER.dedent message_lines, return_lines: true
|
51
|
+
short_message = message_lines.first.chomp
|
52
|
+
extended_message_lines = message_lines.rest
|
53
|
+
else
|
54
|
+
short_message = message
|
55
|
+
extended_message_lines = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Ok, `short_message` is a single line string
|
59
|
+
# `extended_message_lines` is an array of string lines or `nil`
|
60
|
+
|
61
|
+
# 2.
|
62
|
+
|
63
|
+
if get_extended_message
|
64
|
+
got_extended_message = if get_extended_message.arity == 0
|
65
|
+
get_extended_message.call
|
66
|
+
else
|
67
|
+
get_extended_message.call context
|
68
|
+
end
|
69
|
+
|
70
|
+
got_extended_message_lines = NRSER.dedent \
|
71
|
+
got_extended_message,
|
72
|
+
return_lines: true
|
73
|
+
|
74
|
+
if extended_message_lines
|
75
|
+
extended_message_lines += [
|
76
|
+
"\n",
|
77
|
+
*got_extended_message_lines
|
78
|
+
]
|
79
|
+
else
|
80
|
+
extended_message_lines = got_extended_message_lines
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if add_context
|
85
|
+
extended_message_lines += [
|
86
|
+
"Context:\n",
|
87
|
+
"\n"
|
88
|
+
"\n <%= %>"
|
89
|
+
]
|
90
|
+
end
|
91
|
+
|
92
|
+
end # .render_message
|
93
|
+
|
94
|
+
|
95
|
+
# @todo Document initialize method.
|
96
|
+
#
|
97
|
+
# @param [type] arg_name
|
98
|
+
# @todo Add name param description.
|
99
|
+
#
|
100
|
+
# @return [return_type]
|
101
|
+
# @todo Document return value.
|
102
|
+
#
|
103
|
+
def initialize message,
|
104
|
+
context: {},
|
105
|
+
add_context: true,
|
106
|
+
&
|
107
|
+
@context = context
|
108
|
+
end # #initialize
|
109
|
+
|
110
|
+
end # module NRSER::NicerError
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
# Raised when there is a problem with a *value* that does not fall into one
|
5
|
+
# of the other built-in exception categories (non-exhaustive list):
|
6
|
+
#
|
7
|
+
# 1. It's the wrong type (TypeError)
|
8
|
+
# 2. It's an argument (ArgumentError)
|
9
|
+
#
|
10
|
+
# The invalid value is attached to the error as an instance value so that
|
11
|
+
# rescuers up the stack can do more intelligent things with it if need be.
|
12
|
+
#
|
13
|
+
class NRSER::ValueError < StandardError
|
14
|
+
|
15
|
+
# The invalid value.
|
16
|
+
#
|
17
|
+
# @return [Object]
|
18
|
+
#
|
19
|
+
attr_reader :subject
|
20
|
+
|
21
|
+
|
22
|
+
def initialize message = nil, subject:
|
23
|
+
@subject = subject
|
24
|
+
|
25
|
+
# If we received `nil` for the message, call {#build_message} to get it.
|
26
|
+
#
|
27
|
+
# This provides a "hook" to assemble the message at the last possible
|
28
|
+
# moment before it needs to go up to {StandardError#initialize}, allowing
|
29
|
+
# {#build_message} to work with an otherwise fully-initialized instance.
|
30
|
+
#
|
31
|
+
# Of course, {NRSER::ValueError#build_message}
|
32
|
+
# throws {NotImplementedError} since it doesn't really have enough
|
33
|
+
# knowledge to build anything useful (we're going for useful errors,
|
34
|
+
# "Value #{ value } is invalid" does not suffice).
|
35
|
+
#
|
36
|
+
message = build_message if message.nil?
|
37
|
+
|
38
|
+
super message
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Build the error message when none is provided to `#initialize`.
|
43
|
+
#
|
44
|
+
# When no `message` (or `nil`) is provided to {NRSER::ValueError.initialize}
|
45
|
+
# it will call this method to get the error message just before it needs it
|
46
|
+
# to call up to {StandardError#initialize} (via `super message`).
|
47
|
+
#
|
48
|
+
# This allows {NRSER::ValueError} subclasses that are able to build a useful
|
49
|
+
# default message or would like to augment the user-provided one to do so
|
50
|
+
# at the last possible moment before it's needed, letting them work with an
|
51
|
+
# otherwise fully-initialized instance.
|
52
|
+
#
|
53
|
+
# Hence a subclass several generations down from {NRSER::ValueError} can
|
54
|
+
# use values initialized in all the constructors in-between, avoiding a lot
|
55
|
+
# of headache.
|
56
|
+
#
|
57
|
+
# This implementation always raises {NRSER::AbstractMethodError} because
|
58
|
+
# {NRSER::ValueError} does not have enough information to construct a useful
|
59
|
+
# message.
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
# Implementations must return the message string for
|
63
|
+
# {StadardError#initialize}.
|
64
|
+
#
|
65
|
+
# @raise [NRSER::AbstractMethodError]
|
66
|
+
# Must be implemented by subclasses if they wish to use message building.
|
67
|
+
#
|
68
|
+
def build_message
|
69
|
+
raise NRSER::AbstractMethodError.new( self, __method__ )
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/nrser/errors.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
|
2
|
+
# Requirements
|
3
|
+
# =======================================================================
|
4
|
+
|
5
|
+
# Stdlib
|
6
|
+
# -----------------------------------------------------------------------
|
7
|
+
|
8
|
+
# Deps
|
9
|
+
# -----------------------------------------------------------------------
|
10
|
+
|
11
|
+
# Project / Package
|
12
|
+
# -----------------------------------------------------------------------
|
13
|
+
require_relative './errors/value_error'
|
14
|
+
require_relative './errors/attr_error'
|
15
|
+
require_relative './errors/count_error'
|
16
|
+
|
17
|
+
|
1
18
|
module NRSER
|
2
19
|
|
3
20
|
# Indicates some piece of application state is in conflict with the attempted
|
@@ -5,7 +22,7 @@ module NRSER
|
|
5
22
|
class ConflictError < StandardError; end
|
6
23
|
|
7
24
|
|
8
|
-
# Extension of Ruby's {NotImplementedError} to provide a useful message
|
25
|
+
# Extension of Ruby's {NotImplementedError} to provide a useful message
|
9
26
|
# and convenient constructor for abstract methods.
|
10
27
|
#
|
11
28
|
# @example
|
@@ -31,22 +48,22 @@ module NRSER
|
|
31
48
|
|
32
49
|
message = if @method.owner == instance.class
|
33
50
|
NRSER.dedent <<-END
|
34
|
-
Method #{ @method.owner.name }##{ @method_name } is abstract, meaning
|
35
|
-
#{ @method.owner.name } is an abstract class and the invoking
|
51
|
+
Method #{ @method.owner.name }##{ @method_name } is abstract, meaning
|
52
|
+
#{ @method.owner.name } is an abstract class and the invoking
|
36
53
|
instance #{ @instance } should NOT have been constructed.
|
37
54
|
END
|
38
55
|
else
|
39
56
|
NRSER.squish <<-END
|
40
|
-
Method #{ @method.owner.name }##{ @method_name } is abstract and
|
57
|
+
Method #{ @method.owner.name }##{ @method_name } is abstract and
|
41
58
|
has not been implemented in invoking class #{ @instance.class }.
|
42
59
|
|
43
60
|
If you *are* developing the invoking class #{ @instance.class } it
|
44
|
-
(or a parent class between it and #{ @method.owner.name }) must
|
61
|
+
(or a parent class between it and #{ @method.owner.name }) must
|
45
62
|
implement ##{ @method_name }.
|
46
63
|
|
47
64
|
If you *are not* developing #{ @instance.class } it should be treated
|
48
65
|
as an abstract base class and should NOT be constructed. You need to
|
49
|
-
find a subclass of #{ @instance.class } to instantiate or write
|
66
|
+
find a subclass of #{ @instance.class } to instantiate or write
|
50
67
|
your own.
|
51
68
|
END
|
52
69
|
end
|
@@ -193,8 +193,10 @@ module NRSER
|
|
193
193
|
# If `enum` does not have `#count == 1`.
|
194
194
|
#
|
195
195
|
def self.only! enum
|
196
|
-
|
197
|
-
|
196
|
+
count = enum.count
|
197
|
+
|
198
|
+
unless count == 1
|
199
|
+
message = erb binding, <<-END
|
198
200
|
Expected enumerable to have #count == 1 but it has
|
199
201
|
|
200
202
|
#count = <%= enum.count %>
|
@@ -204,6 +206,11 @@ module NRSER
|
|
204
206
|
<%= enum.pretty_inspect %>
|
205
207
|
|
206
208
|
END
|
209
|
+
|
210
|
+
raise NRSER::CountError.new message,
|
211
|
+
subject: enum,
|
212
|
+
count: count,
|
213
|
+
expected: 1
|
207
214
|
end
|
208
215
|
|
209
216
|
enum.first
|