nrser 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2f2d4d8bf9243e750dd94962a0331985b7ec0ca
4
- data.tar.gz: 55ae615d5733b4e183052cbe0afdc5cca39fd172
3
+ metadata.gz: 736ee433c6078e250b89ebbc65455ff157888b6e
4
+ data.tar.gz: a99e836e91a873ea4b06767376a6c38e30c2fe5a
5
5
  SHA512:
6
- metadata.gz: 740ca5dd0ada05a5adb259c13f1e2ca5150308b26686f0ffa8d63e399ad748d5c36beea986623bea9884a1fdcdd690f4bce7d03e3b8cd124b615f3f99b11b860
7
- data.tar.gz: 43421a77de4da50eb540cc1055e7a732f47b4a058af81898a63da68737b8a4bbe3c8d715d021d9d325580c8d623ffd8d2919fe39a9dd52826a8b458441c71a83
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
- unless enum.count == 1
197
- raise ArgumentError.new erb binding, <<-END
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