xeme 1.1 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
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
  #===============================================================================