bundler 4.1.0.beta2 → 4.1.0.beta3

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.
@@ -16,30 +16,27 @@ end
16
16
 
17
17
  module Gem
18
18
  module YAMLSerializer
19
- Scalar = Struct.new(:value, :tag, :anchor, keyword_init: true)
20
-
21
- Mapping = Struct.new(:pairs, :tag, :anchor, keyword_init: true) do
22
- def initialize(pairs: [], tag: nil, anchor: nil)
23
- super
24
- end
25
- end
26
-
27
- Sequence = Struct.new(:items, :tag, :anchor, keyword_init: true) do
28
- def initialize(items: [], tag: nil, anchor: nil)
29
- super
30
- end
31
- end
32
-
33
- AliasRef = Struct.new(:name, keyword_init: true)
19
+ Scalar = Struct.new(:value, :tag, :anchor)
20
+ Mapping = Struct.new(:pairs, :tag, :anchor)
21
+ Sequence = Struct.new(:items, :tag, :anchor)
22
+ AliasRef = Struct.new(:name)
34
23
 
35
24
  class Parser
36
25
  # A plain (unquoted) mapping key followed by ":". "#" inside a key is
37
26
  # literal unless preceded by a space (which would start a comment).
38
- MAPPING_KEY_RE = /^((?:[^#:\s]|:[^ ])(?:[^#:]|:[^ ]|(?<=[^ ])#)*):(?:[ ]+(.*))?$/
27
+ MAPPING_KEY_RE = /\G((?:[^#:\s]|:[^ ])(?:[^#:]|:[^ ]|(?<=[^ ])#)*):(?:[ ]+(.*))?$/
39
28
  # A quoted mapping key followed by ":". A whole-line quoted scalar
40
29
  # cannot match because its closing quote is at the end of the line.
41
- QUOTED_KEY_RE = /^("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'):(?:[ ]+(.*))?$/
30
+ QUOTED_KEY_RE = /\G("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'):(?:[ ]+(.*))?$/
31
+ # An entry of a flow sequence. A quoted entry may contain commas.
32
+ FLOW_SEQUENCE_ENTRY_RE = /"(?:[^"\\]|\\.)*"|'(?:[^']|'')*'|[^,\s](?:[^,]*[^,\s])?/
33
+ SEQUENCE_ENTRY_RE = /\G-(?: |\z)/
34
+ RUBY_OBJECT_TAG_RE = %r{\G!ruby/object:}
35
+ LSTRIP_BYTES = [0, 9, 10, 11, 12, 13, 32].freeze
42
36
  MAX_NESTING_DEPTH = 1_000
37
+ NULL_SCALARS = %w[~ null].freeze
38
+ BLANK_RE = /\A[\0\s]*\z/
39
+ BLANK_OR_COMMENT_RE = /\A[\0\s]*(?:#|\z)/
43
40
 
44
41
  STRING_UNESCAPES = {
45
42
  "\\\\" => "\\",
@@ -53,10 +50,14 @@ module Gem
53
50
  "\\f" => "\f",
54
51
  "\\r" => "\r",
55
52
  "\\e" => "\e",
53
+ "\\N" => "\u0085",
54
+ "\\_" => "\u00A0",
55
+ "\\L" => "\u2028",
56
+ "\\P" => "\u2029",
56
57
  }.freeze
57
58
 
58
59
  def initialize(source)
59
- @lines = source.split(/\r?\n/)
60
+ @lines = source.split(/\r?\n/).each(&:freeze)
60
61
  @anchors = {}
61
62
  @depth = 0
62
63
  strip_document_prefix
@@ -101,37 +102,35 @@ module Gem
101
102
  return nil if @lines.empty?
102
103
 
103
104
  line = @lines[0]
104
- stripped = line.lstrip
105
- indent = line.size - stripped.size
105
+ indent = indent_of(line)
106
106
  return nil if indent < base_indent
107
107
 
108
- return parse_alias_ref if stripped.start_with?("*")
108
+ return parse_alias_ref if /\G\*/.match?(line, indent)
109
109
 
110
- anchor = consume_anchor
111
-
112
- if anchor
110
+ pos = indent
111
+ if /\G&/.match?(line, indent) && (anchor = consume_anchor)
113
112
  line = @lines[0]
114
- stripped = line.lstrip
113
+ pos = indent_of(line)
115
114
  end
116
115
 
117
- if stripped.start_with?("- ") || stripped == "-"
116
+ if SEQUENCE_ENTRY_RE.match?(line, pos)
118
117
  parse_sequence(indent, anchor)
119
- elsif QUOTED_KEY_RE.match?(stripped)
118
+ elsif QUOTED_KEY_RE.match?(line, pos)
120
119
  # A mapping whose key is quoted, e.g. '"have: colon": value'.
121
120
  parse_mapping(indent, anchor)
122
- elsif (stripped.start_with?("\"") && stripped.end_with?("\"")) ||
123
- (stripped.start_with?("'") && stripped.end_with?("'"))
121
+ elsif (/\G"/.match?(line, pos) && line.end_with?("\"")) ||
122
+ (/\G'/.match?(line, pos) && line.end_with?("'"))
124
123
  # A whole-line quoted scalar, e.g. '"system: foo: bar"'. It may
125
124
  # contain ": ", so it must be checked before MAPPING_KEY_RE.
126
125
  parse_plain_scalar(indent, anchor)
127
- elsif stripped =~ MAPPING_KEY_RE && !stripped.start_with?("!ruby/object:")
126
+ elsif MAPPING_KEY_RE.match?(line, pos) && !RUBY_OBJECT_TAG_RE.match?(line, pos)
128
127
  parse_mapping(indent, anchor)
129
- elsif stripped.start_with?("!ruby/object:")
128
+ elsif RUBY_OBJECT_TAG_RE.match?(line, pos)
130
129
  parse_tagged_node(indent, anchor)
131
- elsif stripped.start_with?("|")
132
- modifier = stripped[1..].to_s.strip
130
+ elsif /\G\|/.match?(line, pos)
131
+ modifier = line[pos + 1, line.size].strip
133
132
  @lines.shift
134
- register_anchor(anchor, Scalar.new(value: parse_block_scalar(indent, modifier)))
133
+ register_anchor(anchor, Scalar.new(parse_block_scalar(indent, modifier)))
135
134
  else
136
135
  parse_plain_scalar(indent, anchor)
137
136
  end
@@ -142,22 +141,26 @@ module Gem
142
141
  def parse_sequence(indent, anchor)
143
142
  items = []
144
143
  while @lines.any?
144
+ skip_blank_and_comments
145
+ break if @lines.empty?
145
146
  line = @lines[0]
146
- stripped = line.lstrip
147
- break unless line.size - stripped.size == indent &&
148
- (stripped.start_with?("- ") || stripped == "-")
149
- content = @lines.shift.lstrip[1..].strip
150
- item_anchor, content = extract_item_anchor(content)
147
+ break unless indent_of(line) == indent && SEQUENCE_ENTRY_RE.match?(line, indent)
148
+ @lines.shift
149
+ content = line[indent + 1, line.size]
150
+ content.strip!
151
+ item_anchor = nil
152
+ item_anchor, content = extract_item_anchor(content) if content.start_with?("&")
151
153
  item = parse_sequence_item(content, indent)
152
154
  items << register_anchor(item_anchor, item)
153
155
  end
154
- register_anchor(anchor, Sequence.new(items: items))
156
+ register_anchor(anchor, Sequence.new(items))
155
157
  end
156
158
 
157
159
  def parse_sequence_item(content, indent)
158
160
  if content.start_with?("*")
159
161
  parse_inline_alias(content)
160
162
  elsif content.empty?
163
+ skip_blank_and_comments
161
164
  @lines.any? && current_indent > indent ? parse_node(indent) : nil
162
165
  elsif content.start_with?("!ruby/object:")
163
166
  parse_tagged_content(content.strip, indent)
@@ -167,11 +170,11 @@ module Gem
167
170
  @lines.unshift("#{" " * (indent + 2)}#{content}")
168
171
  parse_node(indent)
169
172
  elsif QUOTED_KEY_RE.match?(content) ||
170
- (content =~ MAPPING_KEY_RE && !content.start_with?("!ruby/object:"))
173
+ (MAPPING_KEY_RE.match?(content) && !content.start_with?("!ruby/object:"))
171
174
  @lines.unshift("#{" " * (indent + 2)}#{content}")
172
175
  parse_node(indent)
173
176
  elsif content.start_with?("|")
174
- Scalar.new(value: parse_block_scalar(indent, content[1..].to_s.strip))
177
+ Scalar.new(parse_block_scalar(indent, content[1..].to_s.strip))
175
178
  else
176
179
  parse_inline_scalar(content, indent)
177
180
  end
@@ -180,28 +183,33 @@ module Gem
180
183
  def parse_mapping(indent, anchor)
181
184
  pairs = []
182
185
  while @lines.any?
186
+ skip_blank_and_comments
187
+ break if @lines.empty?
183
188
  line = @lines[0]
184
- stripped = line.lstrip
185
- break unless line.size - stripped.size == indent
189
+ break unless indent_of(line) == indent
186
190
 
187
- if (match = QUOTED_KEY_RE.match(stripped))
191
+ if (match = QUOTED_KEY_RE.match(line, indent))
188
192
  key = coerce(match[1])
189
- elsif (match = MAPPING_KEY_RE.match(stripped)) && !stripped.start_with?("!ruby/object:")
190
- key = match[1].strip
193
+ elsif (match = MAPPING_KEY_RE.match(line, indent)) && !RUBY_OBJECT_TAG_RE.match?(line, indent)
194
+ key = match[1]
195
+ key.strip!
191
196
  key = decode_binary_tag(key) if key.start_with?("!binary ")
192
197
  else
193
198
  break
194
199
  end
195
200
  @lines.shift
196
- val = strip_comment(match[2].to_s.strip)
201
+ val = match[2]
202
+ val&.strip!
203
+ val = strip_comment(val || "")
197
204
 
198
- val_anchor, val = consume_value_anchor(val)
205
+ val_anchor = nil
206
+ val_anchor, val = consume_value_anchor(val) if val.start_with?("&")
199
207
  value = parse_mapping_value(val, indent)
200
208
  value = register_anchor(val_anchor, value) if val_anchor
201
209
 
202
- pairs << [Scalar.new(value: key), value]
210
+ pairs << key << value
203
211
  end
204
- register_anchor(anchor, Mapping.new(pairs: pairs))
212
+ register_anchor(anchor, Mapping.new(pairs))
205
213
  end
206
214
 
207
215
  def parse_mapping_value(val, indent)
@@ -212,25 +220,19 @@ module Gem
212
220
  elsif val.start_with?("!binary ")
213
221
  parse_binary_value(val, indent)
214
222
  elsif val.empty?
215
- next_stripped = nil
216
- next_indent = nil
217
- if @lines.any?
218
- next_stripped = @lines[0].lstrip
219
- next_indent = @lines[0].size - next_stripped.size
220
- end
221
- if next_stripped &&
222
- (next_stripped.start_with?("- ") || next_stripped == "-") &&
223
- next_indent == indent
223
+ skip_blank_and_comments
224
+ line = @lines[0]
225
+ if line && indent_of(line) == indent && SEQUENCE_ENTRY_RE.match?(line, indent)
224
226
  parse_node(indent)
225
227
  else
226
228
  parse_node(indent + 1)
227
229
  end
228
230
  elsif val == "[]"
229
- Sequence.new
231
+ Sequence.new([])
230
232
  elsif val == "{}"
231
- Mapping.new
233
+ Mapping.new([])
232
234
  elsif val.start_with?("|")
233
- Scalar.new(value: parse_block_scalar(indent, val[1..].to_s.strip))
235
+ Scalar.new(parse_block_scalar(indent, val[1..].to_s.strip))
234
236
  else
235
237
  parse_inline_scalar(val, indent)
236
238
  end
@@ -253,21 +255,24 @@ module Gem
253
255
  node.anchor = anchor
254
256
  node
255
257
  else
256
- Mapping.new(pairs: [[Scalar.new(value: "value"), node]], tag: tag, anchor: anchor)
258
+ Mapping.new(["value", node], tag, anchor)
257
259
  end
258
260
  end
259
261
 
260
262
  def parse_block_scalar(base_indent, modifier)
261
263
  parts = []
262
- block_indent = nil
264
+ indentation = modifier[/\A[+-]?([1-9])/, 1]
265
+ chomping = modifier[/\A[1-9]?([+-])/, 1]
266
+ block_indent = indentation && base_indent + indentation.to_i
263
267
 
264
268
  while @lines.any?
265
269
  line = @lines[0]
266
- if line.strip.empty?
270
+ if BLANK_RE.match?(line)
271
+ parts << line[block_indent..] if block_indent && line.size > block_indent
267
272
  parts << "\n"
268
273
  @lines.shift
269
274
  else
270
- line_indent = line.size - line.lstrip.size
275
+ line_indent = indent_of(line)
271
276
  break if line_indent <= base_indent
272
277
  block_indent ||= line_indent
273
278
  parts << @lines.shift[block_indent..].to_s << "\n"
@@ -275,30 +280,35 @@ module Gem
275
280
  end
276
281
 
277
282
  res = parts.join
278
- res.chomp! if modifier == "-" && res.end_with?("\n")
283
+ res.delete_suffix!("\n") while chomping == "-" && res.end_with?("\n")
279
284
  res
280
285
  end
281
286
 
282
287
  def parse_plain_scalar(indent, anchor)
283
- result = coerce(@lines.shift.strip)
288
+ val = @lines.shift.strip
289
+ result = coerce(val)
284
290
  return register_anchor(anchor, result) if result.is_a?(Mapping) || result.is_a?(Sequence)
285
291
 
286
- while result.is_a?(String) && @lines.any? &&
287
- !@lines[0].strip.empty? && current_indent > indent
288
- result << " " << @lines.shift.strip
289
- end
290
- register_anchor(anchor, Scalar.new(value: result))
292
+ fold_continuation_lines(result, val, indent)
293
+ register_anchor(anchor, Scalar.new(result))
291
294
  end
292
295
 
293
296
  def parse_inline_scalar(val, indent)
294
297
  result = coerce(val)
295
298
  return result if result.is_a?(Mapping) || result.is_a?(Sequence)
296
299
 
297
- while result.is_a?(String) && @lines.any? &&
298
- !@lines[0].strip.empty? && current_indent > indent
300
+ fold_continuation_lines(result, val, indent)
301
+ Scalar.new(result)
302
+ end
303
+
304
+ def fold_continuation_lines(result, val, indent)
305
+ return unless result.is_a?(String)
306
+
307
+ # "#" does not start a comment inside a quoted scalar that runs onto the next lines.
308
+ stop = val.start_with?("'", "\"") && result == val ? BLANK_RE : BLANK_OR_COMMENT_RE
309
+ while @lines.any? && !stop.match?(@lines[0]) && current_indent > indent
299
310
  result << " " << @lines.shift.strip
300
311
  end
301
- Scalar.new(value: result)
302
312
  end
303
313
 
304
314
  def coerce(val, depth = 0)
@@ -306,25 +316,30 @@ module Gem
306
316
 
307
317
  val = val.sub(/^! /, "") if val.start_with?("! ")
308
318
 
309
- if val =~ /^"(.*)"$/
310
- $1.gsub(/\\(?:["\\0abtnvfre]|x\h{2})/) do |m|
311
- STRING_UNESCAPES[m] || m[2..].to_i(16).chr(Encoding::UTF_8)
319
+ if val.size >= 2 && val.start_with?("\"") && val.end_with?("\"")
320
+ inner = val[1..-2]
321
+ return inner unless inner.include?("\\")
322
+ inner.gsub(/\\(?:["\\0abtnvfreN_LP]|x\h{2}|u\h{4}|U\h{8})/) do |m|
323
+ char = STRING_UNESCAPES[m] || unescape_code_point(m)
324
+ # Metadata is read as binary, where non-ASCII text stays as UTF-8 bytes.
325
+ Encoding.compatible?(inner, char) ? char : char.b
312
326
  end
313
- elsif val =~ /^'(.*)'$/
314
- $1.gsub(/''/, "'")
327
+ elsif val.size >= 2 && val.start_with?("'") && val.end_with?("'")
328
+ inner = val[1..-2]
329
+ inner.include?("''") ? inner.gsub("''", "'") : inner
315
330
  elsif val == "true"
316
331
  true
317
332
  elsif val == "false"
318
333
  false
319
- elsif ["~", "null"].include?(val)
334
+ elsif NULL_SCALARS.include?(val)
320
335
  nil
321
336
  elsif val == "{}"
322
- Mapping.new
337
+ Mapping.new([])
323
338
  elsif val =~ /^\[(.*)\]$/
324
339
  inner = $1.strip
325
- return Sequence.new if inner.empty?
326
- items = inner.split(/\s*,\s*/).reject(&:empty?).map {|e| Scalar.new(value: coerce(e, depth + 1)) }
327
- Sequence.new(items: items)
340
+ return Sequence.new([]) if inner.empty?
341
+ items = inner.scan(FLOW_SEQUENCE_ENTRY_RE).map {|e| Scalar.new(coerce(e, depth + 1)) }
342
+ Sequence.new(items)
328
343
  elsif /\A\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}:\d{2})?/.match?(val)
329
344
  begin
330
345
  Time.new(val)
@@ -343,6 +358,12 @@ module Gem
343
358
  end
344
359
  end
345
360
 
361
+ def unescape_code_point(escape)
362
+ code = escape[2..].hex
363
+ return escape if code > 0x10FFFF || code.between?(0xD800, 0xDFFF)
364
+ code.chr(Encoding::UTF_8)
365
+ end
366
+
346
367
  def decode_binary_tag(str)
347
368
  content = str.sub(/\A!binary\s+/, "")
348
369
  content = $1 if content =~ /\A"(.*)"\z/ || content =~ /\A'(.*)'\z/
@@ -353,23 +374,28 @@ module Gem
353
374
  rest = val.sub(/\A!binary\s+/, "")
354
375
  if rest.start_with?("|")
355
376
  content = parse_block_scalar(indent, rest[1..].to_s.strip)
356
- Scalar.new(value: content.unpack1("m"))
377
+ Scalar.new(content.unpack1("m"))
357
378
  else
358
- Scalar.new(value: decode_binary_tag(val))
379
+ Scalar.new(decode_binary_tag(val))
359
380
  end
360
381
  end
361
382
 
362
383
  def parse_alias_ref
363
- AliasRef.new(name: @lines.shift.lstrip[1..].strip)
384
+ AliasRef.new(@lines.shift.lstrip[1..].strip)
364
385
  end
365
386
 
366
387
  def parse_inline_alias(content)
367
- AliasRef.new(name: content[1..].strip)
388
+ AliasRef.new(content[1..].strip)
368
389
  end
369
390
 
370
391
  def current_indent
371
- line = @lines[0]
372
- line.size - line.lstrip.size
392
+ indent_of(@lines[0])
393
+ end
394
+
395
+ def indent_of(line)
396
+ i = 0
397
+ i += 1 while LSTRIP_BYTES.include?(line.getbyte(i))
398
+ i
373
399
  end
374
400
 
375
401
  def consume_anchor
@@ -414,12 +440,7 @@ module Gem
414
440
  end
415
441
 
416
442
  def skip_blank_and_comments
417
- while @lines.any?
418
- line = @lines[0]
419
- stripped = line.lstrip
420
- break unless stripped.empty? || stripped.start_with?("#")
421
- @lines.shift
422
- end
443
+ @lines.shift while @lines.any? && BLANK_OR_COMMENT_RE.match?(@lines[0])
423
444
  end
424
445
 
425
446
  def strip_comment(val)
@@ -542,8 +563,7 @@ module Gem
542
563
 
543
564
  def build_hash(node)
544
565
  result = {}
545
- node.pairs.each do |key_node, value_node|
546
- key = key_node.is_a?(Scalar) ? key_node.value.to_s : build_node(key_node).to_s
566
+ each_pair(node) do |key, value_node|
547
567
  value = build_node(value_node)
548
568
 
549
569
  if ARRAY_FIELDS.include?(key)
@@ -643,13 +663,21 @@ module Gem
643
663
 
644
664
  def pairs_to_hash(node)
645
665
  result = {}
646
- node.pairs.each do |key_node, value_node|
647
- key = key_node.is_a?(Scalar) ? key_node.value.to_s : build_node(key_node).to_s
666
+ each_pair(node) do |key, value_node|
648
667
  result[key] = build_node(value_node)
649
668
  end
650
669
  result
651
670
  end
652
671
 
672
+ def each_pair(node)
673
+ pairs = node.pairs
674
+ i = 0
675
+ while i < pairs.size
676
+ yield pairs[i], pairs[i + 1]
677
+ i += 2
678
+ end
679
+ end
680
+
653
681
  def validate_tag!(tag)
654
682
  return if @permitted_classes.include?(tag)
655
683
  raise_disallowed_class!(tag)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0.beta2
4
+ version: 4.1.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
@@ -411,7 +411,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
411
411
  - !ruby/object:Gem::Version
412
412
  version: 3.4.1
413
413
  requirements: []
414
- rubygems_version: 4.1.0.beta2
414
+ rubygems_version: 4.1.0.beta3
415
415
  specification_version: 4
416
416
  summary: The best way to manage your application's dependencies
417
417
  test_files: []