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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/lib/enolib.rb +42 -0
- data/lib/enolib/constants.rb +16 -0
- data/lib/enolib/context.rb +220 -0
- data/lib/enolib/elements/element.rb +42 -0
- data/lib/enolib/elements/element_base.rb +141 -0
- data/lib/enolib/elements/empty.rb +9 -0
- data/lib/enolib/elements/field.rb +63 -0
- data/lib/enolib/elements/fieldset.rb +151 -0
- data/lib/enolib/elements/fieldset_entry.rb +15 -0
- data/lib/enolib/elements/list.rb +107 -0
- data/lib/enolib/elements/list_item.rb +13 -0
- data/lib/enolib/elements/missing/missing_element_base.rb +44 -0
- data/lib/enolib/elements/missing/missing_empty.rb +13 -0
- data/lib/enolib/elements/missing/missing_field.rb +13 -0
- data/lib/enolib/elements/missing/missing_fieldset.rb +29 -0
- data/lib/enolib/elements/missing/missing_fieldset_entry.rb +13 -0
- data/lib/enolib/elements/missing/missing_list.rb +33 -0
- data/lib/enolib/elements/missing/missing_section.rb +105 -0
- data/lib/enolib/elements/missing/missing_section_element.rb +53 -0
- data/lib/enolib/elements/missing/missing_value_element_base.rb +21 -0
- data/lib/enolib/elements/section.rb +560 -0
- data/lib/enolib/elements/section_element.rb +141 -0
- data/lib/enolib/elements/value_element_base.rb +79 -0
- data/lib/enolib/errors.rb +25 -0
- data/lib/enolib/errors/parsing.rb +136 -0
- data/lib/enolib/errors/selections.rb +83 -0
- data/lib/enolib/errors/validation.rb +146 -0
- data/lib/enolib/grammar_regexp.rb +103 -0
- data/lib/enolib/lookup.rb +235 -0
- data/lib/enolib/messages/de.rb +79 -0
- data/lib/enolib/messages/en.rb +79 -0
- data/lib/enolib/messages/es.rb +79 -0
- data/lib/enolib/parse.rb +9 -0
- data/lib/enolib/parser.rb +708 -0
- data/lib/enolib/register.rb +24 -0
- data/lib/enolib/reporters/html_reporter.rb +115 -0
- data/lib/enolib/reporters/reporter.rb +258 -0
- data/lib/enolib/reporters/terminal_reporter.rb +107 -0
- data/lib/enolib/reporters/text_reporter.rb +46 -0
- metadata +130 -0
@@ -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,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,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
|