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.
- checksums.yaml +4 -4
- data/README.md +306 -225
- data/lib/xeme.rb +694 -627
- 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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
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
|
#===============================================================================
|