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,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enolib
|
4
|
+
module Errors
|
5
|
+
module Validation
|
6
|
+
def self.comment_error(context, message, element)
|
7
|
+
ValidationError.new(
|
8
|
+
context.messages.comment_error(message),
|
9
|
+
context.reporter.new(context).report_comments(element).snippet(),
|
10
|
+
Selections::select_comments(element)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.element_error(context, message, element)
|
15
|
+
ValidationError.new(
|
16
|
+
message,
|
17
|
+
context.reporter.new(context).report_element(element).snippet(),
|
18
|
+
Selections::select_element(element)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.key_error(context, message, element)
|
23
|
+
ValidationError.new(
|
24
|
+
context.messages.key_error(message),
|
25
|
+
context.reporter.new(context).report_line(element).snippet(),
|
26
|
+
Selections::select_key(element)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.missing_comment(context, element)
|
31
|
+
ValidationError.new(
|
32
|
+
context.messages::MISSING_COMMENT,
|
33
|
+
context.reporter.new(context).report_line(element).snippet(), # TODO: Question-tag an empty line before an element with missing comment
|
34
|
+
Selections::selection(element, :line, RANGE_BEGIN)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.missing_element(context, key, parent, message)
|
39
|
+
ValidationError.new(
|
40
|
+
key ? context.messages.send(message + '_with_key', key) : context.messages.const_get(message.upcase), # TODO: Solve the upcase rather through a different generated message type, e.g. method instead of constant
|
41
|
+
context.reporter.new(context).report_missing_element(parent).snippet(),
|
42
|
+
parent[:type] == :document ? Selections::DOCUMENT_BEGIN : Selections::selection(parent, :line, RANGE_END)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.missing_value(context, element)
|
47
|
+
selection = {}
|
48
|
+
|
49
|
+
if element[:type] == :field ||
|
50
|
+
element[:type] == :empty_element ||
|
51
|
+
element[:type] == :multiline_field_begin
|
52
|
+
message = context.messages.missing_field_value(element[:key])
|
53
|
+
|
54
|
+
if element[:ranges].has_key?(:template)
|
55
|
+
selection[:from] = Selections::cursor(element, :template, RANGE_END)
|
56
|
+
elsif element[:ranges].has_key?(:element_operator)
|
57
|
+
selection[:from] = Selections::cursor(element, :element_operator, RANGE_END)
|
58
|
+
else
|
59
|
+
selection[:from] = Selections::cursor(element, :line, RANGE_END)
|
60
|
+
end
|
61
|
+
elsif element[:type] == :fieldset_entry
|
62
|
+
message = context.messages.missing_fieldset_entry_value(element[:key])
|
63
|
+
selection[:from] = Selections::cursor(element, :entry_operator, RANGE_END)
|
64
|
+
elsif element[:type] == :list_item
|
65
|
+
message = context.messages.missing_list_item_value(element[:parent][:key])
|
66
|
+
selection[:from] = Selections::cursor(element, :item_operator, RANGE_END)
|
67
|
+
end
|
68
|
+
|
69
|
+
snippet = context.reporter.new(context).report_element(element).snippet()
|
70
|
+
|
71
|
+
if element[:type] == :field and element.has_key?(:continuations)
|
72
|
+
selection[:to] = Selections::cursor(element[:continuations].last, :line, RANGE_END)
|
73
|
+
else
|
74
|
+
selection[:to] = Selections::cursor(element, :line, RANGE_END)
|
75
|
+
end
|
76
|
+
|
77
|
+
ValidationError.new(message, snippet, selection)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.unexpected_element(context, message, element)
|
81
|
+
ValidationError.new(
|
82
|
+
message || context.messages::UNEXPECTED_ELEMENT,
|
83
|
+
context.reporter.new(context).report_element(element).snippet(),
|
84
|
+
Selections::select_element(element)
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.unexpected_multiple_elements(context, key, elements, message)
|
89
|
+
ValidationError.new(
|
90
|
+
key ? context.messages.send(message + '_with_key', key) : context.messages.const_get(message.upcase), # TODO: Solve the upcase rather through a different generated message type, e.g. method instead of constant
|
91
|
+
context.reporter.new(context).report_elements(elements).snippet(),
|
92
|
+
Selections::select_element(elements[0])
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.unexpected_element_type(context, key, section, message)
|
97
|
+
ValidationError.new(
|
98
|
+
key ? context.messages.send(message + '_with_key', key) : context.messages.const_get(message.upcase), # TODO: Solve the upcase rather through a different generated message type, e.g. method instead of constant
|
99
|
+
context.reporter.new(context).report_element(section).snippet(),
|
100
|
+
Selections::select_element(section)
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.value_error(context, message, element)
|
105
|
+
if element.has_key?(:mirror)
|
106
|
+
snippet = context.reporter.new(context).report_line(element).snippet()
|
107
|
+
select = select_key(element)
|
108
|
+
elsif element[:type] == :multiline_field_begin
|
109
|
+
if element.has_key?(:lines)
|
110
|
+
snippet = context.reporter.new(context).report_multiline_value(element).snippet()
|
111
|
+
select = Selections::selection(element[:lines][0], :line, RANGE_BEGIN, element[:lines][-1], :line, RANGE_END)
|
112
|
+
else
|
113
|
+
snippet = context.reporter.new(context).report_element(element).snippet()
|
114
|
+
select = Selections::selection(element, :line, RANGE_END)
|
115
|
+
end
|
116
|
+
else
|
117
|
+
snippet = context.reporter.new(context).report_element(element).snippet()
|
118
|
+
select = {}
|
119
|
+
|
120
|
+
if element[:ranges].has_key?(:value)
|
121
|
+
select[:from] = Selections::cursor(element, :value, RANGE_BEGIN)
|
122
|
+
elsif element[:ranges].has_key?(:element_operator)
|
123
|
+
select[:from] = Selections::cursor(element, :element_operator, RANGE_END)
|
124
|
+
elsif element[:ranges].has_key?(:entry_operator)
|
125
|
+
select[:from] = Selections::cursor(element, :entry_operator, RANGE_END)
|
126
|
+
elsif element[:type] == :list_item
|
127
|
+
select[:from] = Selections::cursor(element, :item_operator, RANGE_END)
|
128
|
+
else
|
129
|
+
# TODO: Possibly never reached - think through state permutations
|
130
|
+
select[:from] = Selections::cursor(element, :line, RANGE_END)
|
131
|
+
end
|
132
|
+
|
133
|
+
if element.has_key?(:continuations)
|
134
|
+
select[:to] = Selections::cursor(element[:continuations][-1], :line, RANGE_END)
|
135
|
+
elsif element[:ranges].has_key?(:value)
|
136
|
+
select[:to] = Selections::cursor(element, :value, RANGE_END)
|
137
|
+
else
|
138
|
+
select[:to] = Selections::cursor(element, :line, RANGE_END)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
ValidationError.new(context.messages.value_error(message), snippet, select)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Note: Study this file from the bottom up
|
4
|
+
|
5
|
+
# TODO: Try out possesive quantifiers (careful x{2,}+ does not work in ruby, only xx++ (!)) - benchmark?
|
6
|
+
|
7
|
+
module Enolib
|
8
|
+
module Grammar
|
9
|
+
OPTIONAL = /([^\n]+?)?/.source
|
10
|
+
REQUIRED = /(\S[^\n]*?)/.source
|
11
|
+
|
12
|
+
#
|
13
|
+
EMPTY = /()/.source
|
14
|
+
EMPTY_LINE_INDEX = 1
|
15
|
+
|
16
|
+
# | value
|
17
|
+
DIRECT_LINE_CONTINUATION = /(\|)[^\S\n]*#{OPTIONAL}/.source
|
18
|
+
DIRECT_LINE_CONTINUATION_OPERATOR_INDEX = 2
|
19
|
+
DIRECT_LINE_CONTINUATION_VALUE_INDEX = 3
|
20
|
+
|
21
|
+
# \ value
|
22
|
+
SPACED_LINE_CONTINUATION = /(\\)[^\S\n]*#{OPTIONAL}/.source
|
23
|
+
SPACED_LINE_CONTINUATION_OPERATOR_INDEX = 4
|
24
|
+
SPACED_LINE_CONTINUATION_VALUE_INDEX = 5
|
25
|
+
|
26
|
+
CONTINUATION = /#{DIRECT_LINE_CONTINUATION}|#{SPACED_LINE_CONTINUATION}/.source
|
27
|
+
|
28
|
+
# > Comment
|
29
|
+
COMMENT = /(>)[^\S\n]*#{OPTIONAL}/.source
|
30
|
+
COMMENT_OPERATOR_INDEX = 6
|
31
|
+
COMMENT_VALUE_INDEX = 7
|
32
|
+
|
33
|
+
# - value
|
34
|
+
LIST_ITEM = /(-)(?!-)[^\S\n]*#{OPTIONAL}/.source
|
35
|
+
LIST_ITEM_OPERATOR_INDEX = 8
|
36
|
+
LIST_ITEM_VALUE_INDEX = 9
|
37
|
+
|
38
|
+
# -- key
|
39
|
+
MULTILINE_FIELD = /(-{2,})(?!-)[^\S\n]*#{REQUIRED}/.source
|
40
|
+
MULTILINE_FIELD_OPERATOR_INDEX = 10
|
41
|
+
MULTILINE_FIELD_KEY_INDEX = 11
|
42
|
+
|
43
|
+
# #
|
44
|
+
SECTION_OPERATOR = /(#+)(?!#)/.source
|
45
|
+
SECTION_OPERATOR_INDEX = 12
|
46
|
+
|
47
|
+
# # key
|
48
|
+
SECTION_KEY_UNESCAPED = /([^\s`<][^<\n]*?)/.source
|
49
|
+
SECTION_KEY_UNESCAPED_INDEX = 13
|
50
|
+
|
51
|
+
# # `key`
|
52
|
+
SECTION_KEY_ESCAPE_BEGIN_OPERATOR_INDEX = 14
|
53
|
+
SECTION_KEY_ESCAPED = /(`+)(?!`)[^\S\n]*(\S[^\n]*?)[^\S\n]*(#{"\\#{SECTION_KEY_ESCAPE_BEGIN_OPERATOR_INDEX}"})/.source # TODO: Should this exclude the backreference inside the quotes? (as in ((?:(?!\1).)+) ) here and elsewhere (probably not because it's not greedy.?!
|
54
|
+
SECTION_KEY_ESCAPED_INDEX = 15
|
55
|
+
SECTION_KEY_ESCAPE_END_OPERATOR_INDEX = 16
|
56
|
+
|
57
|
+
# # key < template
|
58
|
+
# # `key` < template
|
59
|
+
SECTION_KEY = /(?:#{SECTION_KEY_UNESCAPED}|#{SECTION_KEY_ESCAPED})/.source
|
60
|
+
SECTION_TEMPLATE = /(?:(<(?!<)|<<)[^\S\n]*#{REQUIRED})?/.source
|
61
|
+
SECTION = /#{SECTION_OPERATOR}\s*#{SECTION_KEY}[^\S\n]*#{SECTION_TEMPLATE}/.source
|
62
|
+
SECTION_COPY_OPERATOR_INDEX = 17
|
63
|
+
SECTION_TEMPLATE_INDEX = 18
|
64
|
+
|
65
|
+
EARLY_DETERMINED = /#{CONTINUATION}|#{COMMENT}|#{LIST_ITEM}|#{MULTILINE_FIELD}|#{SECTION}/.source
|
66
|
+
|
67
|
+
# key
|
68
|
+
KEY_UNESCAPED = /([^\s>#\-`\\|:=<][^\n:=<]*?)/.source
|
69
|
+
KEY_UNESCAPED_INDEX = 19
|
70
|
+
|
71
|
+
# `key`
|
72
|
+
KEY_ESCAPE_BEGIN_OPERATOR_INDEX = 20
|
73
|
+
KEY_ESCAPED = /(`+)(?!`)[^\S\n]*(\S[^\n]*?)[^\S\n]*(#{"\\#{KEY_ESCAPE_BEGIN_OPERATOR_INDEX}"})/.source
|
74
|
+
KEY_ESCAPED_INDEX = 21
|
75
|
+
KEY_ESCAPE_END_OPERATOR_INDEX = 22
|
76
|
+
|
77
|
+
KEY = /(?:#{KEY_UNESCAPED}|#{KEY_ESCAPED})/.source
|
78
|
+
|
79
|
+
# :
|
80
|
+
# : value
|
81
|
+
ELEMENT_OR_FIELD = /(:)[^\S\n]*#{OPTIONAL}/.source
|
82
|
+
ELEMENT_OPERATOR_INDEX = 23
|
83
|
+
FIELD_VALUE_INDEX = 24
|
84
|
+
|
85
|
+
# =
|
86
|
+
# = value
|
87
|
+
FIELDSET_ENTRY = /(=)[^\S\n]*#{OPTIONAL}/.source
|
88
|
+
FIELDSET_ENTRY_OPERATOR_INDEX = 25
|
89
|
+
FIELDSET_ENTRY_VALUE_INDEX = 26
|
90
|
+
|
91
|
+
# < template
|
92
|
+
# << template
|
93
|
+
COPY = /(<(?!<)|<<)\s*#{REQUIRED}/.source
|
94
|
+
COPY_OPERATOR_INDEX = 27
|
95
|
+
TEMPLATE_INDEX = 28
|
96
|
+
|
97
|
+
LATE_DETERMINED = /#{KEY}\s*(?:#{ELEMENT_OR_FIELD}|#{FIELDSET_ENTRY}|#{COPY})/.source
|
98
|
+
|
99
|
+
NOT_EMPTY = /(?:#{EARLY_DETERMINED}|#{LATE_DETERMINED})/.source
|
100
|
+
|
101
|
+
REGEX = /[^\S\n]*(?:#{EMPTY}|#{NOT_EMPTY})[^\S\n]*(?=\n|$)/
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
RANGE_BEGIN = 0
|
2
|
+
RANGE_END = 1
|
3
|
+
|
4
|
+
def check_multiline_field_by_line(field, line)
|
5
|
+
return false if line < field[:line] ||
|
6
|
+
line > field[:end][:line]
|
7
|
+
|
8
|
+
if line == field[:line]
|
9
|
+
{ element: field, instruction: field }
|
10
|
+
elsif line == field[:end][:line]
|
11
|
+
{ element: field, instruction: field[:end] }
|
12
|
+
else
|
13
|
+
{
|
14
|
+
element: field,
|
15
|
+
instruction: field[:lines].find { |candidate| candidate[:line] == line }
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def check_multiline_field_by_index(field, index)
|
21
|
+
return false if index < field[:ranges][:line][RANGE_BEGIN] ||
|
22
|
+
index > field[:end][:ranges][:line][RANGE_END]
|
23
|
+
|
24
|
+
if index <= field[:ranges][:line][RANGE_END]
|
25
|
+
{ element: field, instruction: field }
|
26
|
+
elsif index >= field[:end][:ranges][:line][RANGE_BEGIN]
|
27
|
+
{ element: field, instruction: field[:end] }
|
28
|
+
else
|
29
|
+
{
|
30
|
+
element: field,
|
31
|
+
instruction: field[:lines].find { |candidate| index <= candidate[:ranges][:line][RANGE_END] }
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_field_by_line(field, line)
|
37
|
+
return false if line < field[:line]
|
38
|
+
return { element: field, instruction: field } if line == field[:line]
|
39
|
+
return false unless field.has_key?(:continuations) &&
|
40
|
+
line <= field[:continuations].last[:line]
|
41
|
+
|
42
|
+
field[:continuations].each do |continuation|
|
43
|
+
return { element: field, instruction: continuation } if line == continuation[:line]
|
44
|
+
return { element: field, instruction: nil } if line < continuation[:line]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_field_by_index(field, index)
|
49
|
+
return false if index < field[:ranges][:line][RANGE_BEGIN]
|
50
|
+
return { element: field, instruction: field } if index <= field[:ranges][:line][RANGE_END]
|
51
|
+
return false unless field.has_key?(:continuations) &&
|
52
|
+
index <= field[:continuations].last[:ranges][:line][RANGE_END]
|
53
|
+
|
54
|
+
field[:continuations].each do |continuation|
|
55
|
+
return { element: field, instruction: nil } if index < continuation[:ranges][:line][RANGE_BEGIN]
|
56
|
+
return { element: field, instruction: continuation } if index <= continuation[:ranges][:line][RANGE_END]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def check_fieldset_by_line(fieldset, line)
|
61
|
+
return false if line < fieldset[:line]
|
62
|
+
return { element: fieldset, instruction: fieldset } if line == fieldset[:line]
|
63
|
+
return false unless fieldset.has_key?(:entries) &&
|
64
|
+
line <= fieldset[:entries].last[:line]
|
65
|
+
|
66
|
+
|
67
|
+
fieldset[:entries].each do |entry|
|
68
|
+
return { element: entry, instruction: entry } if line == entry[:line]
|
69
|
+
return { element: fieldset, instruction: nil } if line < entry[:line]
|
70
|
+
|
71
|
+
match_in_entry = check_field_by_line(entry, line)
|
72
|
+
|
73
|
+
return match_in_entry if match_in_entry
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_fieldset_by_index(fieldset, index)
|
78
|
+
return false if index < fieldset[:ranges][:line][RANGE_BEGIN]
|
79
|
+
return { element: fieldset, instruction: fieldset } if index <= fieldset[:ranges][:line][RANGE_END]
|
80
|
+
return false unless fieldset.has_key?(:entries) &&
|
81
|
+
index <= fieldset[:entries].last[:ranges][:line][RANGE_END]
|
82
|
+
|
83
|
+
fieldset[:entries].each do |entry|
|
84
|
+
return { element: fieldset, instruction: nil } if index < entry[:ranges][:line][RANGE_BEGIN]
|
85
|
+
return { element: entry, instruction: entry } if index <= entry[:ranges][:line][RANGE_END]
|
86
|
+
|
87
|
+
match_in_entry = check_field_by_index(entry, index)
|
88
|
+
|
89
|
+
return match_in_entry if match_in_entry
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_list_by_line(list, line)
|
94
|
+
return false if line < list[:line]
|
95
|
+
return { element: list, instruction: list } if line == list[:line]
|
96
|
+
return false unless list.has_key?(:items) && line > list[:items].last[:line]
|
97
|
+
|
98
|
+
list[:items].each do |item|
|
99
|
+
return { element: item, instruction: item } if line == item[:line]
|
100
|
+
return { element: list, instruction: nil } if line < item[:line]
|
101
|
+
|
102
|
+
match_in_item = check_field_by_line(item, line)
|
103
|
+
|
104
|
+
return match_in_item if match_in_item
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def check_list_by_index(list, index)
|
109
|
+
return false if index < list[:ranges][:line][RANGE_BEGIN]
|
110
|
+
return { element: list, instruction: list } if index <= list[:ranges][:line][RANGE_END]
|
111
|
+
return false unless list.has_key?(:items) &&
|
112
|
+
index > list[:items].last[:ranges][:line][RANGE_END]
|
113
|
+
|
114
|
+
list[:items].each do |item|
|
115
|
+
return { element: list, instruction: nil } if index < item[:ranges][:line][RANGE_BEGIN]
|
116
|
+
return { element: item, instruction: item } if index <= item[:ranges][:line][RANGE_END]
|
117
|
+
|
118
|
+
match_in_item = check_field_by_index(item, index)
|
119
|
+
|
120
|
+
return match_in_item if match_in_item
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def check_in_section_by_line(section, line)
|
125
|
+
section[:elements].reverse_each do |element|
|
126
|
+
next if element[:line] > line
|
127
|
+
|
128
|
+
return { element: element, instruction: element } if element[:line] == line
|
129
|
+
|
130
|
+
case element[:type]
|
131
|
+
when :field
|
132
|
+
match_in_field = check_field_by_line(element, line)
|
133
|
+
return match_in_field if match_in_field
|
134
|
+
when :fieldset
|
135
|
+
match_in_fieldset = check_fieldset_by_line(element, line)
|
136
|
+
return match_in_fieldset if match_in_fieldset
|
137
|
+
when :list
|
138
|
+
match_in_list = check_list_by_line(element, line)
|
139
|
+
return match_in_list if match_in_list
|
140
|
+
when :multiline_field_begin
|
141
|
+
unless element.has_key?(:template)
|
142
|
+
match_in_multiline_field = check_multiline_field_by_line(element, line)
|
143
|
+
return match_in_multiline_field if match_in_multiline_field
|
144
|
+
end
|
145
|
+
when :section
|
146
|
+
return check_in_section_by_line(element, line)
|
147
|
+
end
|
148
|
+
|
149
|
+
break
|
150
|
+
end
|
151
|
+
|
152
|
+
{ element: section, instruction: nil }
|
153
|
+
end
|
154
|
+
|
155
|
+
def check_in_section_by_index(section, index)
|
156
|
+
section[:elements].reverse_each do |element|
|
157
|
+
next if index < element[:ranges][:line][RANGE_BEGIN]
|
158
|
+
|
159
|
+
return { element: element, instruction: element } if index <= element[:ranges][:line][RANGE_END]
|
160
|
+
|
161
|
+
case element[:type]
|
162
|
+
when :field
|
163
|
+
match_in_field = check_field_by_index(element, index)
|
164
|
+
return match_in_field if match_in_field
|
165
|
+
when :fieldset
|
166
|
+
match_in_fieldset = check_fieldset_by_index(element, index)
|
167
|
+
return match_in_fieldset if match_in_fieldset
|
168
|
+
when :list
|
169
|
+
match_in_list = check_list_by_index(element, index)
|
170
|
+
return match_in_list if match_in_list
|
171
|
+
when :multiline_field_begin
|
172
|
+
unless element.has_key?(:template)
|
173
|
+
match_in_multiline_field = check_multiline_field_by_index(element, index)
|
174
|
+
return match_in_multiline_field if match_in_multiline_field
|
175
|
+
end
|
176
|
+
when :section
|
177
|
+
return check_in_section_by_index(element, index)
|
178
|
+
end
|
179
|
+
|
180
|
+
break
|
181
|
+
end
|
182
|
+
|
183
|
+
{ element: section, instruction: nil }
|
184
|
+
end
|
185
|
+
|
186
|
+
module Enolib
|
187
|
+
def self.lookup(input, column: nil, index: nil, line: nil, **options)
|
188
|
+
context = Context.new(input, **options)
|
189
|
+
|
190
|
+
match = nil
|
191
|
+
if index
|
192
|
+
if index < 0 || index > context.input.length
|
193
|
+
raise IndexError.new("You are trying to look up an index (#{index}) outside of the document's index range (0-#{context.input.length})")
|
194
|
+
end
|
195
|
+
|
196
|
+
match = check_in_section_by_index(context.document, index)
|
197
|
+
else
|
198
|
+
if line < 0 || line >= context.line_count
|
199
|
+
raise IndexError.new("You are trying to look up a line (#{line}) outside of the document's line range (0-#{context.line_count - 1})")
|
200
|
+
end
|
201
|
+
|
202
|
+
match = check_in_section_by_line(context.document, line)
|
203
|
+
end
|
204
|
+
|
205
|
+
result = {
|
206
|
+
element: Element.new(context, match[:element]),
|
207
|
+
range: nil
|
208
|
+
}
|
209
|
+
|
210
|
+
instruction = match[:instruction]
|
211
|
+
|
212
|
+
unless instruction
|
213
|
+
instruction = context.meta.find { |candidate| candidate[:line] == line }
|
214
|
+
return result unless instruction
|
215
|
+
end
|
216
|
+
|
217
|
+
rightmost_match = instruction[:ranges][:line][0]
|
218
|
+
|
219
|
+
unless index
|
220
|
+
index = instruction[:ranges][:line][0] + column
|
221
|
+
end
|
222
|
+
|
223
|
+
instruction[:ranges].each do |type, range|
|
224
|
+
next if type == :line
|
225
|
+
|
226
|
+
if index >= range[RANGE_BEGIN] && index <= range[RANGE_END] && range[RANGE_BEGIN] >= rightmost_match
|
227
|
+
result[:range] = type
|
228
|
+
# TODO: Provide content of range too as convenience
|
229
|
+
rightmost_match = index
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
result
|
234
|
+
end
|
235
|
+
end
|