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