enolib 0.5.0

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