123456789_123456789_123456789_123456789_123456789_

Class: Gem::YAMLSerializer::Parser

Relationships & Source Files
Inherits: Object
Defined in: lib/rubygems/yaml_serializer.rb

Constant Summary

Class Method Summary

Instance Method Summary

Constructor Details

.new(source) ⇒ Parser

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 34

def initialize(source)
  @lines = source.split("\n")
  @anchors = {}
  strip_document_prefix
end

Instance Method Details

#apply_tag(node, tag, anchor) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 200

def apply_tag(node, tag, anchor)
  if node.is_a?(Mapping)
    node.tag = tag
    node.anchor = anchor
    node
  else
    Mapping.new(pairs: [[Scalar.new(value: "value"), node]], tag: tag, anchor: anchor)
  end
end

#coerce(val) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 254

def coerce(val)
  val = val.sub(/^! /, "") if val.start_with?("! ")

  if val =~ /^"(.*)"$/
    $1.gsub(/\\"/, '"').gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\t/, "\t").gsub(/\\\\/, "\\")
  elsif val =~ /^'(.*)'$/
    $1.gsub(/''/, "'")
  elsif val == "true"
    true
  elsif val == "false"
    false
  elsif val == "nil"
    nil
  elsif val == "{}"
    Mapping.new
  elsif val =~ /^\[(.*)\]$/
    inner = $1.strip
    return Sequence.new if inner.empty?
    items = inner.split(/\s*,\s*/).reject(&:empty?).map {|e| Scalar.new(value: coerce(e)) }
    Sequence.new(items: items)
  elsif /\A\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}:\d{2})?/.match?(val)
    begin
      Time.new(val)
    rescue ArgumentError
      # date-only format like "2024-06-15" is not supported by Time.new
      if /\A(\d{4})-(\d{2})-(\d{2})\z/.match(val)
        Time.utc($1.to_i, $2.to_i, $3.to_i)
      else
        val
      end
    end
  elsif /^-?\d+$/.match?(val)
    val.to_i
  else
    val
  end
end

#consume_anchor (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 305

def consume_anchor
  line = @lines[0]
  stripped = line.lstrip
  return nil unless stripped.start_with?("&") && stripped =~ /^&(\S)\s/

  anchor = $1
  @lines[0] = line.sub(/&#{Regexp.escape(anchor)}\s+/, "")
  anchor
end

#consume_value_anchor(val) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 322

def consume_value_anchor(val)
  return [nil, val] unless val =~ /^&(\S)\s/

  anchor = $1
  [anchor, val.sub(/^&#{Regexp.escape(anchor)}\s+/, "")]
end

#current_indent (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 300

def current_indent
  line = @lines[0]
  line.size - line.lstrip.size
end

#extract_item_anchor(content) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 315

def extract_item_anchor(content)
  return [nil, content] unless content =~ /^&(\S+)/

  anchor = $1
  [anchor, content.sub(/^&#{Regexp.escape(anchor)}\s*/, "")]
end

#parse

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 40

def parse
  return nil if @lines.empty?

  root = nil
  while @lines.any?
    before = @lines.size
    node = parse_node(-1)
    @lines.shift if @lines.size == before && @lines.any?

    if root.is_a?(Mapping) && node.is_a?(Mapping)
      root.pairs.concat(node.pairs)
    elsif root.nil?
      root = node
    end
  end
  root
end

#parse_alias_ref (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 292

def parse_alias_ref
  AliasRef.new(name: @lines.shift.lstrip[1..].strip)
end

#parse_block_scalar(base_indent, modifier) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 210

def parse_block_scalar(base_indent, modifier)
  parts = []
  block_indent = nil

  while @lines.any?
    line = @lines[0]
    if line.strip.empty?
      parts << "\n"
      @lines.shift
    else
      line_indent = line.size - line.lstrip.size
      break if line_indent <= base_indent
      block_indent ||= line_indent
      parts << @lines.shift[block_indent..].to_s << "\n"
    end
  end

  res = parts.join
  res.chomp! if modifier == "-" && res.end_with?("\n")
  res
end

#parse_inline_alias(content) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 296

def parse_inline_alias(content)
  AliasRef.new(name: content[1..].strip)
end

#parse_inline_scalar(val, indent) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 243

def parse_inline_scalar(val, indent)
  result = coerce(val)
  return result if result.is_a?(Mapping) || result.is_a?(Sequence)

  while result.is_a?(String) && @lines.any? &&
        !@lines[0].strip.empty? && current_indent > indent
    result << " " << @lines.shift.strip
  end
  Scalar.new(value: result)
end

#parse_mapping(indent, anchor) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 139

def parse_mapping(indent, anchor)
  pairs = []
  while @lines.any?
    line = @lines[0]
    stripped = line.lstrip
    break unless line.size - stripped.size == indent &&
                 stripped =~ MAPPING_KEY_RE && !stripped.start_with?("!ruby/object:")
    key = $1.strip
    @lines.shift
    val = strip_comment($2.to_s.strip)

    val_anchor, val = consume_value_anchor(val)
    value = parse_mapping_value(val, indent)
    value = register_anchor(val_anchor, value) if val_anchor

    pairs << [Scalar.new(value: key), value]
  end
  register_anchor(anchor, Mapping.new(pairs: pairs))
end

#parse_mapping_value(val, indent) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 159

def parse_mapping_value(val, indent)
  if val.start_with?("*")
    parse_inline_alias(val)
  elsif val.start_with?("!ruby/object:")
    parse_tagged_content(val.strip, indent)
  elsif val.empty?
    next_stripped = nil
    next_indent = nil
    if @lines.any?
      next_stripped = @lines[0].lstrip
      next_indent = @lines[0].size - next_stripped.size
    end
    if next_stripped &&
       (next_stripped.start_with?("- ") || next_stripped == "-") &&
       next_indent == indent
      parse_node(indent)
    else
      parse_node(indent + 1)
    end
  elsif val == "[]"
    Sequence.new
  elsif val == "{}"
    Mapping.new
  elsif val.start_with?("|")
    Scalar.new(value: parse_block_scalar(indent, val[1..].to_s.strip))
  else
    parse_inline_scalar(val, indent)
  end
end

#parse_node(base_indent) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 71

def parse_node(base_indent)
  skip_blank_and_comments
  return nil if @lines.empty?

  line = @lines[0]
  stripped = line.lstrip
  indent = line.size - stripped.size
  return nil if indent < base_indent

  return parse_alias_ref if stripped.start_with?("*")

  anchor = consume_anchor

  if anchor
    line = @lines[0]
    stripped = line.lstrip
  end

  if stripped.start_with?("- ") || stripped == "-"
    parse_sequence(indent, anchor)
  elsif stripped =~ MAPPING_KEY_RE && !stripped.start_with?("!ruby/object:")
    parse_mapping(indent, anchor)
  elsif stripped.start_with?("!ruby/object:")
    parse_tagged_node(indent, anchor)
  elsif stripped.start_with?("|")
    modifier = stripped[1..].to_s.strip
    @lines.shift
    register_anchor(anchor, Scalar.new(value: parse_block_scalar(indent, modifier)))
  else
    parse_plain_scalar(indent, anchor)
  end
end

#parse_plain_scalar(indent, anchor) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 232

def parse_plain_scalar(indent, anchor)
  result = coerce(@lines.shift.strip)
  return register_anchor(anchor, result) if result.is_a?(Mapping) || result.is_a?(Sequence)

  while result.is_a?(String) && @lines.any? &&
        !@lines[0].strip.empty? && current_indent > indent
    result << " " << @lines.shift.strip
  end
  register_anchor(anchor, Scalar.new(value: result))
end

#parse_sequence(indent, anchor) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 104

def parse_sequence(indent, anchor)
  items = []
  while @lines.any?
    line = @lines[0]
    stripped = line.lstrip
    break unless line.size - stripped.size == indent &&
                 (stripped.start_with?("- ") || stripped == "-")
    content = @lines.shift.lstrip[1..].strip
    item_anchor, content = extract_item_anchor(content)
    item = parse_sequence_item(content, indent)
    items << register_anchor(item_anchor, item)
  end
  register_anchor(anchor, Sequence.new(items: items))
end

#parse_sequence_item(content, indent) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 119

def parse_sequence_item(content, indent)
  if content.start_with?("*")
    parse_inline_alias(content)
  elsif content.empty?
    @lines.any? && current_indent > indent ? parse_node(indent) : nil
  elsif content.start_with?("!ruby/object:")
    parse_tagged_content(content.strip, indent)
  elsif content.start_with?("-")
    @lines.unshift("#{" " * (indent + 2)}#{content}")
    parse_node(indent)
  elsif content =~ MAPPING_KEY_RE && !content.start_with?("!ruby/object:")
    @lines.unshift("#{" " * (indent + 2)}#{content}")
    parse_node(indent)
  elsif content.start_with?("|")
    Scalar.new(value: parse_block_scalar(indent, content[1..].to_s.strip))
  else
    parse_inline_scalar(content, indent)
  end
end

#parse_tagged_content(tag, indent) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 195

def parse_tagged_content(tag, indent)
  nested = parse_node(indent)
  apply_tag(nested, tag, nil)
end

#parse_tagged_node(indent, anchor) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 189

def parse_tagged_node(indent, anchor)
  tag = @lines.shift.strip
  nested = parse_node(indent)
  apply_tag(nested, tag, anchor)
end

#register_anchor(name, node) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 329

def register_anchor(name, node)
  if name
    @anchors[name] = node
    node.anchor = name if node.respond_to?(:anchor=)
  end
  node
end

#skip_blank_and_comments (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 337

def skip_blank_and_comments
  while @lines.any?
    line = @lines[0]
    stripped = line.lstrip
    break unless stripped.empty? || stripped.start_with?("#")
    @lines.shift
  end
end

#strip_comment(val) (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 346

def strip_comment(val)
  return val unless val.include?("#")
  return val if val.lstrip.start_with?("#")

  in_single = false
  in_double = false
  escape = false

  val.each_char.with_index do |ch, i|
    if escape
      escape = false
      next
    end

    if in_single
      in_single = false if ch == "'"
    elsif in_double
      if ch == "\\"
        escape = true
      elsif ch == '"'
        in_double = false
      end
    else
      case ch
      when "'" then in_single = true
      when '"' then in_double = true
      when "#" then return val[0...i].rstrip
      end
    end
  end

  val
end

#strip_document_prefix (private)

[ GitHub ]

  
# File 'lib/rubygems/yaml_serializer.rb', line 60

def strip_document_prefix
  return if @lines.empty?
  return unless @lines[0]&.start_with?("---")

  if @lines[0].strip == "---"
    @lines.shift
  else
    @lines[0] = @lines[0].sub(/^---\s*/, "")
  end
end