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,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
|