puppet 6.2.0-universal-darwin → 6.3.0-universal-darwin
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +70 -0
- data/Gemfile.lock +7 -7
- data/lib/puppet/application.rb +1 -1
- data/lib/puppet/application/lookup.rb +1 -1
- data/lib/puppet/confine/boolean.rb +45 -0
- data/lib/puppet/confine/false.rb +7 -1
- data/lib/puppet/confine/true.rb +7 -1
- data/lib/puppet/defaults.rb +0 -18
- data/lib/puppet/functions/call.rb +2 -1
- data/lib/puppet/functions/group_by.rb +54 -0
- data/lib/puppet/functions/index.rb +167 -0
- data/lib/puppet/functions/partition.rb +54 -0
- data/lib/puppet/pops/evaluator/evaluator_impl.rb +1 -1
- data/lib/puppet/pops/issues.rb +4 -0
- data/lib/puppet/pops/model/factory.rb +38 -4
- data/lib/puppet/pops/parser/egrammar.ra +2 -2
- data/lib/puppet/pops/parser/eparser.rb +2 -2
- data/lib/puppet/pops/parser/heredoc_support.rb +17 -7
- data/lib/puppet/pops/parser/lexer2.rb +6 -1
- data/lib/puppet/pops/parser/locator.rb +106 -86
- data/lib/puppet/pops/parser/parser_support.rb +11 -2
- data/lib/puppet/pops/types/type_mismatch_describer.rb +1 -1
- data/lib/puppet/pops/validation/checker4_0.rb +2 -2
- data/lib/puppet/provider/group/windows_adsi.rb +4 -1
- data/lib/puppet/provider/package/apt.rb +2 -2
- data/lib/puppet/provider/package/dnf.rb +2 -2
- data/lib/puppet/provider/package/gem.rb +2 -2
- data/lib/puppet/provider/package/openbsd.rb +2 -2
- data/lib/puppet/provider/package/pacman.rb +1 -2
- data/lib/puppet/provider/package/pip.rb +1 -2
- data/lib/puppet/provider/package/pip3.rb +1 -2
- data/lib/puppet/provider/package/pkg.rb +2 -4
- data/lib/puppet/provider/package/portage.rb +1 -2
- data/lib/puppet/provider/package/rpm.rb +1 -2
- data/lib/puppet/provider/package/sun.rb +2 -2
- data/lib/puppet/provider/package/windows.rb +2 -2
- data/lib/puppet/provider/package/yum.rb +1 -2
- data/lib/puppet/provider/package/zypper.rb +2 -2
- data/lib/puppet/provider/service/upstart.rb +16 -6
- data/lib/puppet/transaction/resource_harness.rb +1 -0
- data/lib/puppet/type/file.rb +6 -1
- data/lib/puppet/util/log.rb +7 -2
- data/lib/puppet/util/pidlock.rb +14 -1
- data/lib/puppet/util/windows/process.rb +73 -5
- data/lib/puppet/version.rb +1 -1
- data/locales/puppet.pot +92 -92
- data/man/man5/puppet.conf.5 +2 -2
- data/man/man8/puppet-agent.8 +1 -1
- data/man/man8/puppet-apply.8 +1 -1
- data/man/man8/puppet-catalog.8 +1 -1
- data/man/man8/puppet-config.8 +1 -1
- data/man/man8/puppet-describe.8 +1 -1
- data/man/man8/puppet-device.8 +1 -1
- data/man/man8/puppet-doc.8 +1 -1
- data/man/man8/puppet-epp.8 +1 -1
- data/man/man8/puppet-facts.8 +1 -1
- data/man/man8/puppet-filebucket.8 +1 -1
- data/man/man8/puppet-generate.8 +1 -1
- data/man/man8/puppet-help.8 +1 -1
- data/man/man8/puppet-key.8 +1 -1
- data/man/man8/puppet-lookup.8 +2 -2
- data/man/man8/puppet-man.8 +1 -1
- data/man/man8/puppet-module.8 +1 -1
- data/man/man8/puppet-node.8 +1 -1
- data/man/man8/puppet-parser.8 +1 -1
- data/man/man8/puppet-plugin.8 +1 -1
- data/man/man8/puppet-report.8 +1 -1
- data/man/man8/puppet-resource.8 +1 -1
- data/man/man8/puppet-script.8 +1 -1
- data/man/man8/puppet-ssl.8 +1 -1
- data/man/man8/puppet-status.8 +1 -1
- data/man/man8/puppet.8 +2 -2
- data/spec/unit/application_spec.rb +8 -1
- data/spec/unit/confine/false_spec.rb +27 -0
- data/spec/unit/confine/true_spec.rb +27 -0
- data/spec/unit/defaults_spec.rb +0 -14
- data/spec/unit/functions/group_by_spec.rb +40 -0
- data/spec/unit/functions/index_spec.rb +184 -0
- data/spec/unit/functions/partition_spec.rb +40 -0
- data/spec/unit/pops/loaders/loaders_spec.rb +5 -0
- data/spec/unit/pops/parser/locator_spec.rb +45 -0
- data/spec/unit/pops/parser/parse_heredoc_spec.rb +111 -15
- data/spec/unit/pops/types/type_mismatch_describer_spec.rb +9 -0
- data/spec/unit/provider/group/windows_adsi_spec.rb +7 -1
- data/spec/unit/transaction/resource_harness_spec.rb +26 -0
- data/spec/unit/util/execution_spec.rb +2 -2
- data/spec/unit/util/log_spec.rb +15 -0
- data/spec/unit/util/pidlock_spec.rb +21 -1
- metadata +13 -2
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Returns two arrays, the first containing the elements of enum for which the block evaluates to true,
|
4
|
+
# the second containing the rest.
|
5
|
+
Puppet::Functions.create_function(:partition) do
|
6
|
+
# @param collection A collection of things to partition.
|
7
|
+
# @example Partition array of empty strings, results in e.g. [[''], [b, c]]
|
8
|
+
# ['', b, c].partition |$s| { $s.empty }
|
9
|
+
# @example Partition array of strings using index, results in e.g. [['', 'ab'], ['b']]
|
10
|
+
# ['', b, ab].partition |$i, $s| { $i == 2 or $s.empty }
|
11
|
+
# @example Partition hash of strings by key-value pair, results in e.g. [[['b', []]], [['a', [1, 2]]]]
|
12
|
+
# { a => [1, 2], b => [] }.partition |$kv| { $kv[1].empty }
|
13
|
+
# @example Partition hash of strings by key and value, results in e.g. [[['b', []]], [['a', [1, 2]]]]
|
14
|
+
# { a => [1, 2], b => [] }.partition |$k, $v| { $v.empty }
|
15
|
+
dispatch :partition_1 do
|
16
|
+
required_param 'Collection', :collection
|
17
|
+
block_param 'Callable[1,1]', :block
|
18
|
+
return_type 'Tuple[Array, Array]'
|
19
|
+
end
|
20
|
+
|
21
|
+
dispatch :partition_2a do
|
22
|
+
required_param 'Array', :array
|
23
|
+
block_param 'Callable[2,2]', :block
|
24
|
+
return_type 'Tuple[Array, Array]'
|
25
|
+
end
|
26
|
+
|
27
|
+
dispatch :partition_2 do
|
28
|
+
required_param 'Collection', :collection
|
29
|
+
block_param 'Callable[2,2]', :block
|
30
|
+
return_type 'Tuple[Array, Array]'
|
31
|
+
end
|
32
|
+
|
33
|
+
def partition_1(collection)
|
34
|
+
collection.partition do |item|
|
35
|
+
yield(item)
|
36
|
+
end.freeze
|
37
|
+
end
|
38
|
+
|
39
|
+
def partition_2a(array)
|
40
|
+
partitioned = array.size.times.zip(array).partition do |k, v|
|
41
|
+
yield(k, v)
|
42
|
+
end
|
43
|
+
|
44
|
+
partitioned.map do |part|
|
45
|
+
part.map { |item| item[1] }
|
46
|
+
end.freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
def partition_2(collection)
|
50
|
+
collection.partition do |k, v|
|
51
|
+
yield(k, v)
|
52
|
+
end.freeze
|
53
|
+
end
|
54
|
+
end
|
@@ -1028,7 +1028,7 @@ class EvaluatorImpl
|
|
1028
1028
|
|
1029
1029
|
# Evaluates Puppet DSL Heredoc
|
1030
1030
|
def eval_HeredocExpression o, scope
|
1031
|
-
expr = o.text_expr
|
1031
|
+
expr = o.text_expr
|
1032
1032
|
result = evaluate(o.text_expr, scope)
|
1033
1033
|
unless expr.is_a?(Model::LiteralString)
|
1034
1034
|
# When expr is a LiteralString, validation has already validated this
|
data/lib/puppet/pops/issues.rb
CHANGED
@@ -759,6 +759,10 @@ module Issues
|
|
759
759
|
_("An escape char for @() may only appear once. Got '%{escapes}'") % { escapes: escapes.join(', ') }
|
760
760
|
end
|
761
761
|
|
762
|
+
HEREDOC_DIRTY_MARGIN = hard_issue :HEREDOC_DIRTY_MARGIN, :heredoc_line do
|
763
|
+
_("Heredoc with text in the margin is not allowed (line %{heredoc_line} in this heredoc)") % { heredoc_line: heredoc_line }
|
764
|
+
end
|
765
|
+
|
762
766
|
ILLEGAL_BOM = hard_issue :ILLEGAL_BOM, :format_name, :bytes do
|
763
767
|
_("Illegal %{format} Byte Order mark at beginning of input: %{bom} - remove these from the puppet source") % { format: format_name, bom: bytes }
|
764
768
|
end
|
@@ -28,6 +28,7 @@ class Factory
|
|
28
28
|
BUILD_VISITOR = Visitor.new(self, 'build')
|
29
29
|
INFER_VISITOR = Visitor.new(self, 'infer')
|
30
30
|
INTERPOLATION_VISITOR = Visitor.new(self, 'interpolate')
|
31
|
+
MAPOFFSET_VISITOR = Visitor.new(self, 'map_offset')
|
31
32
|
|
32
33
|
def self.infer(o)
|
33
34
|
if o.instance_of?(Factory)
|
@@ -89,6 +90,29 @@ class Factory
|
|
89
90
|
end
|
90
91
|
end
|
91
92
|
|
93
|
+
def map_offset(model, locator)
|
94
|
+
MAPOFFSET_VISITOR.visit_this_1(self, model, locator)
|
95
|
+
end
|
96
|
+
|
97
|
+
def map_offset_Object(o, locator)
|
98
|
+
o
|
99
|
+
end
|
100
|
+
|
101
|
+
def map_offset_Factory(o, locator)
|
102
|
+
map_offset(o.model, locator)
|
103
|
+
end
|
104
|
+
|
105
|
+
def map_offset_Positioned(o, locator)
|
106
|
+
# Transpose the local offset, length to global "coordinates"
|
107
|
+
global_offset, global_length = locator.to_global(o.offset, o.length)
|
108
|
+
|
109
|
+
# mutate
|
110
|
+
o.instance_variable_set(:'@offset', global_offset)
|
111
|
+
o.instance_variable_set(:'@length', global_length)
|
112
|
+
# Change locator since the positions were transposed to the global coordinates
|
113
|
+
o.instance_variable_set(:'@locator', locator.locator) if locator.is_a? Puppet::Pops::Parser::Locator::SubLocator
|
114
|
+
end
|
115
|
+
|
92
116
|
# Polymorphic interpolate
|
93
117
|
def interpolate()
|
94
118
|
INTERPOLATION_VISITOR.visit_this_class(self, @model_class, EMPTY_ARRAY)
|
@@ -433,8 +457,7 @@ class Factory
|
|
433
457
|
@init_hash[KEY_LOCATOR] = locator
|
434
458
|
@init_hash['leading_line_count'] = locator.leading_line_count
|
435
459
|
@init_hash['leading_line_offset'] = locator.leading_line_offset
|
436
|
-
|
437
|
-
@init_hash['line_offsets'] = locator.locator.line_index
|
460
|
+
@init_hash['line_offsets'] = locator.line_index # index of lines in sublocated
|
438
461
|
end
|
439
462
|
|
440
463
|
def build_SelectorEntry(o, matching, value)
|
@@ -729,8 +752,6 @@ class Factory
|
|
729
752
|
|
730
753
|
def self.STRING(*args); new(ConcatenatedString, args); end
|
731
754
|
|
732
|
-
def self.SUBLOCATE(token, expr) new(SubLocatedExpression, token, expr); end
|
733
|
-
|
734
755
|
def self.LIST(entries); new(LiteralList, entries); end
|
735
756
|
|
736
757
|
def self.PARAM(name, expr=nil); new(Parameter, name, expr); end
|
@@ -765,6 +786,19 @@ class Factory
|
|
765
786
|
o.instance_of?(Factory) && o.model_class <= QualifiedReference ? self : new(QualifiedReference, o)
|
766
787
|
end
|
767
788
|
|
789
|
+
def self.SUBLOCATE(token, expr_factory)
|
790
|
+
# expr is a Factory wrapped LiteralString, or ConcatenatedString
|
791
|
+
# The token is SUBLOCATED token which has a SubLocator as the token's locator
|
792
|
+
# Use the SubLocator to recalculate the offsets and lengths.
|
793
|
+
model = expr_factory.model
|
794
|
+
locator = token.locator
|
795
|
+
expr_factory.map_offset(model, locator)
|
796
|
+
model._pcore_all_contents([]) { |element| expr_factory.map_offset(element, locator) }
|
797
|
+
|
798
|
+
# Returned the factory wrapping the now offset/length transformed expression(s)
|
799
|
+
expr_factory
|
800
|
+
end
|
801
|
+
|
768
802
|
def self.TEXT(expr)
|
769
803
|
new(TextExpression, infer(expr).interpolate)
|
770
804
|
end
|
@@ -857,8 +857,8 @@ heredoc
|
|
857
857
|
: HEREDOC sublocated_text { result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] }
|
858
858
|
|
859
859
|
sublocated_text
|
860
|
-
: SUBLOCATE string { result = Factory.SUBLOCATE(val[0], val[1]);
|
861
|
-
| SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]);
|
860
|
+
: SUBLOCATE string { result = Factory.SUBLOCATE(val[0], val[1]); }
|
861
|
+
| SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]); }
|
862
862
|
|
863
863
|
epp_expression
|
864
864
|
: EPP_START epp_parameters_list optional_statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] }
|
@@ -3204,14 +3204,14 @@ module_eval(<<'.,.,', 'egrammar.ra', 856)
|
|
3204
3204
|
|
3205
3205
|
module_eval(<<'.,.,', 'egrammar.ra', 859)
|
3206
3206
|
def _reduce_259(val, _values, result)
|
3207
|
-
result = Factory.SUBLOCATE(val[0], val[1]);
|
3207
|
+
result = Factory.SUBLOCATE(val[0], val[1]);
|
3208
3208
|
result
|
3209
3209
|
end
|
3210
3210
|
.,.,
|
3211
3211
|
|
3212
3212
|
module_eval(<<'.,.,', 'egrammar.ra', 860)
|
3213
3213
|
def _reduce_260(val, _values, result)
|
3214
|
-
result = Factory.SUBLOCATE(val[0], val[1]);
|
3214
|
+
result = Factory.SUBLOCATE(val[0], val[1]);
|
3215
3215
|
result
|
3216
3216
|
end
|
3217
3217
|
.,.,
|
@@ -94,11 +94,12 @@ module HeredocSupport
|
|
94
94
|
|
95
95
|
|
96
96
|
# Process captured lines - remove leading, and trailing newline
|
97
|
-
|
97
|
+
# get processed string and index of removed margin/leading size per line
|
98
|
+
str, margin_per_line = heredoc_text(lines, leading, has_margin, remove_break)
|
98
99
|
|
99
100
|
# Use a new lexer instance configured with a sub-locator to enable correct positioning
|
100
101
|
sublexer = self.class.new()
|
101
|
-
locator = Locator::SubLocator.new(locator, heredoc_line, heredoc_offset,
|
102
|
+
locator = Locator::SubLocator.new(locator, str, heredoc_line, heredoc_offset, has_margin, margin_per_line)
|
102
103
|
|
103
104
|
# Emit a token that provides the grammar with location information about the lines on which the heredoc
|
104
105
|
# content is based.
|
@@ -120,23 +121,32 @@ module HeredocSupport
|
|
120
121
|
raise eof_error
|
121
122
|
end
|
122
123
|
|
123
|
-
# Produces the heredoc text string given the individual (unprocessed) lines as an array
|
124
|
+
# Produces the heredoc text string given the individual (unprocessed) lines as an array and array with margin sizes per line
|
124
125
|
# @param lines [Array<String>] unprocessed lines of text in the heredoc w/o terminating line
|
125
126
|
# @param leading [String] the leading text up (up to pipe or other terminating char)
|
126
127
|
# @param has_margin [Boolean] if the left margin should be adjusted as indicated by `leading`
|
127
128
|
# @param remove_break [Boolean] if the line break (\r?\n) at the end of the last line should be removed or not
|
129
|
+
# @return [Array] - a tuple with resulting string, and an array with margin size per line
|
128
130
|
#
|
129
131
|
def heredoc_text(lines, leading, has_margin, remove_break)
|
130
|
-
if has_margin
|
132
|
+
if has_margin && leading.length > 0
|
131
133
|
leading_pattern = /^#{Regexp.escape(leading)}/
|
132
|
-
|
134
|
+
# TODO: This implementation is not according to the specification, but is kept to be bug compatible.
|
135
|
+
# The specification says that leading space up to the margin marker should be removed, but this implementation
|
136
|
+
# simply leaves lines that have text in the margin untouched.
|
137
|
+
#
|
138
|
+
processed_lines = lines.collect {|s| s.gsub(leading_pattern, '') }
|
139
|
+
margin_per_line = processed_lines.length.times.map {|x| lines[x].length - processed_lines[x].length }
|
140
|
+
lines = processed_lines
|
141
|
+
else
|
142
|
+
# Array with a 0 per line
|
143
|
+
margin_per_line = Array.new(lines.length, 0)
|
133
144
|
end
|
134
145
|
result = lines.join('')
|
135
146
|
result.gsub!(/\r?\n\z/m, '') if remove_break
|
136
|
-
result
|
147
|
+
[result, margin_per_line]
|
137
148
|
end
|
138
149
|
|
139
|
-
|
140
150
|
end
|
141
151
|
end
|
142
152
|
end
|
@@ -189,7 +189,12 @@ class Lexer2
|
|
189
189
|
',' => lambda { emit(TOKEN_COMMA, @scanner.pos) },
|
190
190
|
'[' => lambda do
|
191
191
|
before = @scanner.pos
|
192
|
-
|
192
|
+
# Must check the preceding character to see if it is whitespace.
|
193
|
+
# The fastest thing to do is to simply byteslice to get the string ending at the offset before
|
194
|
+
# and then check what the last character is. (This is the same as what an locator.char_offset needs
|
195
|
+
# to compute, but with less overhead of trying to find out the global offset from a local offset in the
|
196
|
+
# case when this is sublocated in a heredoc).
|
197
|
+
if before == 0 || @scanner.string.byteslice(0, before)[-1] =~ /[[:blank:]\r\n]+/
|
193
198
|
emit(TOKEN_LISTSTART, before)
|
194
199
|
else
|
195
200
|
emit(TOKEN_LBRACK, before)
|
@@ -71,6 +71,16 @@ class Locator
|
|
71
71
|
def line_index()
|
72
72
|
end
|
73
73
|
|
74
|
+
# Common byte based impl that works for all rubies (stringscanner is byte based
|
75
|
+
def self.compute_line_index(string)
|
76
|
+
scanner = StringScanner.new(string)
|
77
|
+
result = [0] # first line starts at 0
|
78
|
+
while scanner.scan_until(/\n/)
|
79
|
+
result << scanner.pos
|
80
|
+
end
|
81
|
+
result.freeze
|
82
|
+
end
|
83
|
+
|
74
84
|
# Produces an URI with path?line=n&pos=n. If origin is unknown the URI is string:?line=n&pos=n
|
75
85
|
def to_uri(ast)
|
76
86
|
f = file
|
@@ -83,81 +93,8 @@ class Locator
|
|
83
93
|
URI("#{f}?line=#{line_for_offset(offset).to_s}&pos=#{pos_on_line(offset).to_s}")
|
84
94
|
end
|
85
95
|
|
86
|
-
# A Sublocator locates a concrete locator (subspace) in a virtual space.
|
87
|
-
# The `leading_line_count` is the (virtual) number of lines preceding the first line in the concrete locator.
|
88
|
-
# The `leading_offset` is the (virtual) byte offset of the first byte in the concrete locator.
|
89
|
-
# The `leading_line_offset` is the (virtual) offset / margin in characters for each line.
|
90
|
-
#
|
91
|
-
# This illustrates characters in the sublocator (`.`) inside the subspace (`X`):
|
92
|
-
#
|
93
|
-
# 1:XXXXXXXX
|
94
|
-
# 2:XXXX.... .. ... ..
|
95
|
-
# 3:XXXX. . .... ..
|
96
|
-
# 4:XXXX............
|
97
|
-
#
|
98
|
-
# This sublocator would be configured with leading_line_count = 1,
|
99
|
-
# leading_offset=8, and leading_line_offset=4
|
100
|
-
#
|
101
|
-
# Note that leading_offset must be the same for all lines and measured in characters.
|
102
|
-
#
|
103
|
-
class SubLocator < Locator
|
104
|
-
attr_reader :locator
|
105
|
-
attr_reader :leading_line_count
|
106
|
-
attr_reader :leading_offset
|
107
|
-
attr_reader :leading_line_offset
|
108
|
-
|
109
|
-
def self.sub_locator(string, file, leading_line_count, leading_offset, leading_line_offset)
|
110
|
-
self.new(Locator.locator(string, file),
|
111
|
-
leading_line_count,
|
112
|
-
leading_offset,
|
113
|
-
leading_line_offset)
|
114
|
-
end
|
115
|
-
|
116
|
-
def initialize(locator, leading_line_count, leading_offset, leading_line_offset)
|
117
|
-
@locator = locator
|
118
|
-
@leading_line_count = leading_line_count
|
119
|
-
@leading_offset = leading_offset
|
120
|
-
@leading_line_offset = leading_line_offset
|
121
|
-
end
|
122
|
-
|
123
|
-
def file
|
124
|
-
@locator.file
|
125
|
-
end
|
126
|
-
|
127
|
-
def string
|
128
|
-
@locator.string
|
129
|
-
end
|
130
|
-
|
131
|
-
# Given offset is offset in the subspace
|
132
|
-
def line_for_offset(offset)
|
133
|
-
@locator.line_for_offset(offset) + @leading_line_count
|
134
|
-
end
|
135
|
-
|
136
|
-
# Given offset is offset in the subspace
|
137
|
-
def offset_on_line(offset)
|
138
|
-
@locator.offset_on_line(offset) + @leading_line_offset
|
139
|
-
end
|
140
|
-
|
141
|
-
# Given offset is offset in the subspace
|
142
|
-
def char_offset(offset)
|
143
|
-
effective_line = @locator.line_for_offset(offset)
|
144
|
-
locator.char_offset(offset) + (effective_line * @leading_line_offset) + @leading_offset
|
145
|
-
end
|
146
|
-
|
147
|
-
# Given offsets are offsets in the subspace
|
148
|
-
def char_length(offset, end_offset)
|
149
|
-
effective_line = @locator.line_for_offset(end_offset) - @locator.line_for_offset(offset)
|
150
|
-
locator.char_length(offset, end_offset) + (effective_line * @leading_line_offset)
|
151
|
-
end
|
152
|
-
|
153
|
-
def pos_on_line(offset)
|
154
|
-
offset_on_line(offset) +1
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
96
|
class AbstractLocator < Locator
|
159
97
|
attr_accessor :line_index
|
160
|
-
attr_accessor :string
|
161
98
|
attr_reader :string
|
162
99
|
attr_reader :file
|
163
100
|
|
@@ -169,8 +106,7 @@ class Locator
|
|
169
106
|
@file = file.freeze
|
170
107
|
@prev_offset = nil
|
171
108
|
@prev_line = nil
|
172
|
-
@line_index = line_index
|
173
|
-
compute_line_index if line_index.nil?
|
109
|
+
@line_index = line_index.nil? ? Locator.compute_line_index(@string) : line_index
|
174
110
|
end
|
175
111
|
|
176
112
|
# Returns the position on line (first position on a line is 1)
|
@@ -235,16 +171,6 @@ class Locator
|
|
235
171
|
self.class == o.class && string == o.string && file == o.file && line_index == o.line_index
|
236
172
|
end
|
237
173
|
|
238
|
-
# Common impl for 18 and 19 since scanner is byte based
|
239
|
-
def compute_line_index
|
240
|
-
scanner = StringScanner.new(string)
|
241
|
-
result = [0] # first line starts at 0
|
242
|
-
while scanner.scan_until(/\n/)
|
243
|
-
result << scanner.pos
|
244
|
-
end
|
245
|
-
self.line_index = result.freeze
|
246
|
-
end
|
247
|
-
|
248
174
|
# Returns the line number (first line is 1) for the given offset
|
249
175
|
def line_for_offset(offset)
|
250
176
|
if @prev_offset == offset
|
@@ -264,6 +190,100 @@ class Locator
|
|
264
190
|
end
|
265
191
|
end
|
266
192
|
|
193
|
+
# A Sublocator locates a concrete locator (subspace) in a virtual space.
|
194
|
+
# The `leading_line_count` is the (virtual) number of lines preceding the first line in the concrete locator.
|
195
|
+
# The `leading_offset` is the (virtual) byte offset of the first byte in the concrete locator.
|
196
|
+
# The `leading_line_offset` is the (virtual) offset / margin in characters for each line.
|
197
|
+
#
|
198
|
+
# This illustrates characters in the sublocator (`.`) inside the subspace (`X`):
|
199
|
+
#
|
200
|
+
# 1:XXXXXXXX
|
201
|
+
# 2:XXXX.... .. ... ..
|
202
|
+
# 3:XXXX. . .... ..
|
203
|
+
# 4:XXXX............
|
204
|
+
#
|
205
|
+
# This sublocator would be configured with leading_line_count = 1,
|
206
|
+
# leading_offset=8, and leading_line_offset=4
|
207
|
+
#
|
208
|
+
# Note that leading_offset must be the same for all lines and measured in characters.
|
209
|
+
#
|
210
|
+
# A SubLocator is only used during parsing as the parser will translate the local offsets/lengths to
|
211
|
+
# the parent locator when a sublocated expression is reduced. Do not call the methods
|
212
|
+
# `char_offset` or `char_length` as those methods will raise an error.
|
213
|
+
#
|
214
|
+
class SubLocator < AbstractLocator
|
215
|
+
attr_reader :locator
|
216
|
+
attr_reader :leading_line_count
|
217
|
+
attr_reader :leading_offset
|
218
|
+
attr_reader :has_margin
|
219
|
+
attr_reader :margin_per_line
|
220
|
+
|
221
|
+
def initialize(locator, str, leading_line_count, leading_offset, has_margin, margin_per_line)
|
222
|
+
super(str, locator.file)
|
223
|
+
@locator = locator
|
224
|
+
@leading_line_count = leading_line_count
|
225
|
+
@leading_offset = leading_offset
|
226
|
+
@has_margin = has_margin
|
227
|
+
@margin_per_line = margin_per_line
|
228
|
+
|
229
|
+
# Since lines can have different margin - accumulated margin per line must be computed
|
230
|
+
# and since this accumulated margin adjustment is needed more than once; both for start offset,
|
231
|
+
# and for end offset (to compute global length) it is computed up front here.
|
232
|
+
# The accumulated_offset holds the sum of all removed margins before a position on line n (line index is 1-n,
|
233
|
+
# and (unused) position 0 is always 0).
|
234
|
+
# The last entry is duplicated since there will be the line "after last line" that would otherwise require
|
235
|
+
# conditional logic.
|
236
|
+
#
|
237
|
+
@accumulated_margin = margin_per_line.reduce([0]) {|memo, val| memo << memo[-1] + val; memo }
|
238
|
+
@accumulated_margin << @accumulated_margin[-1]
|
239
|
+
end
|
240
|
+
|
241
|
+
def file
|
242
|
+
@locator.file
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns array with transposed (local) offset and (local) length. The transposed values
|
246
|
+
# take the margin into account such that it is added to the content to the right
|
247
|
+
#
|
248
|
+
# Using X to denote margin and where end of line is explicitly shown as \n:
|
249
|
+
# ```
|
250
|
+
# XXXXabc\n
|
251
|
+
# XXXXdef\n
|
252
|
+
# ```
|
253
|
+
# A local offset of 0 is translated to the start of the first heredoc line, and a length of 1 is adjusted to
|
254
|
+
# 5 - i.e to cover "XXXXa". A local offset of 1, with length 1 would cover "b".
|
255
|
+
# A local offset of 4 and length 1 would cover "XXXXd"
|
256
|
+
#
|
257
|
+
# It is possible that lines have different margin and that is taken into account.
|
258
|
+
#
|
259
|
+
def to_global(offset, length)
|
260
|
+
# simple case, no margin
|
261
|
+
return [offset + @leading_offset, length] unless @has_margin
|
262
|
+
|
263
|
+
# compute local start and end line
|
264
|
+
start_line = line_for_offset(offset)
|
265
|
+
end_line = line_for_offset(offset+length)
|
266
|
+
|
267
|
+
# complex case when there is a margin
|
268
|
+
transposed_offset = offset == 0 ? @leading_offset : offset + @leading_offset + @accumulated_margin[start_line]
|
269
|
+
transposed_length = length +
|
270
|
+
@accumulated_margin[end_line] - @accumulated_margin[start_line] + # the margins between start and end (0 is line 1)
|
271
|
+
(offset_on_line(offset) == 0 ? margin_per_line[start_line - 1] : 0) # include start's margin in position 0
|
272
|
+
[transposed_offset, transposed_length]
|
273
|
+
end
|
274
|
+
|
275
|
+
# Do not call this method
|
276
|
+
def char_offset(offset)
|
277
|
+
raise "Should not be called"
|
278
|
+
end
|
279
|
+
|
280
|
+
# Do not call this method
|
281
|
+
def char_length(offset, end_offset)
|
282
|
+
raise "Should not be called"
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
267
287
|
class LocatorForChars < AbstractLocator
|
268
288
|
|
269
289
|
def offset_on_line(offset)
|
@@ -311,7 +331,7 @@ class Locator
|
|
311
331
|
# Ruby 19 is multibyte but has no character position methods, must use byteslice
|
312
332
|
def offset_on_line(offset)
|
313
333
|
line_offset = line_index[ line_for_offset(offset)-1 ]
|
314
|
-
string.byteslice(line_offset, offset-line_offset).length
|
334
|
+
@string.byteslice(line_offset, offset-line_offset).length
|
315
335
|
end
|
316
336
|
|
317
337
|
# Returns the character offset for a given byte offset
|