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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class Empty < ElementBase
5
+ def to_s
6
+ "#<Enolib::Empty key=#{@instruction[:key]}>"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class Field < ValueElementBase
5
+ def optional_string_value
6
+ _value(required: false)
7
+ end
8
+
9
+ def optional_value(loader = nil)
10
+ loader = Proc.new if block_given?
11
+
12
+ unless loader
13
+ raise ArgumentError.new('A loader function must be provided')
14
+ end
15
+
16
+ _value(loader, required: false)
17
+ end
18
+
19
+ def parent
20
+ return @parent || Section.new(@context, @instruction[:parent])
21
+ end
22
+
23
+ def required_string_value
24
+ _value(required: true)
25
+ end
26
+
27
+ def required_value(loader = nil)
28
+ loader = Proc.new if block_given?
29
+
30
+ unless loader
31
+ raise ArgumentError.new('A loader function must be provided')
32
+ end
33
+
34
+ _value(loader, required: true)
35
+ end
36
+
37
+ def to_s
38
+ "#<Enolib::Field key=#{@instruction[:key]} value=#{print_value}>"
39
+ end
40
+
41
+ private
42
+
43
+ def _value(loader = nil, required:)
44
+ @touched = true
45
+
46
+ value = @context.value(@instruction)
47
+
48
+ if value
49
+ return value unless loader
50
+
51
+ begin
52
+ loader.call(value)
53
+ rescue => message
54
+ raise Errors::Validation.value_error(@context, message, @instruction)
55
+ end
56
+ else
57
+ return nil unless required
58
+
59
+ raise Errors::Validation.missing_value(@context, @instruction)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class Fieldset < ElementBase
5
+ attr_reader :instruction, :touched
6
+
7
+ def initialize(context, instruction, parent = nil)
8
+ super(context, instruction, parent)
9
+
10
+ @all_entries_required = parent ? parent.all_elements_required? : false
11
+ end
12
+
13
+ def _missing_error(entry)
14
+ raise Errors::Validation.missing_element(@context, entry.key, @instruction, 'missing_fieldset_entry')
15
+ end
16
+
17
+ def _untouched
18
+ return @instruction unless instance_variable_defined?(:@touched)
19
+
20
+ untouched_entry = _entries.find { |entry| !entry.instance_variable_defined?(:@touched) }
21
+
22
+ untouched_entry ? untouched_entry.instruction : false
23
+ end
24
+
25
+ def all_entries_required(required = true)
26
+ @all_entries_required = required
27
+ end
28
+
29
+ def assert_all_touched(message = nil, except: nil, only: nil)
30
+ message = Proc.new if block_given?
31
+
32
+ _entries(map: true).each do |key, entries|
33
+ next if except && except.include?(key) || only && !only.include?(key)
34
+
35
+ entries.each do |entry|
36
+ unless entry.touched # TODO: Revisit instance var existance question in ruby
37
+ if message.is_a?(Proc)
38
+ message = message.call(entry)
39
+ end
40
+
41
+ raise Errors::Validation.unexpected_element(@context, message, entry[:instruction])
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def entries(key = nil)
48
+ @touched = true
49
+
50
+ if key
51
+ entries_map = _entries(map: true)
52
+ entries_map.has_key?(key) ? entries_map[key] : []
53
+ else
54
+ _entries
55
+ end
56
+ end
57
+
58
+ def entry(key = nil)
59
+ _entry(key)
60
+ end
61
+
62
+ def optional_entry(key)
63
+ _entry(key, optional: true)
64
+ end
65
+
66
+ def parent
67
+ @parent || Section.new(@context, @instruction[:parent])
68
+ end
69
+
70
+ def required_entry(key = nil)
71
+ _entry(key, required: true)
72
+ end
73
+
74
+ def to_s
75
+ "#<Enolib::Fieldset key=#{@instruction[:key]} entries=#{_entries.length}>"
76
+ end
77
+
78
+ def touch
79
+ @touched = true
80
+
81
+ _entries.each(&:touch)
82
+ end
83
+
84
+ private
85
+
86
+ def _entries(map: false)
87
+ unless instance_variable_defined?(:@instantiated_entries)
88
+ @instantiated_entries = []
89
+ @instantiated_entries_map = {}
90
+ instantiate_entries(@instruction)
91
+ end
92
+
93
+ map ? @instantiated_entries_map : @instantiated_entries
94
+ end
95
+
96
+ def _entry(key = nil, required: nil)
97
+ @touched = true
98
+
99
+ if key
100
+ entries_map = _entries(map: true)
101
+ entries = entries_map.has_key?(key) ? entries_map[key] : []
102
+ else
103
+ entries = _entries
104
+ end
105
+
106
+ if entries.empty?
107
+ if required || @all_entries_required
108
+ raise Errors::Validation.missing_element(@context, key, @instruction, 'missing_fieldset_entry')
109
+ elsif required == nil
110
+ return MissingFieldsetEntry.new(key, self)
111
+ else
112
+ return nil
113
+ end
114
+ end
115
+
116
+ if entries.length > 1
117
+ raise Errors::Validation.unexpected_multiple_elements(
118
+ @context,
119
+ key,
120
+ entries.map(&:instruction),
121
+ 'expected_single_fieldset_entry'
122
+ )
123
+ end
124
+
125
+ entries[0]
126
+ end
127
+
128
+ def instantiate_entries(fieldset)
129
+ if fieldset.has_key?(:mirror)
130
+ instantiate_entries(fieldset[:mirror])
131
+ elsif fieldset.has_key?(:entries)
132
+ filtered = fieldset[:entries].reject { |entry| @instantiated_entries_map.has_key?(entry[:key]) }
133
+ native_entries = filtered.map do |entry|
134
+ instance = FieldsetEntry.new(@context, entry, self)
135
+
136
+ if @instantiated_entries_map.has_key?(entry[:key])
137
+ @instantiated_entries_map[entry[:key]].push(instance)
138
+ else
139
+ @instantiated_entries_map[entry[:key]] = [instance]
140
+ end
141
+
142
+ instance
143
+ end
144
+
145
+ instantiate_entries(fieldset[:extend]) if fieldset.has_key?(:extend)
146
+
147
+ @instantiated_entries.concat(native_entries)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class FieldsetEntry < ValueElementBase
5
+ attr_reader :instruction # TODO: Revisit this hacky exposition
6
+
7
+ def parent
8
+ @parent || Fieldset.new(@context, @instruction[:parent])
9
+ end
10
+
11
+ def to_s
12
+ "#<Enolib::FieldsetEntry key=#{@instruction[:key]} value=#{print_value}>"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class List < ElementBase
5
+ attr_reader :instruction
6
+
7
+ def _instantiate_items(list)
8
+ if list.has_key?(:mirror)
9
+ _instantiate_items(list[:mirror])
10
+ elsif list.has_key?(:extend)
11
+ _instantiate_items(list[:extend]) + list[:items].map { |item| ListItem.new(@context, item, self) }
12
+ elsif list.has_key?(:items)
13
+ list[:items].map { |item| ListItem.new(@context, item, self) }
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ def _items
20
+ unless instance_variable_defined?(:@instantiated_items)
21
+ @instantiated_items = _instantiate_items(@instruction)
22
+ end
23
+
24
+ @instantiated_items
25
+ end
26
+
27
+ def _untouched
28
+ return @instruction unless instance_variable_defined?(:@touched)
29
+
30
+ untouched_item = _items.find { |item| !item.instance_variable_defined?(:@touched) }
31
+
32
+ untouched_item ? untouched_item.instruction : false
33
+ end
34
+
35
+ def items
36
+ @touched = true
37
+
38
+ _items
39
+ end
40
+
41
+ def length
42
+ @touched = true
43
+
44
+ _items.length
45
+ end
46
+
47
+ def optional_comment(loader = nil)
48
+ loader = Proc.new if block_given?
49
+
50
+ if loader
51
+ _comment(loader, required: false)
52
+ else
53
+ raise ArgumentError.new('A loader block or Proc must be provided')
54
+ end
55
+ end
56
+
57
+ def optional_string_values
58
+ @touched = true
59
+
60
+ _items.map(&:optional_string_value)
61
+ end
62
+
63
+ def optional_values(loader = nil)
64
+ loader = Proc.new if block_given?
65
+
66
+ @touched = true
67
+
68
+ unless loader
69
+ raise ArgumentError.new('A loader function must be provided')
70
+ end
71
+
72
+ _items.map { |item| item.optional_value(loader) }
73
+ end
74
+
75
+ def parent
76
+ @parent || Section.new(@context, @instruction[:parent])
77
+ end
78
+
79
+ def required_string_values
80
+ @touched = true
81
+
82
+ _items.map(&:required_string_value)
83
+ end
84
+
85
+ def required_values(loader = nil)
86
+ loader = Proc.new if block_given?
87
+
88
+ @touched = true
89
+
90
+ unless loader
91
+ raise ArgumentError.new('A loader function must be provided')
92
+ end
93
+
94
+ _items.map { |item| item.required_value(loader) }
95
+ end
96
+
97
+ def to_s
98
+ "#<Enolib::List key=#{@instruction[:key]} items=#{_items.length}>"
99
+ end
100
+
101
+ def touch
102
+ @touched = true
103
+
104
+ _items.each(&:touch)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class ListItem < ValueElementBase
5
+ def parent
6
+ @parent || List.new(@context, @instruction[:parent])
7
+ end
8
+
9
+ def to_s
10
+ "#<Enolib::ListItem value=#{print_value}>"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingElementBase
5
+ attr_reader :parent
6
+
7
+ def initialize(key, parent)
8
+ @key = key
9
+ @parent = parent
10
+ end
11
+
12
+ def _missing_error(_element)
13
+ @parent._missing_error(self)
14
+ end
15
+
16
+ def key(_loader)
17
+ @parent._missing_error(self)
18
+ end
19
+
20
+ def optional_comment(_loader)
21
+ nil
22
+ end
23
+
24
+ def optional_string_comment
25
+ nil
26
+ end
27
+
28
+ def raw
29
+ nil
30
+ end
31
+
32
+ def required_comment(_loader)
33
+ @parent._missing_error(self)
34
+ end
35
+
36
+ def required_string_comment
37
+ @parent._missing_error(self)
38
+ end
39
+
40
+ def string_key
41
+ @parent._missing_error(self)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingEmpty < MissingElementBase
5
+ def to_s
6
+ if @key
7
+ "#<Enolib::MissingEmpty key=#{@key}>"
8
+ else
9
+ '#<Enolib::MissingEmpty>'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingField < MissingValueElementBase
5
+ def to_s
6
+ if @key
7
+ "#<Enolib::MissingField key=#{@key}>"
8
+ else
9
+ '#<Enolib::MissingField>'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class MissingFieldset < MissingElementBase
5
+ def entries(_key = nil)
6
+ []
7
+ end
8
+
9
+ def entry(key = nil)
10
+ MissingFieldsetEntry.new(key, self)
11
+ end
12
+
13
+ def optional_entry(_key = nil)
14
+ nil
15
+ end
16
+
17
+ def required_entry(_key = nil)
18
+ @parent._missing_error(self)
19
+ end
20
+
21
+ def to_s
22
+ if @key
23
+ "#<Enolib::MissingFieldset key=#{@key}>"
24
+ else
25
+ '#<Enolib::MissingFieldset>'
26
+ end
27
+ end
28
+ end
29
+ end