xeme 1.1 → 2.0

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 (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +306 -225
  3. data/lib/xeme.rb +694 -627
  4. metadata +25 -11
data/lib/xeme.rb CHANGED
@@ -1,637 +1,704 @@
1
+ require 'deep_dup'
1
2
  require 'forwardable'
2
- require 'declutter'
3
-
3
+ # require 'declutter'
4
+ require 'json'
5
+ require 'date'
4
6
 
5
7
  #===============================================================================
6
8
  # Xeme
7
9
  #
10
+
11
+ # An object of the Xeme class represents a single xeme.
12
+ #
13
+ # Documentation frequently refers to `@hsh`, which is a private object used to
14
+ # store the xeme information.
15
+
8
16
  class Xeme
9
- # Returns the underlying hash that the xeme manages.
10
- attr_reader :hsh
11
-
12
- #---------------------------------------------------------------------------
13
- # delegate
14
- #
15
- extend Forwardable
16
- delegate %w([] []= each length clear delete to_json) => :hsh
17
- #
18
- # delegate
19
- #---------------------------------------------------------------------------
20
-
21
-
22
- #---------------------------------------------------------------------------
23
- # initialize
24
- #
25
-
26
- # Creates a new Xeme object. Optionally takes a single strin parameter which
27
- # is set as the id for the xeme.
28
-
29
- def initialize(id=nil)
30
- @hsh = {}
31
-
32
- if id
33
- meta id
34
- end
35
- end
36
- #
37
- # initialize
38
- #---------------------------------------------------------------------------
39
-
40
-
41
- #---------------------------------------------------------------------------
42
- # meta
43
- #
44
-
45
- # Returns the meta hash, creating it if necessary. The meta hash
46
- # contains at least a timestamp and a UUID.
47
-
48
- def meta(id=nil)
49
- require 'securerandom'
50
-
51
- # initialize meta element if necessary
52
- @hsh['meta'] ||= {}
53
-
54
- # populate meta
55
- @hsh['meta']['uuid'] ||= SecureRandom.uuid().to_s
56
- @hsh['meta']['timestamp'] ||= Time.now
57
-
58
- # give child id if given
59
- if id
60
- meta['id'] = id
61
- end
62
-
63
- # return
64
- return @hsh['meta']
65
- end
66
-
67
- # Returns the UUID in the meta hash.
68
- def uuid
69
- return meta['uuid']
70
- end
71
-
72
- # Returns the timestamp in the meta hash.
73
- def timestamp
74
- return meta['timestamp']
75
- end
76
-
77
- # Returns the id element of the meta hash if there is one.
78
- def id
79
- if @hsh['meta']
80
- return @hsh['meta']['id']
81
- else
82
- return nil
83
- end
84
- end
85
-
86
- # Sets id element of the meta.
87
- def id=(v)
88
- return meta['id'] = v
89
- end
90
- #
91
- # meta
92
- #---------------------------------------------------------------------------
93
-
94
-
95
- #---------------------------------------------------------------------------
96
- # flatten
97
- #
98
-
99
- # Folds all nested messages into the outermost xeme, and deletes nested
100
- # xemes. Only the messages are folded in, not any metainformation or other
101
- # information that might be in the nested xemes.
102
-
103
- def flatten
104
- resolve()
105
-
106
- as_arr('nested').each do |child|
107
- child.flatten
108
-
109
- %w{errors warnings notes promises}.each do |msg_key|
110
- if child[msg_key]
111
- @hsh[msg_key] ||= []
112
- @hsh[msg_key] += child[msg_key]
113
- child.delete(msg_key)
114
- end
115
- end
116
- end
117
-
118
- @hsh.delete('nested')
119
- end
120
- #
121
- # flatten
122
- #---------------------------------------------------------------------------
123
-
124
-
125
- #---------------------------------------------------------------------------
126
- # to_h
127
- #
128
- # def to_h
129
- # rv = @hsh.to_h
130
- #
131
- # # loop through nested xemes
132
- # if rv['nested']
133
- # rv['nested'] = rv['nested'].map do |child|
134
- # child.to_h
135
- # end
136
- # end
137
- #
138
- # # return
139
- # return rv
140
- # end
141
- #
142
- # to_h
143
- #---------------------------------------------------------------------------
144
-
145
-
146
- #---------------------------------------------------------------------------
147
- # succeed
148
- #
149
-
150
- # Attempt to set success to true. Raises an exception if there are any
151
- # errors or promises in this or any nested Xeme.
152
-
153
- def succeed
154
- # if any errors, don't succeed
155
- if errors.any?
156
- raise 'cannot-set-to-success: errors'
157
- end
158
-
159
- # if any promises, don't succeed
160
- if promises.any?
161
- raise 'cannot-set-to-success: promises'
162
- end
163
-
164
- # set to success
165
- return @hsh['success'] = true
166
- end
167
-
168
- #
169
- # succeed
170
- #---------------------------------------------------------------------------
171
-
172
-
173
- #---------------------------------------------------------------------------
174
- # try_succeed
175
- #
176
-
177
- # Attempt to set success to true. Does not raise an exception if there are
178
- # errors or promises, but in those cases will not set success to true.
179
-
180
- def try_succeed(resolve_self=true)
181
- enforce_nil = false
182
-
183
- # resolve self and descendents
184
- if resolve_self
185
- resolve()
186
- end
187
-
188
- # if any promises
189
- if as_arr('promises').any?
190
- @hsh.delete 'success'
191
- enforce_nil = true
192
- end
193
-
194
- # try_succeed for descendents
195
- as_arr('nested').each do |child|
196
- child.try_succeed false
197
-
198
- if @hsh['success'].nil?
199
- if child['success'].nil?
200
- enforce_nil = true
201
- elsif not child['success']
202
- @hsh['success'] = false
203
- end
204
- end
205
- end
206
-
207
- # set to success if success is nil and no promises
208
- if @hsh['success'].nil? and (! enforce_nil)
209
- @hsh['success'] = true
210
- end
211
-
212
- # return
213
- return @hsh['success']
214
- end
215
-
216
- #
217
- # try_succeed
218
- #---------------------------------------------------------------------------
219
-
220
-
221
- #---------------------------------------------------------------------------
222
- # fail
223
- #
224
-
225
- # Explicitly set success to false. Does not add an error.
226
-
227
- def fail
228
- @hsh['success'] = false
229
- resolve()
230
- end
231
-
232
- #
233
- # fail
234
- #---------------------------------------------------------------------------
235
-
236
-
237
- #---------------------------------------------------------------------------
238
- # success?
239
- #
240
- def success?
241
- resolve()
242
- return @hsh['success']
243
- end
244
- #
245
- # success?
246
- #---------------------------------------------------------------------------
247
-
248
-
249
- #---------------------------------------------------------------------------
250
- # messages
251
- #
252
-
253
- # Returns a locked array of all errors, including errors in nested xemes.
254
- # If the optional id param is sent, returns only errors with that id.
255
- def errors(id=nil)
256
- return all_messages('errors', id)
257
- end
258
-
259
- # Returns a locked array of all warnings, including warnings in nested xemes.
260
- # If the optional id param is sent, returns only warnings with that id.
261
- def warnings(id=nil)
262
- return all_messages('warnings', id)
263
- end
264
-
265
- # Returns a locked array of all notes, including notes in nested xemes.
266
- # If the optional id param is sent, returns only notes with that id.
267
- def notes(id=nil)
268
- return all_messages('notes', id)
269
- end
270
-
271
- # Returns a locked array of all promises, including promises in nested xemes.
272
- # If the optional id param is sent, returns only promises with that id.
273
- def promises(id=nil)
274
- return all_messages('promises', id)
275
- end
276
-
277
- #
278
- # messages
279
- #---------------------------------------------------------------------------
280
-
281
-
282
- #---------------------------------------------------------------------------
283
- # message hashes
284
- #
285
-
286
- # Returns a hash of all errors, including nested errors. The key for each
287
- # element is the id of the error(s). Does not return errors that don't have
288
- # ids.
289
- def errors_hash()
290
- return messages_hash('errors')
291
- end
292
-
293
- # Returns a hash of all warnings, including nested warnings. The key for
294
- # each element is the id of the warnings(s). Does not return warnings that
295
- # don't have ids.
296
- def warnings_hash(id=nil)
297
- return messages_hash('warnings')
298
- end
299
-
300
- # Returns a hash of all notes, including nested notes. The key for each
301
- # element is the id of the notes(s). Does not return notes that don't have
302
- # ids.
303
- def notes_hash(id=nil)
304
- return messages_hash('notes')
305
- end
306
-
307
- # Returns a hash of all promises, including nested promises. The key for
308
- # each element is the id of the promise(s). Does not return promises that
309
- # don't have ids.
310
- def promises_hash(id=nil)
311
- return messages_hash('promises')
312
- end
313
-
314
- #
315
- # message hashes
316
- #---------------------------------------------------------------------------
317
-
318
-
319
- #---------------------------------------------------------------------------
320
- # add message
321
- #
322
-
323
- # Creates and returns an error message. Accepts a do block which yields the
324
- # new message. If given the opional id param, sets that value as the id for
325
- # the message.
326
- def error(id, &block)
327
- @hsh['success'] = false
328
- return message('errors', id, &block)
329
- end
330
-
331
- # Creates and returns a warning message. Accepts a do block which yields
332
- # the new message. If given the opional id param, sets that value as the id
333
- # for the new message.
334
- def warning(id, &block)
335
- return message('warnings', id, &block)
336
- end
337
-
338
- # Creates and returns a note message. Accepts a do block which yields
339
- # the new message. If given the opional id param, sets that value as the id
340
- # for the new message.
341
- def note(id, &block)
342
- return message('notes', id, &block)
343
- end
344
-
345
- # Creates and returns a promise message. Accepts a do block which yields
346
- # the new message. If given the opional id param, sets that value as the id
347
- # for the new message.
348
- def promise(id, &block)
349
- unless @hsh['success'].is_a?(FalseClass)
350
- @hsh.delete 'success'
351
- end
352
-
353
- return message('promises', id, &block)
354
- end
355
-
356
- #
357
- # add message
358
- #---------------------------------------------------------------------------
359
-
360
-
361
- #---------------------------------------------------------------------------
362
- # nest
363
- #
364
-
365
- # Creates a new xeme and nests it in the current xeme. Yields to a do block
366
- # if one is sent. Returns the new child xeme.
367
-
368
- def nest(id=nil)
369
- @hsh['nested'] ||= []
370
- child = self.class.new()
371
- @hsh['nested'].push child
372
-
373
- # give id
374
- if id
375
- child.meta['id'] = id
376
- end
377
-
378
- # yield in block
379
- if block_given?
380
- yield child
381
- end
382
-
383
- # return
384
- return child
385
- end
386
-
387
- #
388
- # nest
389
- #---------------------------------------------------------------------------
390
-
391
-
392
- #---------------------------------------------------------------------------
393
- # nested
394
- #
395
- # def nested()
396
- # @hsh['nested'] ||= []
397
- # return @hsh['nested']
398
- # end
399
- #
400
- # nested
401
- #---------------------------------------------------------------------------
402
-
403
-
404
- #---------------------------------------------------------------------------
405
- # resolve
406
- #
407
-
408
- # Resolves conflicts between errors, promises, and success. See README.md
409
- # for details. You generally don't need to call this method yourself.
410
-
411
- def resolve
412
- # resolve descendents
413
- as_arr('nested').each do |child|
414
- child.resolve
415
- end
416
-
417
- # if own errors, fail
418
- if as_arr('errors').any?
419
- @hsh['success'] = false
420
- return
421
- end
422
-
423
- # if explicitly set to false, return
424
- if @hsh['success'].is_a?(FalseClass)
425
- return
426
- end
427
-
428
- # if any promises, set success to nil
429
- if as_arr('promises').any?
430
- @hsh.delete 'success'
431
- end
432
-
433
- # if any child is set to nil, set self to nil
434
- # if any child set to false, set self to false and return
435
- as_arr('nested').each do |child|
436
- if child['success'].nil?
437
- @hsh.delete 'success'
438
- elsif not child['success']
439
- @hsh['success'] = false
440
- return
441
- end
442
- end
443
- end
444
- #
445
- # resolve
446
- #---------------------------------------------------------------------------
447
-
448
-
449
- #---------------------------------------------------------------------------
450
- # declutter
451
- #
452
-
453
- # Removes empty arrays and hahes.
454
-
455
- def declutter
456
- resolve()
457
- Declutter.process @hsh
458
- return true
459
- end
460
- #
461
- # declutter
462
- #---------------------------------------------------------------------------
463
-
464
-
465
- #---------------------------------------------------------------------------
466
- # all
467
- #
468
-
469
- # Returns an array consisting of the xeme and all nested xemes.
470
-
471
- def all(seek_id=nil)
472
- rv = []
473
-
474
- # add self
475
- if seek_id
476
- if id == seek_id
477
- rv.push self
478
- end
479
- else
480
- rv.push self
481
- end
482
-
483
- # loop through nested children
484
- as_arr('nested').each do |child|
485
- rv += child.all(seek_id)
486
- end
487
-
488
- # freeze return value
489
- rv.freeze
490
-
491
- # return
492
- return rv
493
- end
494
- #
495
- # all
496
- #---------------------------------------------------------------------------
497
-
498
-
499
- #---------------------------------------------------------------------------
500
- # misc
501
- #
502
-
503
- # A handy place to puts miscellaneous information.
504
-
505
- def misc
506
- @hsh['misc'] ||= {}
507
- return @hsh['misc']
508
- end
509
-
510
- #
511
- # misc
512
- #---------------------------------------------------------------------------
513
-
514
-
515
- # private
516
- private
517
-
518
-
519
- #---------------------------------------------------------------------------
520
- # as_arr
521
- #
522
-
523
- # Convenience function for reading messages as arrays even if they don't
524
- # exist.
525
-
526
- def as_arr(key)
527
- return @hsh[key] || []
528
- end
529
- #
530
- # as_arr
531
- #---------------------------------------------------------------------------
532
-
533
-
534
- #---------------------------------------------------------------------------
535
- # message
536
- #
537
-
538
- # Creates a message object of the given type.
539
-
540
- def message(type, id, &block)
541
- # build message
542
- msg = {}
543
- id and msg['id'] = id
544
-
545
- # add to array
546
- @hsh[type] ||= []
547
- @hsh[type].push msg
548
-
549
- # yield if necessary
550
- if block_given?
551
- yield msg
552
- end
553
-
554
- # return
555
- return msg
556
- end
557
- #
558
- # message
559
- #---------------------------------------------------------------------------
560
-
561
-
562
- #---------------------------------------------------------------------------
563
- # all_messages
564
- #
565
-
566
- # Returns a locked array of all messages (including nested) or the given
567
- # type.
568
-
569
- def all_messages(plural, id)
570
- rv = []
571
-
572
- # add own messages
573
- if @hsh[plural]
574
- if id
575
- @hsh[plural].each do |m|
576
- if m['id'] == id
577
- rv.push m
578
- end
579
- end
580
- else
581
- rv += @hsh[plural]
582
- end
583
- end
584
-
585
- # recurse
586
- as_arr('nested').each do |child|
587
- rv += child.send(plural, id)
588
- end
589
-
590
- # freeze return value
591
- rv.freeze
592
-
593
- # return
594
- return rv
595
- end
596
- #
597
- # all_messages
598
- #---------------------------------------------------------------------------
599
-
600
-
601
- #---------------------------------------------------------------------------
602
- # messages_hash
603
- #
604
-
605
- # Returns a hash of the messages (including nested) of the given type. The
606
- # keys to the hash are the message ids. Does not return messages that don't
607
- # have ids.
608
-
609
- def messages_hash(type)
610
- rv = {}
611
-
612
- # add own messages
613
- as_arr(type).each do |msg|
614
- if msg['id']
615
- rv[msg['id']] ||= []
616
- rv[msg['id']].push msg
617
- end
618
- end
619
-
620
- # loop through nested children
621
- as_arr('nested').each do |child|
622
- child.send("#{type}_hash").each do |k, msgs|
623
- rv[k] ||= []
624
- rv[k] += msgs
625
- end
626
- end
627
-
628
- # return
629
- return rv
630
- end
631
- #
632
- # messages_hash
633
- #---------------------------------------------------------------------------
17
+
18
+ # Version.
19
+ VERSION = '2.0'
20
+
21
+ # Default value for `@hsh`. Xeme defaults to an empty hash. Subclasses of Xeme
22
+ # set their own defaults.
23
+ DEFAULT = {}.freeze
24
+
25
+ # An array of keys for `@hsh` that cannot be set.
26
+ PROHIBIT_ASSIGN = ['type'].freeze
27
+
28
+ # Indicates if the xeme is advisory.
29
+ ADVISORY = false
30
+
31
+ # A list of xeme types and their associated classes. Xeme subclasses populate
32
+ # this property.
33
+ TYPES = {}
34
+
35
+ # delegate
36
+ extend Forwardable
37
+ delegate %w([] each length keys value empty? any? has_key? to_json) => :@hsh
38
+
39
+
40
+ #-----------------------------------------------------------------------------
41
+ # initialize
42
+ #
43
+
44
+ # Initializes a new xeme. The single optional param can be nil, a hash that
45
+ # will be imported into the `@hsh` element, or a JSON string that will be
46
+ # parsed into a hash for the @hsh element.
47
+
48
+ def initialize(p_hsh=nil)
49
+ # initialize @hsh
50
+ @hsh = DeepDup.deep_dup(self.class::DEFAULT)
51
+
52
+ # merge in given hash if one was sent
53
+ if p_hsh
54
+ if p_hsh.is_a?(String)
55
+ p_hsh = JSON.parse(p_hsh)
56
+ end
57
+
58
+ @hsh = @hsh.merge(p_hsh)
59
+ end
60
+
61
+ # import nested
62
+ if @hsh['nested']
63
+ raws = @hsh.delete('nested')
64
+ @hsh['nested'] = []
65
+
66
+ raws.each do |raw|
67
+ @hsh['nested'].push Xeme.import(raw)
68
+ end
69
+ end
70
+ end
71
+ #
72
+ # initialize
73
+ #-----------------------------------------------------------------------------
74
+
75
+
76
+ #-----------------------------------------------------------------------------
77
+ # success?
78
+ #
79
+
80
+ # Resolves the xeme, then returns the value of `@hsh['success']`.
81
+ # @return (Boolean)
82
+ def success?
83
+ resolve
84
+ return @hsh['success']
85
+ end
86
+
87
+ #
88
+ # success?
89
+ #-----------------------------------------------------------------------------
90
+
91
+
92
+ #-----------------------------------------------------------------------------
93
+ # []=
94
+ #
95
+
96
+ # Sets values in `@hsh`. Specific Xeme classes can prohibit the setting of
97
+ # specific keys in the hash. `type` is always prohibited. Advisory xemes also
98
+ # prohibit directly setting `success`.
99
+ # @raise (Xeme::Exception::CannotAssignKey)
100
+ def []=(k, v)
101
+ # prohibit keys in PROHIBIT_ASSIGN
102
+ if self.class::PROHIBIT_ASSIGN.include?(k)
103
+ raise Xeme::Exception::CannotAssignKey.new(k)
104
+ end
105
+
106
+ # allow assignment
107
+ @hsh[k] = v
108
+ end
109
+
110
+ # Deletes values in @hsh. Specific Xeme classes can prohibit the deletion of
111
+ # specific keys in the hash. "type" is always prohibited. Advisory xemes also
112
+ # prohibit deleting `success`.
113
+ # @raise (Xeme::Exception::CannotDeleteKey)
114
+ def delete(k)
115
+ # prohibit keys in PROHIBIT_ASSIGN
116
+ if self.class::PROHIBIT_ASSIGN.include?(k)
117
+ raise Xeme::Exception::CannotDeleteKey.new(k)
118
+ end
119
+
120
+ # allow assignment
121
+ return @hsh.delete(k)
122
+ end
123
+
124
+ #
125
+ # []=
126
+ #-----------------------------------------------------------------------------
127
+
128
+
129
+ #-----------------------------------------------------------------------------
130
+ # nested, misc
131
+ #
132
+
133
+ # Creates `@hsh['nested']` if necessary and returns it.
134
+ # @return (Array)
135
+ def nested
136
+ @hsh['nested'] ||= []
137
+ return @hsh['nested']
138
+ end
139
+
140
+ # A handy place to put miscellaneous information. No Xeme methods use this
141
+ # hash.
142
+ # @return (Hash)
143
+ def misc
144
+ @hsh['misc'] ||= {}
145
+ return @hsh['misc']
146
+ end
147
+
148
+ #
149
+ # nested, misc
150
+ #-----------------------------------------------------------------------------
151
+
152
+
153
+ #-----------------------------------------------------------------------------
154
+ # meta
155
+ #
156
+
157
+ # Creates `@hsh['meta']` if it does not already exist, then returns it.
158
+ # @return (Hash)
159
+ def meta
160
+ return @hsh['meta'] ||= {}
161
+ end
162
+
163
+ # Creates `@hsh['meta']['uuid']` if it does not already exist, then returns
164
+ # it.
165
+ # @return (String)
166
+ def init_uuid
167
+ if not meta['uuid']
168
+ require 'securerandom'
169
+ meta['uuid'] ||= SecureRandom.uuid
170
+ end
171
+
172
+ return meta['uuid']
173
+ end
174
+
175
+ # Creates `@hsh['meta']['timestamp']` if it doesn't already exist.
176
+ # @return (DateTime)
177
+ def init_timestamp
178
+ meta['timestamp'] ||= DateTime.now
179
+ return meta['timestamp']
180
+ end
181
+
182
+ # Initializes `@hsh['meta']` if it doesn't already exist. Also initializes
183
+ # `@hsh['meta']['timestamp']` and `@hsh['meta']['uuid']` if they don't already
184
+ # exist.
185
+ # @return (Hash)
186
+ def init_meta
187
+ init_timestamp()
188
+ init_uuid()
189
+ return @hsh['meta']
190
+ end
191
+
192
+ # Returns `@hsh['meta']['timestamp']`. Does not initialize it if it doesn't
193
+ # exist.
194
+ # @return (DateTime)
195
+ def timestamp
196
+ if @hsh['meta']
197
+ return @hsh['meta']['timestamp']
198
+ else
199
+ return nil
200
+ end
201
+ end
202
+
203
+ # Returns `@hsh['meta']['uuid']`. Does not initialize it if it doesn't
204
+ # exist.
205
+ # @return (String)
206
+ def uuid
207
+ if @hsh['meta']
208
+ return @hsh['meta']['uuid']
209
+ else
210
+ return nil
211
+ end
212
+ end
213
+
214
+ # Sets the value of `@hsh['meta']['id']`.
215
+ def id=(v)
216
+ return meta['id'] = v
217
+ end
218
+
219
+ # Returns the value of `@hsh['meta']['id']`.
220
+ def id()
221
+ return meta['id']
222
+ end
223
+ #
224
+ # meta
225
+ #-----------------------------------------------------------------------------
226
+
227
+
228
+ #-----------------------------------------------------------------------------
229
+ # all, errors, successes, warnings, notes, promises
230
+ #
231
+
232
+ # Returns a locked array of the xeme and all of its nested xemes.
233
+ # @param clss [Class] optional param of which class of xemes to return.
234
+ # @return [Array]
235
+ def all(clss=Xeme)
236
+ # initialize return value
237
+ rv = []
238
+
239
+ # add self if right class
240
+ if self.is_a?(clss)
241
+ rv << self
242
+ end
243
+
244
+ # add nested xemes
245
+ if @hsh['nested']
246
+ @hsh['nested'].each do |child|
247
+ rv += child.all(clss)
248
+ end
249
+ end
250
+
251
+ # freeze and return
252
+ rv.freeze
253
+ return rv
254
+ end
255
+
256
+ # Returns a locked array of all warning xemes.
257
+ # @return [Array]
258
+ def warnings
259
+ return all(Warning)
260
+ end
261
+
262
+ # Returns a locked array of all note xemes.
263
+ # @return [Array]
264
+ def notes
265
+ return all(Note)
266
+ end
267
+
268
+ # Returns a locked array of all advisory (warning and note) xemes.
269
+ # @return [Array]
270
+ def advisories
271
+ return all(Advisory)
272
+ end
273
+
274
+ # Returns a locked array of all promise xemes.
275
+ # @return [Array]
276
+ def promises
277
+ return all(Promise)
278
+ end
279
+
280
+ # Returns a locked array of all xemes with an explicit setting of
281
+ # success=false.
282
+ # @return [Array]
283
+ def errors
284
+ return select {|x| x['success'].is_a?(FalseClass)}
285
+ end
286
+
287
+ # Returns a locked array of all xemes with an explicit setting of
288
+ # success=true.
289
+ # @return [Array]
290
+ def successes
291
+ return select {|x| x['success']}
292
+ end
293
+
294
+ # Returns a locked array of all xemes in which success is nil. Does not return
295
+ # advisory xemes.
296
+ # @return [Array]
297
+ def nils
298
+ return select {|x| (not x.advisory?) and x['success'].nil?}
299
+ end
300
+
301
+ #
302
+ # all
303
+ #-----------------------------------------------------------------------------
304
+
305
+
306
+ #-----------------------------------------------------------------------------
307
+ # nest
308
+ #
309
+
310
+ # Creates or accepts a xeme and nests it.
311
+ # @return (Xeme)
312
+ # @yield (Xeme)
313
+ # @raise (Xeme::Exception::InvalidNestClass)
314
+ # @raise (Xeme::Exception::InvalidNestType)
315
+ # @raise (Xeme::Exception::CannotNestNonadvisory)
316
+ def nest(type=nil)
317
+ # determine which type of Xeme to use. type can be a Xeme class or a xeme.
318
+ if type.nil?
319
+ child = self.class.new
320
+ elsif type.is_a?(Class)
321
+ if type <= Xeme
322
+ child = type.new
323
+ else
324
+ raise Xeme::Exception::InvalidNestClass.new(type)
325
+ end
326
+ elsif type.is_a?(Xeme)
327
+ child = type
328
+ else
329
+ raise Xeme::Exception::InvalidNestType.new(type)
330
+ end
331
+
332
+ # advisory cannot nest non-advisory
333
+ if advisory? and (not child.advisory?)
334
+ raise Xeme::Exception::CannotNestNonadvisory.new(child.class)
335
+ end
336
+
337
+ # add child to nested array
338
+ nested.push child
339
+
340
+ # yield if necessary
341
+ if block_given?
342
+ yield child
343
+ end
344
+
345
+ # return child
346
+ return child
347
+ end
348
+
349
+ #
350
+ # nest
351
+ #-----------------------------------------------------------------------------
352
+
353
+
354
+ #-----------------------------------------------------------------------------
355
+ # nesting types
356
+ #
357
+
358
+ # Adds a nested xeme that is automatically set as failed. Also sets the
359
+ # calling xeme to failure, because if a nested xeme is set to failure than the
360
+ # parent xeme must be set to failure.
361
+ # @return [Xeme]
362
+ # @yield [Xeme]
363
+ def error(&block)
364
+ fail
365
+ child = Xeme.new()
366
+ child.fail
367
+ return nest(child, &block)
368
+ end
369
+
370
+ alias_method :failure, :error
371
+
372
+ # Adds a nested xeme that is automatically set as successful.
373
+ # @return [Xeme]
374
+ # @yield [Xeme]
375
+ def success(&block)
376
+ child = Xeme.new()
377
+ child.try_succeed
378
+ return nest(child, &block)
379
+ end
380
+
381
+ # Adds a nested Warning xeme.
382
+ # @return [Xeme::Warning]
383
+ # @yield [Xeme::Warning]
384
+ def warning(&block)
385
+ return nest(Xeme::Warning.new, &block)
386
+ end
387
+
388
+ # Adds a nested Note xeme.
389
+ # @return [Xeme::Note]
390
+ # @yield [Xeme::Note]
391
+ def note(&block)
392
+ return nest(Xeme::Note.new, &block)
393
+ end
394
+
395
+ # Adds a nested Promise xeme.
396
+ # @return [Xeme::Promise]
397
+ # @yield [Xeme::Promise]
398
+ def promise(&block)
399
+ unless @hsh['success'].is_a?(FalseClass)
400
+ @hsh.delete 'success'
401
+ end
402
+
403
+ return nest(Xeme::Promise.new, &block)
404
+ end
405
+ #
406
+ # nesting types
407
+ #-----------------------------------------------------------------------------
408
+
409
+
410
+ #-----------------------------------------------------------------------------
411
+ # fail, undecide, try_succeed
412
+ #
413
+
414
+ # Sets `success` to `false`.
415
+ # @return [Boolean] `false`
416
+ def fail
417
+ return self['success'] = false
418
+ end
419
+
420
+ # Deletes the `success` element.
421
+ # @return [Boolean] `nil`
422
+ def undecide
423
+ @hsh.delete 'success'
424
+ return nil
425
+ end
426
+
427
+ # Resolves the xeme and all descendents, then tries to set it to successful.
428
+ # Will only set to success if the xeme (and all descendents) has its `success`
429
+ # element to `nil` or `true`. Will not override `false` if `success` is
430
+ # already set to that value.
431
+ # @return [Boolean] The value of `success`.
432
+ def try_succeed
433
+ return resolve(true)
434
+ end
435
+
436
+ #
437
+ # fail, undecide, try_succeed
438
+ #-----------------------------------------------------------------------------
439
+
440
+
441
+ #-----------------------------------------------------------------------------
442
+ # resolve
443
+ #
444
+
445
+ # Resolves the xeme including all nested xemes.
446
+ # @return (Boolean) value of `@hsh['success']`
447
+ # @param p_try_succeed [Boolean] if the method should also try to succeed the
448
+ # xeme.
449
+ def resolve(p_try_succeed=false)
450
+ # if this is just an advisory xeme, do nothing
451
+ if advisory?
452
+ return
453
+ end
454
+
455
+ # If trying to and allowed to succeed, set success here. Resolution will
456
+ # change success if necessary.
457
+ if allow_try_succeed?(p_try_succeed)
458
+ @hsh['success'] = true
459
+ end
460
+
461
+ # loop through nested children
462
+ if @hsh['nested']
463
+ @hsh['nested'].each do |child|
464
+ unless child.advisory?
465
+ child.resolve p_try_succeed
466
+
467
+ # if this xeme isn't already explicitly set to false, downgrade as
468
+ # necessary from child xeme
469
+ unless @hsh['success'].is_a?(FalseClass)
470
+ if child['success'].nil?
471
+ @hsh.delete 'success'
472
+ elsif not child['success']
473
+ @hsh['success'] = false
474
+ end
475
+ end
476
+ end
477
+ end
478
+ end
479
+
480
+ # return
481
+ return @hsh['success']
482
+ end
483
+
484
+ #
485
+ # resolve
486
+ #-----------------------------------------------------------------------------
487
+
488
+
489
+ #-----------------------------------------------------------------------------
490
+ # convenience accessors
491
+ #
492
+
493
+ # Indicates if the xeme is advisory.
494
+ # @return [Boolean]
495
+ def advisory?
496
+ return self.class::ADVISORY
497
+ end
498
+
499
+ #
500
+ # convenience accessors
501
+ #-----------------------------------------------------------------------------
502
+
503
+
504
+ #-----------------------------------------------------------------------------
505
+ # extended classes
506
+ #
507
+
508
+ # Base class for Note and Warning. Directly instantiating this class is not
509
+ # advised.
510
+ class Advisory < self
511
+ DEFAULT = {'type'=>'advisory'}.freeze
512
+ PROHIBIT_ASSIGN = ['success', 'type'].freeze
513
+ ADVISORY = true
514
+ Xeme::TYPES['advisory'] = self
515
+ end
516
+
517
+ # Represents a note xeme.
518
+ class Note < Advisory
519
+ DEFAULT = {'type'=>'note'}.freeze
520
+ Xeme::TYPES['note'] = self
521
+ end
522
+
523
+ # Represents a warning xeme.
524
+ class Warning < Advisory
525
+ DEFAULT = {'type'=>'warning'}.freeze
526
+ Xeme::TYPES['warning'] = self
527
+ end
528
+
529
+ # Represents a promise xeme.
530
+ class Promise < self
531
+ DEFAULT = {'type'=>'promise'}.freeze
532
+ PROHIBIT_ASSIGN = ['type'].freeze
533
+ Xeme::TYPES['promise'] = self
534
+
535
+ # Sets values in `@hsh`. Does not allow setting `success` to true if
536
+ # `supplanted` is not set to true.
537
+ # @raise (Xeme::Exception::CannotSucceedPromiseUnlessSupplanted)
538
+ def []=(k, v)
539
+ if v and (k=='success') and (not @hsh['supplanted'])
540
+ raise Xeme::Exception::CannotSucceedPromiseUnlessSupplanted.new
541
+ end
542
+
543
+ return super(k, v)
544
+ end
545
+
546
+ # Resolves the xeme.
547
+ # @param p_try_succeed (Boolean). Meaningless if `supplanted` is not true.
548
+ # @return (Boolean)
549
+ def resolve(p_try_succeed=false)
550
+ super p_try_succeed
551
+
552
+ if not @hsh['supplanted']
553
+ @hsh.delete 'success'
554
+ end
555
+
556
+ return @hsh['success']
557
+ end
558
+ end
559
+ #
560
+ # extended classes
561
+ #-----------------------------------------------------------------------------
562
+
563
+
564
+ ### class methods
565
+
566
+
567
+ #-----------------------------------------------------------------------------
568
+ # import
569
+ #
570
+
571
+ # Imports a hash or JSON string into a xeme, including nested xemes. The
572
+ # `type` value, if present, is used to determine the class of the xeme.
573
+
574
+ def self.import(org)
575
+ # parse JSON if necessary
576
+ if org.is_a?(String)
577
+ org = JSON.parse(org)
578
+ end
579
+
580
+ # if a type is indicated and recognized, instantiate based on that type,
581
+ # else instantiate as Xeme.
582
+ if clss = Xeme::TYPES[org['type']]
583
+ xeme = clss.new(org)
584
+ else
585
+ xeme = self.new(org)
586
+ end
587
+
588
+ # convert timestamp if it is present
589
+ if xeme['meta'] and xeme['meta']['timestamp']
590
+ xeme['meta']['timestamp'] = DateTime.parse(xeme['meta']['timestamp'])
591
+ end
592
+
593
+ # return
594
+ return xeme
595
+ end
596
+ #
597
+ # import
598
+ #-----------------------------------------------------------------------------
599
+
600
+
601
+ # private
602
+ private
603
+
604
+
605
+ #-----------------------------------------------------------------------------
606
+ # allow_try_succeed?
607
+ #
608
+ def allow_try_succeed?(p_try_succeed)
609
+ p_try_succeed or return false
610
+ advisory? and return false
611
+ @hsh['success'].nil? or return false
612
+ return true
613
+ end
614
+ #
615
+ # allow_try_succeed?
616
+ #-----------------------------------------------------------------------------
617
+
618
+
619
+ #-----------------------------------------------------------------------------
620
+ # select
621
+ #
622
+ def select()
623
+ resolve
624
+ rv = all.select{|x| yield(x)}
625
+ rv.freeze
626
+ return rv
627
+ end
628
+ #
629
+ # select
630
+ #-----------------------------------------------------------------------------
634
631
  end
635
632
  #
636
633
  # Xeme
634
+ #===============================================================================
635
+
636
+
637
+ #===============================================================================
638
+ # Xeme::Exception
639
+ #
640
+
641
+ # Base class for Xeme-specific exceptions. Each Xeme-specific exception is
642
+ # raised in only one place.
643
+ class Xeme::Exception < StandardError
644
+
645
+ # A human readable string representing the exception.
646
+ KEY = 'exception'
647
+
648
+ # Holds a detail about the exception. Usually holds an invalid value that
649
+ # triggered the exception.
650
+ attr_reader :detail
651
+
652
+ def initialize(p_detail=nil)
653
+ @detail = p_detail
654
+ end
655
+
656
+ # Returns a human readable representation of the exception, including
657
+ # `detail` if provided.
658
+ def message
659
+ rv = self.class::KEY
660
+
661
+ if @detail
662
+ rv += ': ' + @detail.to_s
663
+ end
664
+
665
+ return rv
666
+ end
667
+
668
+ # Raised when a element in `@hsh` cannot be set directly.
669
+ class CannotAssignKey < self
670
+ KEY = 'cannot-assign-to-key'
671
+ end
672
+
673
+ # Raised when a element in `@hsh` cannot be directly deleted.
674
+ class CannotDeleteKey < self
675
+ KEY = 'cannot-assign-to-key'
676
+ end
677
+
678
+ # Raised when a xeme will not allow a specific class of xeme to be nested in
679
+ # itself.
680
+ class InvalidNestClass < self
681
+ KEY = 'invalid-nest-class'
682
+ end
683
+
684
+ # Raised when a xeme will not allow a specific type of xeme to be nested in
685
+ # itself.
686
+ class InvalidNestType < self
687
+ KEY = 'invalid-nest-type'
688
+ end
689
+
690
+ # Raised when an attempt is made to nest a non-advisory xeme in an advisory
691
+ # xeme.
692
+ class CannotNestNonadvisory < self
693
+ KEY = 'cannot-nest-non-advisory-in-advisory'
694
+ end
695
+
696
+ # Raised when an attempt is made to mark a promise as successful when the
697
+ # `supplanted` value is not true.
698
+ class CannotSucceedPromiseUnlessSupplanted < self
699
+ KEY = 'cannot-succeed-promise-unless-supplanted'
700
+ end
701
+ end
702
+ #
703
+ # Xeme::Exception
637
704
  #===============================================================================