Class: Gem::YAMLSerializer::Parser
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | lib/rubygems/yaml_serializer.rb |
Constant Summary
-
MAPPING_KEY_RE =
# File 'lib/rubygems/yaml_serializer.rb', line 32/^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ -
MAX_NESTING_DEPTH =
# File 'lib/rubygems/yaml_serializer.rb', line 331_000
Class Method Summary
- .new(source) ⇒ Parser constructor
Instance Method Summary
- #parse
- #apply_tag(node, tag, anchor) private
- #coerce(val, depth = 0) private
- #consume_anchor private
- #consume_value_anchor(val) private
- #current_indent private
- #decode_binary_tag(str) private
- #extract_item_anchor(content) private
- #parse_alias_ref private
- #parse_binary_value(val, indent) private
- #parse_block_scalar(base_indent, modifier) private
- #parse_inline_alias(content) private
- #parse_inline_scalar(val, indent) private
- #parse_mapping(indent, anchor) private
- #parse_mapping_value(val, indent) private
- #parse_node(base_indent) private
- #parse_plain_scalar(indent, anchor) private
- #parse_sequence(indent, anchor) private
- #parse_sequence_item(content, indent) private
- #parse_tagged_content(tag, indent) private
- #parse_tagged_node(indent, anchor) private
- #raise_max_nesting! private
- #register_anchor(name, node) private
- #skip_blank_and_comments private
- #strip_comment(val) private
- #strip_document_prefix private
Constructor Details
.new(source) ⇒ Parser
# File 'lib/rubygems/yaml_serializer.rb', line 35
def initialize(source) @lines = source.split("\n") @anchors = {} @depth = 0 strip_document_prefix end
Instance Method Details
#apply_tag(node, tag, anchor) (private)
[ GitHub ]#coerce(val, depth = 0) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 286
def coerce(val, depth = 0) raise_max_nesting! if depth > MAX_NESTING_DEPTH val = val.sub(/^! /, "") if val.start_with?("! ") if val =~ /^"(.*)"$/ $1.gsub(/\\["nrt\\]/) do |m| case m when '\\"' then '"' when "\\n" then "\n" when "\\r" then "\r" when "\\t" then "\t" when "\\\\" then "\\" end end elsif val =~ /^'(.*)'$/ $1.gsub(/''/, "'") elsif val == "true" true elsif val == "false" false elsif ["~", "null"].include?(val) 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, depth + 1)) } 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 363
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 380
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 358
def current_indent line = @lines[0] line.size - line.lstrip.size end
#decode_binary_tag(str) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 334
def decode_binary_tag(str) content = str.sub(/\A!binary\s+/, "") content = $1 if content =~ /\A"(.*)"\z/ || content =~ /\A'(.*)'\z/ content.unpack1("m") end
#extract_item_anchor(content) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 373
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 42
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 ]#parse_binary_value(val, indent) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 340
def parse_binary_value(val, indent) rest = val.sub(/\A!binary\s+/, "") if rest.start_with?("|") content = parse_block_scalar(indent, rest[1..].to_s.strip) Scalar.new(value: content.unpack1("m")) else Scalar.new(value: decode_binary_tag(val)) end end
#parse_block_scalar(base_indent, modifier) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 242
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 ]#parse_inline_scalar(val, indent) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 275
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 167
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) key = decode_binary_tag(key) if key.start_with?("!binary ") 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 189
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.start_with?("!binary ") parse_binary_value(val, 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 73
def parse_node(base_indent) @depth += 1 raise_max_nesting! if @depth > MAX_NESTING_DEPTH 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.start_with?("\"") && stripped.end_with?("\"") # We don't need to care about the following case here: # 1. "value with comment" # ... # 2. "key": "value" # # 1. must not happen because YAMLSerializer doesn't emit any # comment. YAMLSerializer parses only YAML that is generated # by YAMLSerializer. # # 2. must not happen because #parse_node isn't used non # top-level mapping. Non top-level mapping always uses # #parse_mapping. Top-level mapping never use the '"key": # "value"' form because all top-level keys # ("!ruby/object:Gem::Specification"'s keys) are known and # #emit_specification doesn't quote anything. parse_plain_scalar(indent, anchor) elsif stripped.start_with?("'") && stripped.end_with?("'") # See also the above note for double quotation. parse_plain_scalar(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 ensure @depth -= 1 end
#parse_plain_scalar(indent, anchor) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 264
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 130
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 145
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?("!binary ") parse_binary_value(content, 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 227
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 221
def parse_tagged_node(indent, anchor) tag = @lines.shift.strip nested = parse_node(indent) apply_tag(nested, tag, anchor) end
#raise_max_nesting! (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 395
def raise_max_nesting! = "exceeded maximum nesting depth (#{MAX_NESTING_DEPTH})" if defined?(Psych::VERSION) raise Psych::SyntaxError.new(nil, 0, 0, 0, , nil) else raise Psych::SyntaxError, end end
#register_anchor(name, node) (private)
[ GitHub ]# File 'lib/rubygems/yaml_serializer.rb', line 387
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 404
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 413
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 62
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