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,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: Safe-guard against conflicting loader names (e.g. previous definition or native library function conflict)
|
4
|
+
|
5
|
+
module Enolib
|
6
|
+
def self.register(definitions)
|
7
|
+
definitions.each do |name, loader|
|
8
|
+
ElementBase.send(:define_method, "#{name}_key") { key(loader) }
|
9
|
+
ElementBase.send(:define_method, "optional_#{name}_comment") { optional_comment(loader) }
|
10
|
+
ElementBase.send(:define_method, "required_#{name}_comment") { required_comment(loader) }
|
11
|
+
ValueElementBase.send(:define_method, "optional_#{name}_value") { optional_value(loader) }
|
12
|
+
ValueElementBase.send(:define_method, "required_#{name}_value") { required_value(loader) }
|
13
|
+
List.send(:define_method, "optional_#{name}_values") { optional_values(loader) }
|
14
|
+
List.send(:define_method, "required_#{name}_values") { required_values(loader) }
|
15
|
+
MissingElementBase.send(:alias_method, "#{name}_key", :string_key)
|
16
|
+
MissingElementBase.send(:alias_method, "optional_#{name}_comment", :optional_string_comment)
|
17
|
+
MissingElementBase.send(:alias_method, "required_#{name}_comment", :required_string_comment)
|
18
|
+
MissingValueElementBase.send(:alias_method, "optional_#{name}_value", :optional_string_value)
|
19
|
+
MissingValueElementBase.send(:alias_method, "required_#{name}_value", :required_string_value)
|
20
|
+
MissingList.send(:alias_method, "optional_#{name}_values", :optional_string_values)
|
21
|
+
MissingList.send(:alias_method, "required_#{name}_values", :required_string_values)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enolib
|
4
|
+
class HtmlReporter < Reporter
|
5
|
+
def self.report(context, emphasized = [], marked = [])
|
6
|
+
emphasized = [emphasized] unless emphasized.is_a?(Array)
|
7
|
+
marked = [marked] unless marked.is_a?(Array)
|
8
|
+
|
9
|
+
content_header = context.messages::CONTENT_HEADER
|
10
|
+
gutter_header = context.messages::GUTTER_HEADER
|
11
|
+
omission = line('...', '...')
|
12
|
+
|
13
|
+
snippet = '<pre class="eno-report">'
|
14
|
+
|
15
|
+
snippet += "<div>#{context.sourceLabel}</div>" if context.source
|
16
|
+
snippet += line(gutter_header, content_header)
|
17
|
+
|
18
|
+
in_omission = false
|
19
|
+
|
20
|
+
context[:instructions].each do |instruction|
|
21
|
+
emphasize = emphasized.include?(instruction)
|
22
|
+
mark = marked.include?(instruction)
|
23
|
+
|
24
|
+
show = (emphasized + marked).any? do |marked_instruction|
|
25
|
+
instruction[:line] >= marked_instruction[:line] - 2 &&
|
26
|
+
instruction[:line] <= marked_instruction[:line] + 2
|
27
|
+
end
|
28
|
+
|
29
|
+
if show
|
30
|
+
classes = []
|
31
|
+
|
32
|
+
if emphasize
|
33
|
+
classes.push('eno-report-line-emphasized')
|
34
|
+
elsif mark
|
35
|
+
classes.push('eno-report-line-marked')
|
36
|
+
end
|
37
|
+
|
38
|
+
snippet += line(
|
39
|
+
(instruction[:line] + Enolib::HUMAN_INDEXING).to_s,
|
40
|
+
context[:input][instruction[:index], instruction[:length]],
|
41
|
+
classes
|
42
|
+
)
|
43
|
+
|
44
|
+
in_omission = false
|
45
|
+
elsif !in_omission
|
46
|
+
snippet += omission
|
47
|
+
in_omission = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
snippet += '</pre>'
|
52
|
+
|
53
|
+
snippet
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def print_line(line, tag)
|
59
|
+
return markup('...', '...') if tag == :omission
|
60
|
+
|
61
|
+
number = (line + HUMAN_INDEXING).to_s
|
62
|
+
instruction = @index[line]
|
63
|
+
|
64
|
+
content = ''
|
65
|
+
if instruction
|
66
|
+
content = @context.input[instruction[:ranges][:line][RANGE_BEGIN]..instruction[:ranges][:line][RANGE_END]]
|
67
|
+
end
|
68
|
+
|
69
|
+
tag_class =
|
70
|
+
case tag
|
71
|
+
when :emphasize
|
72
|
+
'eno-report-line-emphasized'
|
73
|
+
when :indicate
|
74
|
+
'eno-report-line-indicated'
|
75
|
+
when :question
|
76
|
+
'eno-report-line-questioned'
|
77
|
+
else
|
78
|
+
''
|
79
|
+
end
|
80
|
+
|
81
|
+
markup(number, content, tag_class)
|
82
|
+
end
|
83
|
+
|
84
|
+
def escape(string)
|
85
|
+
string.gsub(/[&<>"'\/]/) { |c| HTML_ESCAPE[c] }
|
86
|
+
end
|
87
|
+
|
88
|
+
def markup(gutter, content, tag_class = '')
|
89
|
+
"<div class=\"eno-report-line #{tag_class}\">" +
|
90
|
+
"<div class=\"eno-report-gutter\">#{gutter.rjust(10)}</div>" +
|
91
|
+
"<div class=\"eno-report-content\">#{escape(content)}</div>" +
|
92
|
+
'</div>'
|
93
|
+
end
|
94
|
+
|
95
|
+
def print
|
96
|
+
columns_header = markup(@context.messages::GUTTER_HEADER, @context.messages::CONTENT_HEADER)
|
97
|
+
snippet = @snippet.each_with_index.map { |tag, line| print_line(line, tag) if tag }.compact.join("\n")
|
98
|
+
|
99
|
+
if @context.source
|
100
|
+
return "<div><div>#{@context.source}</div><pre class=\"eno-report\">#{columns_header}#{snippet}</pre></div>"
|
101
|
+
end
|
102
|
+
|
103
|
+
"<pre class=\"eno-report\">#{columns_header}#{snippet}</pre>"
|
104
|
+
end
|
105
|
+
|
106
|
+
HTML_ESCAPE = {
|
107
|
+
'&' => '&',
|
108
|
+
'<' => '<',
|
109
|
+
'>' => '>',
|
110
|
+
'"' => '"',
|
111
|
+
"'" => ''',
|
112
|
+
'/' => '/'
|
113
|
+
}.freeze
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enolib
|
4
|
+
class Reporter
|
5
|
+
def initialize(context)
|
6
|
+
@context = context
|
7
|
+
@index = Array.new(@context.line_count)
|
8
|
+
@snippet = Array.new(@context.line_count)
|
9
|
+
|
10
|
+
build_index
|
11
|
+
end
|
12
|
+
|
13
|
+
def indicate_line(element)
|
14
|
+
@snippet[element[:line]] = :indicate
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def question_line(element)
|
19
|
+
@snippet[element[:line]] = :question
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def report_comments(element)
|
24
|
+
@snippet[element[:line]] = :indicate
|
25
|
+
element[:comments].each do |comment|
|
26
|
+
@snippet[comment[:line]] = :emphasize
|
27
|
+
end
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def report_element(element)
|
33
|
+
@snippet[element[:line]] = :emphasize
|
34
|
+
tag_element(element, :indicate)
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def report_elements(elements)
|
40
|
+
elements.each do |element|
|
41
|
+
@snippet[element[:line]] = :emphasize
|
42
|
+
tag_element(element, :indicate)
|
43
|
+
end
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def report_line(instruction)
|
49
|
+
@snippet[instruction[:line]] = :emphasize
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def report_multiline_value(element)
|
55
|
+
element[:lines].each do |line|
|
56
|
+
@snippet[line[:line]] = :emphasize
|
57
|
+
end
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def report_missing_element(parent)
|
63
|
+
@snippet[parent[:line]] = :indicate unless parent[:type] == :document
|
64
|
+
|
65
|
+
if parent[:type] == :section
|
66
|
+
tag_section(parent, :question, false)
|
67
|
+
else
|
68
|
+
tag_element(parent, :question)
|
69
|
+
end
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def snippet
|
75
|
+
if @snippet.none?
|
76
|
+
(0...@snippet.length).each do |line|
|
77
|
+
@snippet[line] = :question
|
78
|
+
end
|
79
|
+
else
|
80
|
+
# TODO: Possibly better algorithm for this
|
81
|
+
@snippet.each_with_index do |tag, line|
|
82
|
+
next if tag
|
83
|
+
|
84
|
+
if line + 2 < @context.line_count && @snippet[line + 2] && @snippet[line + 2] != :display ||
|
85
|
+
line - 2 > 0 && @snippet[line - 2] && @snippet[line - 2] != :display ||
|
86
|
+
line + 1 < @context.line_count && @snippet[line + 1] && @snippet[line + 1] != :display ||
|
87
|
+
line - 1 > 0 && @snippet[line - 1] && @snippet[line - 1] != :display
|
88
|
+
@snippet[line] = :display
|
89
|
+
elsif line + 3 < @context.line_count && @snippet[line + 3] && @snippet[line + 3] != :display
|
90
|
+
@snippet[line] = :omission
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@snippet[-1] = :omission unless @snippet[-1]
|
95
|
+
end
|
96
|
+
|
97
|
+
print
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def index_comments(element)
|
103
|
+
return unless element.has_key?(:comments)
|
104
|
+
|
105
|
+
element[:comments].each do |comment|
|
106
|
+
@index[comment[:line]] = comment
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def traverse(section)
|
111
|
+
section[:elements].each do |element|
|
112
|
+
index_comments(element)
|
113
|
+
|
114
|
+
@index[element[:line]] = element
|
115
|
+
|
116
|
+
if element[:type] == :section
|
117
|
+
traverse(element)
|
118
|
+
elsif element[:type] == :field
|
119
|
+
if element.has_key?(:continuations)
|
120
|
+
element[:continuations].each do |continuation|
|
121
|
+
@index[continuation[:line]] = continuation
|
122
|
+
end
|
123
|
+
end
|
124
|
+
elsif element[:type] == :multiline_field_begin
|
125
|
+
# Missing when reporting an unterminated multiline field
|
126
|
+
if element.has_key?(:end)
|
127
|
+
@index[element[:end][:line]] = element[:end]
|
128
|
+
end
|
129
|
+
|
130
|
+
if element.has_key?(:lines)
|
131
|
+
element[:lines].each do |line|
|
132
|
+
@index[line[:line]] = line
|
133
|
+
end
|
134
|
+
end
|
135
|
+
elsif element[:type] == :list
|
136
|
+
if element.has_key?(:items)
|
137
|
+
element[:items].each do |item|
|
138
|
+
index_comments(item)
|
139
|
+
|
140
|
+
@index[item[:line]] = item
|
141
|
+
|
142
|
+
if item.has_key?(:continuations)
|
143
|
+
item[:continuations].each do |continuation|
|
144
|
+
@index[continuation[:line]] = continuation
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
elsif element[:type] == :fieldset
|
150
|
+
if element.has_key?(:entries)
|
151
|
+
element[:entries].each do |entry|
|
152
|
+
index_comments(entry)
|
153
|
+
|
154
|
+
@index[entry[:line]] = entry
|
155
|
+
|
156
|
+
if entry.has_key?(:continuations)
|
157
|
+
entry[:continuations].each do |continuation|
|
158
|
+
@index[continuation[:line]] = continuation
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def build_index
|
168
|
+
traverse(@context.document)
|
169
|
+
|
170
|
+
@context.meta.each do |instruction|
|
171
|
+
@index[instruction[:line]] = instruction
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def tag_continuations(element, tag)
|
176
|
+
scan_line = element[:line] + 1
|
177
|
+
|
178
|
+
return scan_line unless element.has_key?(:continuations)
|
179
|
+
|
180
|
+
element[:continuations].each do |continuation|
|
181
|
+
while scan_line < continuation[:line]
|
182
|
+
@snippet[scan_line] = tag
|
183
|
+
scan_line += 1
|
184
|
+
end
|
185
|
+
|
186
|
+
@snippet[continuation[:line]] = tag
|
187
|
+
scan_line += 1
|
188
|
+
end
|
189
|
+
|
190
|
+
scan_line
|
191
|
+
end
|
192
|
+
|
193
|
+
def tag_continuables(element, collection, tag)
|
194
|
+
scan_line = element[:line] + 1
|
195
|
+
|
196
|
+
return scan_line unless element.has_key?(collection)
|
197
|
+
|
198
|
+
element[collection].each do |continuable|
|
199
|
+
while scan_line < continuable[:line]
|
200
|
+
@snippet[scan_line] = tag
|
201
|
+
scan_line += 1
|
202
|
+
end
|
203
|
+
|
204
|
+
@snippet[continuable[:line]] = tag
|
205
|
+
|
206
|
+
scan_line = tag_continuations(continuable, tag)
|
207
|
+
end
|
208
|
+
|
209
|
+
scan_line
|
210
|
+
end
|
211
|
+
|
212
|
+
def tag_element(element, tag)
|
213
|
+
if element[:type] == :field || element[:type] == :list_item || element[:type] == :fieldset_entry
|
214
|
+
return tag_continuations(element, tag)
|
215
|
+
elsif element[:type] == :list
|
216
|
+
return tag_continuables(element, :items, tag)
|
217
|
+
elsif element[:type] == :fieldset && element.has_key?(:entries)
|
218
|
+
return tag_continuables(element, :entries, tag)
|
219
|
+
elsif element[:type] == :multiline_field_begin
|
220
|
+
if element.has_key?(:lines)
|
221
|
+
element[:lines].each do |line|
|
222
|
+
@snippet[line[:line]] = tag
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
if element.has_key?(:end)
|
227
|
+
@snippet[element[:end][:line]] = tag
|
228
|
+
return element[:end][:line] + 1
|
229
|
+
elsif element.has_key?(:lines)
|
230
|
+
return element[:lines][-1][:line] + 1
|
231
|
+
else
|
232
|
+
return element[:line] + 1
|
233
|
+
end
|
234
|
+
elsif element[:type] == :section
|
235
|
+
return tag_section(element, tag)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def tag_section(section, tag, recursive=true)
|
240
|
+
scan_line = section[:line] + 1
|
241
|
+
|
242
|
+
section[:elements].each do |element|
|
243
|
+
while scan_line < element[:line]
|
244
|
+
@snippet[scan_line] = :indicate
|
245
|
+
scan_line += 1
|
246
|
+
end
|
247
|
+
|
248
|
+
break if element[:type] == :section && !recursive
|
249
|
+
|
250
|
+
@snippet[element[:line]] = :indicate
|
251
|
+
|
252
|
+
scan_line = tag_element(element, tag)
|
253
|
+
end
|
254
|
+
|
255
|
+
scan_line
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RESET = "\x1b[0m"
|
4
|
+
BOLD = "\x1b[1m"
|
5
|
+
DIM = "\x1b[2m"
|
6
|
+
|
7
|
+
BLACK = "\x1b[30m"
|
8
|
+
BRIGHT_BLACK = "\x1b[90m"
|
9
|
+
WHITE = "\x1b[37m"
|
10
|
+
BRIGHT_WHITE = "\x1b[97m"
|
11
|
+
|
12
|
+
BRIGHT_BLACK_BACKGROUND = "\x1b[40m"
|
13
|
+
BRIGHT_RED_BACKGROUND = "\x1b[101m"
|
14
|
+
WHITE_BACKGROUND = "\x1b[47m"
|
15
|
+
|
16
|
+
INDICATORS = {
|
17
|
+
display: ' ',
|
18
|
+
emphasize: '>',
|
19
|
+
indicate: '*',
|
20
|
+
question: '?'
|
21
|
+
}
|
22
|
+
|
23
|
+
GUTTER_STYLE = {
|
24
|
+
display: BRIGHT_BLACK_BACKGROUND,
|
25
|
+
emphasize: BLACK + BRIGHT_RED_BACKGROUND,
|
26
|
+
indicate: BLACK + WHITE_BACKGROUND,
|
27
|
+
question: BLACK + WHITE_BACKGROUND
|
28
|
+
}
|
29
|
+
|
30
|
+
RANGE_STYLE = {
|
31
|
+
'element_operator': WHITE,
|
32
|
+
'escape_begin_operator': WHITE,
|
33
|
+
'escape_end_operator': WHITE,
|
34
|
+
'item_operator': WHITE,
|
35
|
+
'entry_operator': WHITE,
|
36
|
+
'section_operator': WHITE,
|
37
|
+
'copy_operator': WHITE,
|
38
|
+
'deepCopy_operator': WHITE,
|
39
|
+
'multiline_field_operator': WHITE,
|
40
|
+
'direct_line_continuation_operator': WHITE,
|
41
|
+
'spaced_line_continuation_operator': WHITE,
|
42
|
+
'key': BOLD + BRIGHT_WHITE,
|
43
|
+
'template': BOLD + BRIGHT_WHITE,
|
44
|
+
'value': DIM + WHITE
|
45
|
+
}
|
46
|
+
|
47
|
+
module Enolib
|
48
|
+
class TerminalReporter < Reporter
|
49
|
+
def initialize(context)
|
50
|
+
super(context)
|
51
|
+
|
52
|
+
highest_shown_line_number = @snippet.length
|
53
|
+
|
54
|
+
@snippet.reverse.each_with_index do |tag, index|
|
55
|
+
if tag && tag != :omission
|
56
|
+
highest_shown_line_number = index + 1
|
57
|
+
break
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
@line_number_padding = [4, highest_shown_line_number.to_s.length].max
|
62
|
+
@header = ''
|
63
|
+
|
64
|
+
if @context.source
|
65
|
+
@header += "#{BLACK + BRIGHT_RED_BACKGROUND} #{INDICATORS[EMPHASIZE]} #{' '.rjust(@line_number_padding)} #{RESET} #{BOLD}#{@context.source}#{RESET}\n"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def print_line(line, tag)
|
70
|
+
if tag == :omission
|
71
|
+
return "#{DIM + BRIGHT_BLACK_BACKGROUND}#{'...'.rjust(@line_number_padding + 2)} #{RESET}"
|
72
|
+
end
|
73
|
+
|
74
|
+
number = (line + HUMAN_INDEXING).to_s
|
75
|
+
instruction = @index[line]
|
76
|
+
|
77
|
+
content = ''
|
78
|
+
if instruction
|
79
|
+
if instruction[:type] == :comment or instruction[:type] == :unparsed
|
80
|
+
content = BRIGHT_BLACK + @context.input[instruction[:ranges][:line][RANGE_BEGIN]...instruction[:ranges][:line][RANGE_END]] + RESET
|
81
|
+
else
|
82
|
+
content = @context.input[instruction[:ranges][:line][RANGE_BEGIN]...instruction[:ranges][:line][RANGE_END]]
|
83
|
+
|
84
|
+
instruction[:ranges].sort_by { |type, range| range[0] }.reverse_each do |type, range|
|
85
|
+
next if type == :line
|
86
|
+
|
87
|
+
before = content[0...range[RANGE_BEGIN] - instruction[:ranges][:line][RANGE_BEGIN]]
|
88
|
+
after = content[range[RANGE_END] - instruction[:ranges][:line][RANGE_BEGIN]..-1]
|
89
|
+
|
90
|
+
# TODO: Here and everywhere: Why is RANGE_BEGIN without anything and like that even working? Check soundness
|
91
|
+
content = before + RANGE_STYLE[type] + @context.input[range[RANGE_BEGIN]...range[RANGE_END]] + RESET + after
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
"#{GUTTER_STYLE[tag]} #{INDICATORS[tag]} #{number.rjust(@line_number_padding)} #{RESET} #{content}"
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def print
|
102
|
+
snippet = @snippet.each_with_index.map { |tag, line| print_line(line, tag) if tag }.compact.join("\n")
|
103
|
+
|
104
|
+
@header + snippet
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|