enolib 0.5.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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +52 -0
  4. data/lib/enolib.rb +42 -0
  5. data/lib/enolib/constants.rb +16 -0
  6. data/lib/enolib/context.rb +220 -0
  7. data/lib/enolib/elements/element.rb +42 -0
  8. data/lib/enolib/elements/element_base.rb +141 -0
  9. data/lib/enolib/elements/empty.rb +9 -0
  10. data/lib/enolib/elements/field.rb +63 -0
  11. data/lib/enolib/elements/fieldset.rb +151 -0
  12. data/lib/enolib/elements/fieldset_entry.rb +15 -0
  13. data/lib/enolib/elements/list.rb +107 -0
  14. data/lib/enolib/elements/list_item.rb +13 -0
  15. data/lib/enolib/elements/missing/missing_element_base.rb +44 -0
  16. data/lib/enolib/elements/missing/missing_empty.rb +13 -0
  17. data/lib/enolib/elements/missing/missing_field.rb +13 -0
  18. data/lib/enolib/elements/missing/missing_fieldset.rb +29 -0
  19. data/lib/enolib/elements/missing/missing_fieldset_entry.rb +13 -0
  20. data/lib/enolib/elements/missing/missing_list.rb +33 -0
  21. data/lib/enolib/elements/missing/missing_section.rb +105 -0
  22. data/lib/enolib/elements/missing/missing_section_element.rb +53 -0
  23. data/lib/enolib/elements/missing/missing_value_element_base.rb +21 -0
  24. data/lib/enolib/elements/section.rb +560 -0
  25. data/lib/enolib/elements/section_element.rb +141 -0
  26. data/lib/enolib/elements/value_element_base.rb +79 -0
  27. data/lib/enolib/errors.rb +25 -0
  28. data/lib/enolib/errors/parsing.rb +136 -0
  29. data/lib/enolib/errors/selections.rb +83 -0
  30. data/lib/enolib/errors/validation.rb +146 -0
  31. data/lib/enolib/grammar_regexp.rb +103 -0
  32. data/lib/enolib/lookup.rb +235 -0
  33. data/lib/enolib/messages/de.rb +79 -0
  34. data/lib/enolib/messages/en.rb +79 -0
  35. data/lib/enolib/messages/es.rb +79 -0
  36. data/lib/enolib/parse.rb +9 -0
  37. data/lib/enolib/parser.rb +708 -0
  38. data/lib/enolib/register.rb +24 -0
  39. data/lib/enolib/reporters/html_reporter.rb +115 -0
  40. data/lib/enolib/reporters/reporter.rb +258 -0
  41. data/lib/enolib/reporters/terminal_reporter.rb +107 -0
  42. data/lib/enolib/reporters/text_reporter.rb +46 -0
  43. metadata +130 -0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingFieldsetEntry < MissingValueElementBase
5
+ def to_s
6
+ if @key
7
+ "#<Enolib::MissingFieldsetEntry key=#{@key}>"
8
+ else
9
+ '#<Enolib::MissingFieldsetEntry>'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingList < MissingElementBase
5
+ def items
6
+ []
7
+ end
8
+
9
+ def optional_string_values
10
+ []
11
+ end
12
+
13
+ def optional_values(_loader)
14
+ []
15
+ end
16
+
17
+ def required_string_values
18
+ []
19
+ end
20
+
21
+ def required_values(_loader)
22
+ []
23
+ end
24
+
25
+ def to_s
26
+ if @key
27
+ "#<Enolib::MissingList key=#{@key}>"
28
+ else
29
+ '#<Enolib::MissingList>'
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingSection < MissingElementBase
5
+ def element(key = nil)
6
+ MissingSectionElement.new(key, self)
7
+ end
8
+
9
+ def elements
10
+ []
11
+ end
12
+
13
+ def empty(key = nil)
14
+ MissingEmpty.new(key, self)
15
+ end
16
+
17
+ def field(key = nil)
18
+ MissingField.new(key, self)
19
+ end
20
+
21
+ def fields(_key = nil)
22
+ []
23
+ end
24
+
25
+ def fieldset(key = nil)
26
+ MissingFieldset.new(key, self)
27
+ end
28
+
29
+ def fieldsets(_key = nil)
30
+ []
31
+ end
32
+
33
+ def list(key = nil)
34
+ MissingList.new(key, self)
35
+ end
36
+
37
+ def lists(_key = nil)
38
+ []
39
+ end
40
+
41
+ def optional_element(_key = nil)
42
+ nil
43
+ end
44
+
45
+ def optional_empty(_key = nil)
46
+ nil
47
+ end
48
+
49
+ def optional_field(_key = nil)
50
+ nil
51
+ end
52
+
53
+ def optional_fieldset(_key = nil)
54
+ nil
55
+ end
56
+
57
+ def optional_list(_key = nil)
58
+ nil
59
+ end
60
+
61
+ def optional_section(_key = nil)
62
+ nil
63
+ end
64
+
65
+ def required_element(_key = nil)
66
+ @parent._missing_error(self)
67
+ end
68
+
69
+ def required_empty(_key = nil)
70
+ @parent._missing_error(self)
71
+ end
72
+
73
+ def required_field(_key = nil)
74
+ @parent._missing_error(self)
75
+ end
76
+
77
+ def required_fieldset(_key = nil)
78
+ @parent._missing_error(self)
79
+ end
80
+
81
+ def required_list(_key = nil)
82
+ @parent._missing_error(self)
83
+ end
84
+
85
+ def required_section(_key = nil)
86
+ @parent._missing_error(self)
87
+ end
88
+
89
+ def section(key = nil)
90
+ MissingSection.new(key, self)
91
+ end
92
+
93
+ def sections(_key = nil)
94
+ []
95
+ end
96
+
97
+ def to_s
98
+ if @key
99
+ "#<Enolib::MissingSection key=#{@key}>"
100
+ else
101
+ '#<Enolib::MissingSection>'
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingSectionElement < MissingElementBase
5
+ def to_empty
6
+ MissingEmpty.new(@key, @parent)
7
+ end
8
+
9
+ def to_field
10
+ MissingField.new(@key, @parent)
11
+ end
12
+
13
+ def to_fieldset
14
+ MissingFieldset.new(@key, @parent)
15
+ end
16
+
17
+ def to_list
18
+ MissingList.new(@key, @parent)
19
+ end
20
+
21
+ def to_section
22
+ MissingSection.new(@key, @parent)
23
+ end
24
+
25
+ def yields_empty
26
+ true
27
+ end
28
+
29
+ def yields_field
30
+ true
31
+ end
32
+
33
+ def yields_fieldset
34
+ true
35
+ end
36
+
37
+ def yields_list
38
+ true
39
+ end
40
+
41
+ def yields_section
42
+ true
43
+ end
44
+
45
+ def to_s
46
+ if @key
47
+ "#<Enolib::MissingSectionElement key=#{@key}>"
48
+ else
49
+ '#<Enolib::MissingSectionElement>'
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingValueElementBase < MissingElementBase
5
+ def optional_value(_loader)
6
+ nil
7
+ end
8
+
9
+ def optional_string_value
10
+ nil
11
+ end
12
+
13
+ def required_string_value
14
+ @parent._missing_error(self)
15
+ end
16
+
17
+ def required_value(_loader)
18
+ @parent._missing_error(self)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,560 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: For each value store the representational type as well ? (e.g. string may come from "- foo" or -- foo\nxxx\n-- foo) and use that for precise error messages?
4
+
5
+ module Enolib
6
+ class Section < ElementBase
7
+ def initialize(context, instruction, parent = nil)
8
+ super(context, instruction, parent)
9
+
10
+ @all_elements_required = parent ? parent.all_elements_required? : false
11
+ end
12
+
13
+ def _missing_error(element)
14
+ case element
15
+ when MissingField
16
+ raise Errors::Validation.missing_element(@context, element.instance_variable_get(:@key), @instruction, 'missing_field')
17
+ when MissingFieldset
18
+ raise Errors::Validation.missing_element(@context, element.instance_variable_get(:@key), @instruction, 'missing_fieldset')
19
+ when MissingList
20
+ raise Errors::Validation.missing_element(@context, element.instance_variable_get(:@key), @instruction, 'missing_list')
21
+ when MissingSection
22
+ raise Errors::Validation.missing_element(@context, element.instance_variable_get(:@key), @instruction, 'missing_section')
23
+ else
24
+ raise Errors::Validation.missing_element(@context, element.instance_variable_get(:@key), @instruction, 'missing_element')
25
+ end
26
+ end
27
+
28
+ def _untouched
29
+ return @instruction unless instance_variable_defined?(:@touched)
30
+
31
+ _elements.each do |element|
32
+ untouched_element = element._untouched
33
+ return untouched_element if untouched_element
34
+ end
35
+
36
+ false
37
+ end
38
+
39
+ def all_elements_required(required = true)
40
+ @all_elements_required = required
41
+
42
+ _elements.each do |element|
43
+ if element.instruciton[:type] == :section && element.yielded?
44
+ element.to_section.all_elements_required(required)
45
+ elsif element.instruciton[:type] == :fieldset && element.yielded?
46
+ element.to_fieldset.all_entries_required(required)
47
+ end
48
+ end
49
+ end
50
+
51
+ def all_elements_required?
52
+ @all_elements_required
53
+ end
54
+
55
+ def assert_all_touched(message = nil, except: nil, only: nil)
56
+ message = Proc.new if block_given?
57
+
58
+ _elements(map: true).each do |key, elements|
59
+ next if except && except.include?(key) || only && !only.include?(key)
60
+
61
+ elements.each do |element|
62
+ untouched = element._untouched
63
+
64
+ if untouched
65
+ if message.is_a?(Proc)
66
+ message = message.call(Element.new(@context, untouched, self))
67
+ end
68
+
69
+ raise Errors::Validation.unexpected_element(@context, message, untouched)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def element(key = nil)
76
+ _element(key)
77
+ end
78
+
79
+ def elements(key = nil)
80
+ @touched = true
81
+
82
+ if key
83
+ elements_map = _elements(map: true)
84
+ elements_map.has_key?(key) ? elements_map[key] : []
85
+ else
86
+ _elements
87
+ end
88
+ end
89
+
90
+ def empty(key = nil)
91
+ _empty(key)
92
+ end
93
+
94
+ def field(key = nil)
95
+ _field(key)
96
+ end
97
+
98
+ def fields(key = nil)
99
+ @touched = true
100
+
101
+ if key
102
+ elements_map = _elements(map: true)
103
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
104
+ else
105
+ elements = _elements
106
+ end
107
+
108
+ elements.map do |element|
109
+ unless element.yields_field?
110
+ raise Errors::Validation.unexpected_element_type(
111
+ @context,
112
+ key,
113
+ element.instruction,
114
+ 'expected_fields'
115
+ )
116
+ end
117
+
118
+ element.to_field
119
+ end
120
+ end
121
+
122
+ def fieldset(key = nil)
123
+ _fieldset(key)
124
+ end
125
+
126
+ def fieldsets(key = nil)
127
+ @touched = true
128
+
129
+ if key
130
+ elements_map = _elements(map: true)
131
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
132
+ else
133
+ elements = _elements
134
+ end
135
+
136
+ elements.map do |element|
137
+ unless element.yields_fieldset?
138
+ raise Errors::Validation.unexpected_element_type(
139
+ @context,
140
+ key,
141
+ element.instruction,
142
+ 'expected_fieldsets'
143
+ )
144
+ end
145
+
146
+ element.to_fieldset
147
+ end
148
+ end
149
+
150
+ def list(key = nil)
151
+ _list(key)
152
+ end
153
+
154
+ def lists(key = nil)
155
+ @touched = true
156
+
157
+ if key
158
+ elements_map = _elements(map: true)
159
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
160
+ else
161
+ elements = _elements
162
+ end
163
+
164
+ elements.map do |element|
165
+ unless element.yields_list?
166
+ raise Errors::Validation.unexpected_element_type(
167
+ @context,
168
+ key,
169
+ element.instruction,
170
+ 'expected_lists'
171
+ )
172
+ end
173
+
174
+ element.to_list
175
+ end
176
+ end
177
+
178
+ def optional_element(key = nil)
179
+ _element(key, required: false)
180
+ end
181
+
182
+ def optional_empty(key = nil)
183
+ _empty(key, required: false)
184
+ end
185
+
186
+ def optional_field(key = nil)
187
+ _field(key, required: false)
188
+ end
189
+
190
+ def optional_fieldset(key = nil)
191
+ _fieldset(key, required: false)
192
+ end
193
+
194
+ def optional_list(key = nil)
195
+ _list(key, required: false)
196
+ end
197
+
198
+ def optional_section(key = nil)
199
+ _section(key, required: false)
200
+ end
201
+
202
+ def parent
203
+ if @instruction[:type] == :document
204
+ nil
205
+ else
206
+ @parent || Section.new(@context, @instruction[:parent])
207
+ end
208
+ end
209
+
210
+ def required_element(key = nil)
211
+ _element(key, required: true)
212
+ end
213
+
214
+ def required_empty(key = nil)
215
+ _empty(key, required: true)
216
+ end
217
+
218
+ def required_field(key = nil)
219
+ _field(key, required: true)
220
+ end
221
+
222
+ def required_fieldset(key = nil)
223
+ _fieldset(key, required: true)
224
+ end
225
+
226
+ def required_list(key = nil)
227
+ _list(key, required: true)
228
+ end
229
+
230
+ def required_section(key = nil)
231
+ _section(key, required: true)
232
+ end
233
+
234
+ def section(key = nil)
235
+ _section(key)
236
+ end
237
+
238
+ def sections(key = nil)
239
+ @touched = true
240
+
241
+ if key
242
+ elements_map = _elements(map: true)
243
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
244
+ else
245
+ elements = _elements
246
+ end
247
+
248
+ elements.map do |element|
249
+ unless element.yields_section?
250
+ raise Errors::Validation.unexpected_element_type(
251
+ @context,
252
+ key,
253
+ element.instruction,
254
+ 'expected_sections'
255
+ )
256
+ end
257
+
258
+ element.to_section
259
+ end
260
+ end
261
+
262
+ def to_s
263
+ if @instruction[:type] == :document
264
+ "#<Enolib::Section document elements=#{elements.length}>"
265
+ else
266
+ "#<Enolib::Section key=#{@instruction[:key]} elements=#{elements.length}>"
267
+ end
268
+ end
269
+
270
+ def touch
271
+ @touched = true
272
+
273
+ _elements.each do |element|
274
+ element.touch
275
+ end
276
+ end
277
+
278
+ private
279
+
280
+ def _element(key = nil, required: nil)
281
+ @touched = true
282
+
283
+ if key
284
+ elements_map = _elements(map: true)
285
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
286
+ else
287
+ elements = _elements
288
+ end
289
+
290
+ if elements.empty?
291
+ if required || @all_elements_required
292
+ raise Errors::Validation.missing_element(@context, key, @instruction, 'missing_element')
293
+ elsif required == nil
294
+ return MissingElement.new(key, self)
295
+ else
296
+ return nil
297
+ end
298
+ end
299
+
300
+ if elements.length > 1
301
+ raise Errors::Validation.unexpected_multiple_elements(
302
+ @context,
303
+ key,
304
+ elements.map(&:instruction),
305
+ 'expected_single_element'
306
+ )
307
+ end
308
+
309
+ elements[0]
310
+ end
311
+
312
+ def _elements(map: false)
313
+ unless instance_variable_defined?(:@instantiated_elements)
314
+ @instantiated_elements = []
315
+ @instantiated_elements_map = {}
316
+ instantiate_elements(@instruction)
317
+ end
318
+
319
+ map ? @instantiated_elements_map : @instantiated_elements
320
+ end
321
+
322
+ def _empty(key = nil, required: nil)
323
+ @touched = true
324
+
325
+ if key
326
+ elements_map = _elements(map: true)
327
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
328
+ else
329
+ elements = _elements
330
+ end
331
+
332
+ if elements.empty?
333
+ if required || @all_elements_required
334
+ raise Errors::Validation.missing_element(@context, key, @instruction, 'missing_empty')
335
+ elsif required == nil
336
+ return MissingEmpty.new(key, self)
337
+ else
338
+ return nil
339
+ end
340
+ end
341
+
342
+ if elements.length > 1
343
+ raise Errors::Validation.unexpected_multiple_elements(
344
+ @context,
345
+ key,
346
+ elements.map(&:instruction),
347
+ 'expected_single_empty'
348
+ )
349
+ end
350
+
351
+ element = elements[0]
352
+
353
+ unless element.yields_empty?
354
+ raise Errors::Validation.unexpected_element_type(
355
+ @context,
356
+ key,
357
+ element.instruction,
358
+ 'expected_empty'
359
+ )
360
+ end
361
+
362
+ element.to_empty
363
+ end
364
+
365
+ def _field(key = nil, required: nil)
366
+ @touched = true
367
+
368
+ if key
369
+ elements_map = _elements(map: true)
370
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
371
+ else
372
+ elements = _elements
373
+ end
374
+
375
+ if elements.empty?
376
+ if required || @all_elements_required
377
+ raise Errors::Validation.missing_element(@context, key, @instruction, 'missing_field')
378
+ elsif required == nil
379
+ return MissingField.new(key, self)
380
+ else
381
+ return nil
382
+ end
383
+ end
384
+
385
+ if elements.length > 1
386
+ raise Errors::Validation.unexpected_multiple_elements(
387
+ @context,
388
+ key,
389
+ elements.map(&:instruction),
390
+ 'expected_single_field'
391
+ )
392
+ end
393
+
394
+ element = elements[0]
395
+
396
+ unless element.yields_field?
397
+ raise Errors::Validation.unexpected_element_type(
398
+ @context,
399
+ key,
400
+ element.instruction,
401
+ 'expected_field'
402
+ )
403
+ end
404
+
405
+ element.to_field
406
+ end
407
+
408
+ def _fieldset(key = nil, required: nil)
409
+ @touched = true
410
+
411
+ if key
412
+ elements_map = _elements(map: true)
413
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
414
+ else
415
+ elements = _elements
416
+ end
417
+
418
+ if elements.empty?
419
+ if required || @all_elements_required
420
+ raise Errors::Validation.missing_element(@context, key, @instruction, 'missing_fieldset')
421
+ elsif required == nil
422
+ return MissingFieldset.new(key, self)
423
+ else
424
+ return nil
425
+ end
426
+ end
427
+
428
+ if elements.length > 1
429
+ raise Errors::Validation.unexpected_multiple_elements(
430
+ @context,
431
+ key,
432
+ elements.map(&:instruction),
433
+ 'expected_single_fieldset'
434
+ )
435
+ end
436
+
437
+ element = elements[0]
438
+
439
+ unless element.yields_fieldset?
440
+ raise Errors::Validation.unexpected_element_type(
441
+ @context,
442
+ key,
443
+ element.instruction,
444
+ 'expected_fieldset'
445
+ )
446
+ end
447
+
448
+ element.to_fieldset
449
+ end
450
+
451
+ def instantiate_elements(section)
452
+ if section.has_key?(:mirror)
453
+ instantiate_elements(section[:mirror])
454
+ else
455
+ filtered = section[:elements].reject { |element| @instantiated_elements_map.has_key?(element[:key]) }
456
+ instantiated = filtered.map do |element|
457
+ instance = SectionElement.new(@context, element, self)
458
+
459
+ if @instantiated_elements_map.has_key?(element[:key])
460
+ @instantiated_elements_map[element[:key]].push(instance)
461
+ else
462
+ @instantiated_elements_map[element[:key]] = [instance]
463
+ end
464
+
465
+ instance
466
+ end
467
+
468
+ @instantiated_elements.concat(instantiated) # TODO: Revisit order of this and the following
469
+
470
+ instantiate_elements(section[:extend]) if section.has_key?(:extend)
471
+ end
472
+ end
473
+
474
+ def _list(key = nil, required: nil)
475
+ @touched = true
476
+
477
+ if key
478
+ elements_map = _elements(map: true)
479
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
480
+ else
481
+ elements = _elements
482
+ end
483
+
484
+ if elements.empty?
485
+ if required || @all_elements_required
486
+ raise Errors::Validation.missing_element(@context, key, @instruction, 'missing_list')
487
+ elsif required == nil
488
+ return MissingList.new(key, self)
489
+ else
490
+ return nil
491
+ end
492
+ end
493
+
494
+ if elements.length > 1
495
+ raise Errors::Validation.unexpected_multiple_elements(
496
+ @context,
497
+ key,
498
+ elements.map(&:instruction),
499
+ 'expected_single_list'
500
+ )
501
+ end
502
+
503
+ element = elements[0]
504
+
505
+ unless element.yields_list?
506
+ raise Errors::Validation.unexpected_element_type(
507
+ @context,
508
+ key,
509
+ element.instruction,
510
+ 'expected_list'
511
+ )
512
+ end
513
+
514
+ element.to_list
515
+ end
516
+
517
+ def _section(key = nil, required: nil)
518
+ @touched = true
519
+
520
+ if key
521
+ elements_map = _elements(map: true)
522
+ elements = elements_map.has_key?(key) ? elements_map[key] : []
523
+ else
524
+ elements = _elements
525
+ end
526
+
527
+ if elements.empty?
528
+ if required || @all_elements_required
529
+ raise Errors::Validation.missing_element(@context, key, @instruction, 'missing_section')
530
+ elsif required == nil
531
+ return MissingSection.new(key, self)
532
+ else
533
+ return nil
534
+ end
535
+ end
536
+
537
+ if elements.length > 1
538
+ raise Errors::Validation.unexpected_multiple_elements(
539
+ @context,
540
+ key,
541
+ elements.map(&:instruction),
542
+ 'expected_single_section'
543
+ )
544
+ end
545
+
546
+ element = elements[0]
547
+
548
+ unless element.yields_section?
549
+ raise Errors::Validation.unexpected_element_type(
550
+ @context,
551
+ key,
552
+ element.instruction,
553
+ 'expected_section'
554
+ )
555
+ end
556
+
557
+ element.to_section
558
+ end
559
+ end
560
+ end