summaryrefslogtreecommitdiff
path: root/ext/psych/lib
diff options
context:
space:
mode:
authorAnas Nashif <anas.nashif@intel.com>2012-11-06 11:57:02 -0800
committerAnas Nashif <anas.nashif@intel.com>2012-11-06 11:57:02 -0800
commit3b35de2a90e26b99e2a6d4f61dc56d6ce7ded748 (patch)
treef66334a0ad8cf59590dd682d95d6244e8b454853 /ext/psych/lib
downloadruby-3b35de2a90e26b99e2a6d4f61dc56d6ce7ded748.tar.gz
ruby-3b35de2a90e26b99e2a6d4f61dc56d6ce7ded748.tar.bz2
ruby-3b35de2a90e26b99e2a6d4f61dc56d6ce7ded748.zip
Imported Upstream version 1.9.3.p194upstream/1.9.3.p194
Diffstat (limited to 'ext/psych/lib')
-rw-r--r--ext/psych/lib/psych.rb331
-rw-r--r--ext/psych/lib/psych/coder.rb94
-rw-r--r--ext/psych/lib/psych/core_ext.rb42
-rw-r--r--ext/psych/lib/psych/deprecated.rb83
-rw-r--r--ext/psych/lib/psych/handler.rb221
-rw-r--r--ext/psych/lib/psych/handlers/document_stream.rb22
-rw-r--r--ext/psych/lib/psych/json/ruby_events.rb19
-rw-r--r--ext/psych/lib/psych/json/stream.rb15
-rw-r--r--ext/psych/lib/psych/json/tree_builder.rb12
-rw-r--r--ext/psych/lib/psych/json/yaml_events.rb29
-rw-r--r--ext/psych/lib/psych/nodes.rb77
-rw-r--r--ext/psych/lib/psych/nodes/alias.rb18
-rw-r--r--ext/psych/lib/psych/nodes/document.rb60
-rw-r--r--ext/psych/lib/psych/nodes/mapping.rb56
-rw-r--r--ext/psych/lib/psych/nodes/node.rb53
-rw-r--r--ext/psych/lib/psych/nodes/scalar.rb67
-rw-r--r--ext/psych/lib/psych/nodes/sequence.rb81
-rw-r--r--ext/psych/lib/psych/nodes/stream.rb37
-rw-r--r--ext/psych/lib/psych/omap.rb4
-rw-r--r--ext/psych/lib/psych/parser.rb51
-rw-r--r--ext/psych/lib/psych/scalar_scanner.rb128
-rw-r--r--ext/psych/lib/psych/set.rb4
-rw-r--r--ext/psych/lib/psych/stream.rb36
-rw-r--r--ext/psych/lib/psych/streaming.rb22
-rw-r--r--ext/psych/lib/psych/syntax_error.rb19
-rw-r--r--ext/psych/lib/psych/tree_builder.rb96
-rw-r--r--ext/psych/lib/psych/visitors.rb6
-rw-r--r--ext/psych/lib/psych/visitors/depth_first.rb26
-rw-r--r--ext/psych/lib/psych/visitors/emitter.rb44
-rw-r--r--ext/psych/lib/psych/visitors/json_tree.rb21
-rw-r--r--ext/psych/lib/psych/visitors/to_ruby.rb317
-rw-r--r--ext/psych/lib/psych/visitors/visitor.rb19
-rw-r--r--ext/psych/lib/psych/visitors/yaml_tree.rb452
33 files changed, 2562 insertions, 0 deletions
diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb
new file mode 100644
index 0000000..1895be6
--- /dev/null
+++ b/ext/psych/lib/psych.rb
@@ -0,0 +1,331 @@
+require 'psych.so'
+require 'psych/nodes'
+require 'psych/streaming'
+require 'psych/visitors'
+require 'psych/handler'
+require 'psych/tree_builder'
+require 'psych/parser'
+require 'psych/omap'
+require 'psych/set'
+require 'psych/coder'
+require 'psych/core_ext'
+require 'psych/deprecated'
+require 'psych/stream'
+require 'psych/json/tree_builder'
+require 'psych/json/stream'
+require 'psych/handlers/document_stream'
+
+###
+# = Overview
+#
+# Psych is a YAML parser and emitter. Psych leverages
+# libyaml[http://libyaml.org] for it's YAML parsing and emitting capabilities.
+# In addition to wrapping libyaml, Psych also knows how to serialize and
+# de-serialize most Ruby objects to and from the YAML format.
+#
+# = I NEED TO PARSE OR EMIT YAML RIGHT NOW!
+#
+# # Parse some YAML
+# Psych.load("--- foo") # => "foo"
+#
+# # Emit some YAML
+# Psych.dump("foo") # => "--- foo\n...\n"
+# { :a => 'b'}.to_yaml # => "---\n:a: b\n"
+#
+# Got more time on your hands? Keep on reading!
+#
+# == YAML Parsing
+#
+# Psych provides a range of interfaces for parsing a YAML document ranging from
+# low level to high level, depending on your parsing needs. At the lowest
+# level, is an event based parser. Mid level is access to the raw YAML AST,
+# and at the highest level is the ability to unmarshal YAML to ruby objects.
+#
+# === Low level parsing
+#
+# The lowest level parser should be used when the YAML input is already known,
+# and the developer does not want to pay the price of building an AST or
+# automatic detection and conversion to ruby objects. See Psych::Parser for
+# more information on using the event based parser.
+#
+# === Mid level parsing
+#
+# Psych provides access to an AST produced from parsing a YAML document. This
+# tree is built using the Psych::Parser and Psych::TreeBuilder. The AST can
+# be examined and manipulated freely. Please see Psych::parse_stream,
+# Psych::Nodes, and Psych::Nodes::Node for more information on dealing with
+# YAML syntax trees.
+#
+# === High level parsing
+#
+# The high level YAML parser provided by Psych simply takes YAML as input and
+# returns a Ruby data structure. For information on using the high level parser
+# see Psych.load
+#
+# == YAML Emitting
+#
+# Psych provides a range of interfaces ranging from low to high level for
+# producing YAML documents. Very similar to the YAML parsing interfaces, Psych
+# provides at the lowest level, an event based system, mid-level is building
+# a YAML AST, and the highest level is converting a Ruby object straight to
+# a YAML document.
+#
+# === Low level emitting
+#
+# The lowest level emitter is an event based system. Events are sent to a
+# Psych::Emitter object. That object knows how to convert the events to a YAML
+# document. This interface should be used when document format is known in
+# advance or speed is a concern. See Psych::Emitter for more information.
+#
+# === Mid level emitting
+#
+# At the mid level is building an AST. This AST is exactly the same as the AST
+# used when parsing a YAML document. Users can build an AST by hand and the
+# AST knows how to emit itself as a YAML document. See Psych::Nodes,
+# Psych::Nodes::Node, and Psych::TreeBuilder for more information on building
+# a YAML AST.
+#
+# === High level emitting
+#
+# The high level emitter has the easiest interface. Psych simply takes a Ruby
+# data structure and converts it to a YAML document. See Psych.dump for more
+# information on dumping a Ruby data structure.
+
+module Psych
+ # The version is Psych you're using
+ VERSION = '1.3.2'
+
+ # The version of libyaml Psych is using
+ LIBYAML_VERSION = Psych.libyaml_version.join '.'
+
+ class Exception < RuntimeError
+ end
+
+ class BadAlias < Exception
+ end
+
+ ###
+ # Load +yaml+ in to a Ruby data structure. If multiple documents are
+ # provided, the object contained in the first document will be returned.
+ # +filename+ will be used in the exception message if any exception is raised
+ # while parsing.
+ #
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
+ #
+ # Example:
+ #
+ # Psych.load("--- a") # => 'a'
+ # Psych.load("---\n - a\n - b") # => ['a', 'b']
+ #
+ # begin
+ # Psych.load("--- `", "file.txt")
+ # rescue Psych::SyntaxError => ex
+ # ex.file # => 'file.txt'
+ # ex.message # => "(foo.txt): found character that cannot start any token"
+ # end
+ def self.load yaml, filename = nil
+ result = parse(yaml, filename)
+ result ? result.to_ruby : result
+ end
+
+ ###
+ # Parse a YAML string in +yaml+. Returns the first object of a YAML AST.
+ # +filename+ is used in the exception message if a Psych::SyntaxError is
+ # raised.
+ #
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
+ #
+ # Example:
+ #
+ # Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Sequence:0x00>
+ #
+ # begin
+ # Psych.parse("--- `", "file.txt")
+ # rescue Psych::SyntaxError => ex
+ # ex.file # => 'file.txt'
+ # ex.message # => "(foo.txt): found character that cannot start any token"
+ # end
+ #
+ # See Psych::Nodes for more information about YAML AST.
+ def self.parse yaml, filename = nil
+ parse_stream(yaml, filename) do |node|
+ return node
+ end
+ false
+ end
+
+ ###
+ # Parse a file at +filename+. Returns the YAML AST.
+ #
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
+ def self.parse_file filename
+ File.open filename, 'r:bom|utf-8' do |f|
+ parse f, filename
+ end
+ end
+
+ ###
+ # Returns a default parser
+ def self.parser
+ Psych::Parser.new(TreeBuilder.new)
+ end
+
+ ###
+ # Parse a YAML string in +yaml+. Returns the full AST for the YAML document.
+ # This method can handle multiple YAML documents contained in +yaml+.
+ # +filename+ is used in the exception message if a Psych::SyntaxError is
+ # raised.
+ #
+ # If a block is given, a Psych::Nodes::Document node will be yielded to the
+ # block as it's being parsed.
+ #
+ # Raises a Psych::SyntaxError when a YAML syntax error is detected.
+ #
+ # Example:
+ #
+ # Psych.parse_stream("---\n - a\n - b") # => #<Psych::Nodes::Stream:0x00>
+ #
+ # Psych.parse_stream("--- a\n--- b") do |node|
+ # node # => #<Psych::Nodes::Document:0x00>
+ # end
+ #
+ # begin
+ # Psych.parse_stream("--- `", "file.txt")
+ # rescue Psych::SyntaxError => ex
+ # ex.file # => 'file.txt'
+ # ex.message # => "(foo.txt): found character that cannot start any token"
+ # end
+ #
+ # See Psych::Nodes for more information about YAML AST.
+ def self.parse_stream yaml, filename = nil, &block
+ if block_given?
+ parser = Psych::Parser.new(Handlers::DocumentStream.new(&block))
+ parser.parse yaml, filename
+ else
+ parser = self.parser
+ parser.parse yaml, filename
+ parser.handler.root
+ end
+ end
+
+ ###
+ # call-seq:
+ # Psych.dump(o) -> string of yaml
+ # Psych.dump(o, options) -> string of yaml
+ # Psych.dump(o, io) -> io object passed in
+ # Psych.dump(o, io, options) -> io object passed in
+ #
+ # Dump Ruby object +o+ to a YAML string. Optional +options+ may be passed in
+ # to control the output format. If an IO object is passed in, the YAML will
+ # be dumped to that IO object.
+ #
+ # Example:
+ #
+ # # Dump an array, get back a YAML string
+ # Psych.dump(['a', 'b']) # => "---\n- a\n- b\n"
+ #
+ # # Dump an array to an IO object
+ # Psych.dump(['a', 'b'], StringIO.new) # => #<StringIO:0x000001009d0890>
+ #
+ # # Dump an array with indentation set
+ # Psych.dump(['a', ['b']], :indentation => 3) # => "---\n- a\n- - b\n"
+ #
+ # # Dump an array to an IO with indentation set
+ # Psych.dump(['a', ['b']], StringIO.new, :indentation => 3)
+ def self.dump o, io = nil, options = {}
+ if Hash === io
+ options = io
+ io = nil
+ end
+
+ visitor = Psych::Visitors::YAMLTree.new options
+ visitor << o
+ visitor.tree.yaml io, options
+ end
+
+ ###
+ # Dump a list of objects as separate documents to a document stream.
+ #
+ # Example:
+ #
+ # Psych.dump_stream("foo\n ", {}) # => "--- ! \"foo\\n \"\n--- {}\n"
+ def self.dump_stream *objects
+ visitor = Psych::Visitors::YAMLTree.new {}
+ objects.each do |o|
+ visitor << o
+ end
+ visitor.tree.yaml
+ end
+
+ ###
+ # Dump Ruby object +o+ to a JSON string.
+ def self.to_json o
+ visitor = Psych::Visitors::JSONTree.new
+ visitor << o
+ visitor.tree.yaml
+ end
+
+ ###
+ # Load multiple documents given in +yaml+. Returns the parsed documents
+ # as a list. If a block is given, each document will be converted to ruby
+ # and passed to the block during parsing
+ #
+ # Example:
+ #
+ # Psych.load_stream("--- foo\n...\n--- bar\n...") # => ['foo', 'bar']
+ #
+ # list = []
+ # Psych.load_stream("--- foo\n...\n--- bar\n...") do |ruby|
+ # list << ruby
+ # end
+ # list # => ['foo', 'bar']
+ #
+ def self.load_stream yaml, filename = nil
+ if block_given?
+ parse_stream(yaml, filename) do |node|
+ yield node.to_ruby
+ end
+ else
+ parse_stream(yaml, filename).children.map { |child| child.to_ruby }
+ end
+ end
+
+ ###
+ # Load the document contained in +filename+. Returns the yaml contained in
+ # +filename+ as a ruby object
+ def self.load_file filename
+ File.open(filename, 'r:bom|utf-8') { |f| self.load f, filename }
+ end
+
+ # :stopdoc:
+ @domain_types = {}
+ def self.add_domain_type domain, type_tag, &block
+ key = ['tag', domain, type_tag].join ':'
+ @domain_types[key] = [key, block]
+ @domain_types["tag:#{type_tag}"] = [key, block]
+ end
+
+ def self.add_builtin_type type_tag, &block
+ domain = 'yaml.org,2002'
+ key = ['tag', domain, type_tag].join ':'
+ @domain_types[key] = [key, block]
+ end
+
+ def self.remove_type type_tag
+ @domain_types.delete type_tag
+ end
+
+ @load_tags = {}
+ @dump_tags = {}
+ def self.add_tag tag, klass
+ @load_tags[tag] = klass
+ @dump_tags[klass] = tag
+ end
+
+ class << self
+ attr_accessor :load_tags
+ attr_accessor :dump_tags
+ attr_accessor :domain_types
+ end
+ # :startdoc:
+end
diff --git a/ext/psych/lib/psych/coder.rb b/ext/psych/lib/psych/coder.rb
new file mode 100644
index 0000000..2b830d2
--- /dev/null
+++ b/ext/psych/lib/psych/coder.rb
@@ -0,0 +1,94 @@
+module Psych
+ ###
+ # If an object defines +encode_with+, then an instance of Psych::Coder will
+ # be passed to the method when the object is being serialized. The Coder
+ # automatically assumes a Psych::Nodes::Mapping is being emitted. Other
+ # objects like Sequence and Scalar may be emitted if +seq=+ or +scalar=+ are
+ # called, respectively.
+ class Coder
+ attr_accessor :tag, :style, :implicit, :object
+ attr_reader :type, :seq
+
+ def initialize tag
+ @map = {}
+ @seq = []
+ @implicit = false
+ @type = :map
+ @tag = tag
+ @style = Psych::Nodes::Mapping::BLOCK
+ @scalar = nil
+ @object = nil
+ end
+
+ def scalar *args
+ if args.length > 0
+ warn "#{caller[0]}: Coder#scalar(a,b,c) is deprecated" if $VERBOSE
+ @tag, @scalar, _ = args
+ @type = :scalar
+ end
+ @scalar
+ end
+
+ # Emit a map. The coder will be yielded to the block.
+ def map tag = @tag, style = @style
+ @tag = tag
+ @style = style
+ yield self if block_given?
+ @map
+ end
+
+ # Emit a scalar with +value+ and +tag+
+ def represent_scalar tag, value
+ self.tag = tag
+ self.scalar = value
+ end
+
+ # Emit a sequence with +list+ and +tag+
+ def represent_seq tag, list
+ @tag = tag
+ self.seq = list
+ end
+
+ # Emit a sequence with +map+ and +tag+
+ def represent_map tag, map
+ @tag = tag
+ self.map = map
+ end
+
+ # Emit an arbitrary object +obj+ and +tag+
+ def represent_object tag, obj
+ @tag = tag
+ @type = :object
+ @object = obj
+ end
+
+ # Emit a scalar with +value+
+ def scalar= value
+ @type = :scalar
+ @scalar = value
+ end
+
+ # Emit a map with +value+
+ def map= map
+ @type = :map
+ @map = map
+ end
+
+ def []= k, v
+ @type = :map
+ @map[k] = v
+ end
+ alias :add :[]=
+
+ def [] k
+ @type = :map
+ @map[k]
+ end
+
+ # Emit a sequence of +list+
+ def seq= list
+ @type = :seq
+ @seq = list
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/core_ext.rb b/ext/psych/lib/psych/core_ext.rb
new file mode 100644
index 0000000..4a04c2d
--- /dev/null
+++ b/ext/psych/lib/psych/core_ext.rb
@@ -0,0 +1,42 @@
+class Object
+ def self.yaml_tag url
+ Psych.add_tag(url, self)
+ end
+
+ # FIXME: rename this to "to_yaml" when syck is removed
+
+ ###
+ # call-seq: to_yaml(options = {})
+ #
+ # Convert an object to YAML. See Psych.dump for more information on the
+ # available +options+.
+ def psych_to_yaml options = {}
+ Psych.dump self, options
+ end
+ remove_method :to_yaml rescue nil
+ alias :to_yaml :psych_to_yaml
+end
+
+class Module
+ def psych_yaml_as url
+ return if caller[0].end_with?('rubytypes.rb')
+ if $VERBOSE
+ warn "#{caller[0]}: yaml_as is deprecated, please use yaml_tag"
+ end
+ Psych.add_tag(url, self)
+ end
+
+ remove_method :yaml_as rescue nil
+ alias :yaml_as :psych_yaml_as
+end
+
+if defined?(::IRB)
+module Kernel
+ def psych_y *objects
+ puts Psych.dump_stream(*objects)
+ end
+ remove_method :y rescue nil
+ alias y psych_y
+ private :y
+end
+end
diff --git a/ext/psych/lib/psych/deprecated.rb b/ext/psych/lib/psych/deprecated.rb
new file mode 100644
index 0000000..333c3a1
--- /dev/null
+++ b/ext/psych/lib/psych/deprecated.rb
@@ -0,0 +1,83 @@
+require 'date'
+
+module Psych
+ DEPRECATED = __FILE__ # :nodoc:
+
+ module DeprecatedMethods # :nodoc:
+ attr_accessor :taguri
+ attr_accessor :to_yaml_style
+ end
+
+ def self.quick_emit thing, opts = {}, &block # :nodoc:
+ warn "#{caller[0]}: YAML.quick_emit is deprecated" if $VERBOSE && !caller[0].start_with?(File.dirname(__FILE__))
+ target = eval 'self', block.binding
+ target.extend DeprecatedMethods
+ metaclass = class << target; self; end
+ metaclass.send(:define_method, :encode_with) do |coder|
+ target.taguri = coder.tag
+ target.to_yaml_style = coder.style
+ block.call coder
+ end
+ target.psych_to_yaml unless opts[:nodump]
+ end
+
+ def self.load_documents yaml, &block
+ if $VERBOSE
+ warn "#{caller[0]}: load_documents is deprecated, use load_stream"
+ end
+ list = load_stream yaml
+ return list unless block_given?
+ list.each(&block)
+ end
+
+ def self.detect_implicit thing
+ warn "#{caller[0]}: detect_implicit is deprecated" if $VERBOSE
+ return '' unless String === thing
+ return 'null' if '' == thing
+ ScalarScanner.new.tokenize(thing).class.name.downcase
+ end
+
+ def self.add_ruby_type type_tag, &block
+ warn "#{caller[0]}: add_ruby_type is deprecated, use add_domain_type" if $VERBOSE
+ domain = 'ruby.yaml.org,2002'
+ key = ['tag', domain, type_tag].join ':'
+ @domain_types[key] = [key, block]
+ end
+
+ def self.add_private_type type_tag, &block
+ warn "#{caller[0]}: add_private_type is deprecated, use add_domain_type" if $VERBOSE
+ domain = 'x-private'
+ key = [domain, type_tag].join ':'
+ @domain_types[key] = [key, block]
+ end
+
+ def self.tagurize thing
+ warn "#{caller[0]}: add_private_type is deprecated, use add_domain_type" if $VERBOSE
+ return thing unless String === thing
+ "tag:yaml.org,2002:#{thing}"
+ end
+
+ def self.read_type_class type, reference
+ warn "#{caller[0]}: read_type_class is deprecated" if $VERBOSE
+ _, _, type, name = type.split ':', 4
+
+ reference = name.split('::').inject(reference) do |k,n|
+ k.const_get(n.to_sym)
+ end if name
+ [type, reference]
+ end
+
+ def self.object_maker klass, hash
+ warn "#{caller[0]}: object_maker is deprecated" if $VERBOSE
+ klass.allocate.tap do |obj|
+ hash.each { |k,v| obj.instance_variable_set(:"@#{k}", v) }
+ end
+ end
+end
+
+class Object
+ undef :to_yaml_properties rescue nil
+ def to_yaml_properties # :nodoc:
+ instance_variables
+ end
+end
diff --git a/ext/psych/lib/psych/handler.rb b/ext/psych/lib/psych/handler.rb
new file mode 100644
index 0000000..a2aa6bb
--- /dev/null
+++ b/ext/psych/lib/psych/handler.rb
@@ -0,0 +1,221 @@
+module Psych
+ ###
+ # Psych::Handler is an abstract base class that defines the events used
+ # when dealing with Psych::Parser. Clients who want to use Psych::Parser
+ # should implement a class that inherits from Psych::Handler and define
+ # events that they can handle.
+ #
+ # Psych::Handler defines all events that Psych::Parser can possibly send to
+ # event handlers.
+ #
+ # See Psych::Parser for more details
+ class Handler
+ ###
+ # Called with +encoding+ when the YAML stream starts. This method is
+ # called once per stream. A stream may contain multiple documents.
+ #
+ # See the constants in Psych::Parser for the possible values of +encoding+.
+ def start_stream encoding
+ end
+
+ ###
+ # Called when the document starts with the declared +version+,
+ # +tag_directives+, if the document is +implicit+.
+ #
+ # +version+ will be an array of integers indicating the YAML version being
+ # dealt with, +tag_directives+ is a list of tuples indicating the prefix
+ # and suffix of each tag, and +implicit+ is a boolean indicating whether
+ # the document is started implicitly.
+ #
+ # === Example
+ #
+ # Given the following YAML:
+ #
+ # %YAML 1.1
+ # %TAG ! tag:tenderlovemaking.com,2009:
+ # --- !squee
+ #
+ # The parameters for start_document must be this:
+ #
+ # version # => [1, 1]
+ # tag_directives # => [["!", "tag:tenderlovemaking.com,2009:"]]
+ # implicit # => false
+ def start_document version, tag_directives, implicit
+ end
+
+ ###
+ # Called with the document ends. +implicit+ is a boolean value indicating
+ # whether or not the document has an implicit ending.
+ #
+ # === Example
+ #
+ # Given the following YAML:
+ #
+ # ---
+ # hello world
+ #
+ # +implicit+ will be true. Given this YAML:
+ #
+ # ---
+ # hello world
+ # ...
+ #
+ # +implicit+ will be false.
+ def end_document implicit
+ end
+
+ ###
+ # Called when an alias is found to +anchor+. +anchor+ will be the name
+ # of the anchor found.
+ #
+ # === Example
+ #
+ # Here we have an example of an array that references itself in YAML:
+ #
+ # --- &ponies
+ # - first element
+ # - *ponies
+ #
+ # &ponies is the achor, *ponies is the alias. In this case, alias is
+ # called with "ponies".
+ def alias anchor
+ end
+
+ ###
+ # Called when a scalar +value+ is found. The scalar may have an
+ # +anchor+, a +tag+, be implicitly +plain+ or implicitly +quoted+
+ #
+ # +value+ is the string value of the scalar
+ # +anchor+ is an associated anchor or nil
+ # +tag+ is an associated tag or nil
+ # +plain+ is a boolean value
+ # +quoted+ is a boolean value
+ # +style+ is an integer idicating the string style
+ #
+ # See the constants in Psych::Nodes::Scalar for the possible values of
+ # +style+
+ #
+ # === Example
+ #
+ # Here is a YAML document that exercises most of the possible ways this
+ # method can be called:
+ #
+ # ---
+ # - !str "foo"
+ # - &anchor fun
+ # - many
+ # lines
+ # - |
+ # many
+ # newlines
+ #
+ # The above YAML document contains a list with four strings. Here are
+ # the parameters sent to this method in the same order:
+ #
+ # # value anchor tag plain quoted style
+ # ["foo", nil, "!str", false, false, 3 ]
+ # ["fun", "anchor", nil, true, false, 1 ]
+ # ["many lines", nil, nil, true, false, 1 ]
+ # ["many\nnewlines\n", nil, nil, false, true, 4 ]
+ #
+ def scalar value, anchor, tag, plain, quoted, style
+ end
+
+ ###
+ # Called when a sequence is started.
+ #
+ # +anchor+ is the anchor associated with the sequence or nil.
+ # +tag+ is the tag associated with the sequence or nil.
+ # +implicit+ a boolean indicating whether or not the sequence was implicitly
+ # started.
+ # +style+ is an integer indicating the list style.
+ #
+ # See the constants in Psych::Nodes::Sequence for the possible values of
+ # +style+.
+ #
+ # === Example
+ #
+ # Here is a YAML document that exercises most of the possible ways this
+ # method can be called:
+ #
+ # ---
+ # - !!seq [
+ # a
+ # ]
+ # - &pewpew
+ # - b
+ #
+ # The above YAML document consists of three lists, an outer list that
+ # contains two inner lists. Here is a matrix of the parameters sent
+ # to represent these lists:
+ #
+ # # anchor tag implicit style
+ # [nil, nil, true, 1 ]
+ # [nil, "tag:yaml.org,2002:seq", false, 2 ]
+ # ["pewpew", nil, true, 1 ]
+
+ def start_sequence anchor, tag, implicit, style
+ end
+
+ ###
+ # Called when a sequence ends.
+ def end_sequence
+ end
+
+ ###
+ # Called when a map starts.
+ #
+ # +anchor+ is the anchor associated with the map or +nil+.
+ # +tag+ is the tag associated with the map or +nil+.
+ # +implicit+ is a boolean indicating whether or not the map was implicitly
+ # started.
+ # +style+ is an integer indicating the mapping style.
+ #
+ # See the constants in Psych::Nodes::Mapping for the possible values of
+ # +style+.
+ #
+ # === Example
+ #
+ # Here is a YAML document that exercises most of the possible ways this
+ # method can be called:
+ #
+ # ---
+ # k: !!map { hello: world }
+ # v: &pewpew
+ # hello: world
+ #
+ # The above YAML document consists of three maps, an outer map that contains
+ # two inner maps. Below is a matrix of the parameters sent in order to
+ # represent these three maps:
+ #
+ # # anchor tag implicit style
+ # [nil, nil, true, 1 ]
+ # [nil, "tag:yaml.org,2002:map", false, 2 ]
+ # ["pewpew", nil, true, 1 ]
+
+ def start_mapping anchor, tag, implicit, style
+ end
+
+ ###
+ # Called when a map ends
+ def end_mapping
+ end
+
+ ###
+ # Called when an empty event happens. (Which, as far as I can tell, is
+ # never).
+ def empty
+ end
+
+ ###
+ # Called when the YAML stream ends
+ def end_stream
+ end
+
+ ###
+ # Is this handler a streaming handler?
+ def streaming?
+ false
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/handlers/document_stream.rb b/ext/psych/lib/psych/handlers/document_stream.rb
new file mode 100644
index 0000000..e429993
--- /dev/null
+++ b/ext/psych/lib/psych/handlers/document_stream.rb
@@ -0,0 +1,22 @@
+require 'psych/tree_builder'
+
+module Psych
+ module Handlers
+ class DocumentStream < Psych::TreeBuilder # :nodoc:
+ def initialize &block
+ super
+ @block = block
+ end
+
+ def start_document version, tag_directives, implicit
+ n = Nodes::Document.new version, tag_directives, implicit
+ push n
+ end
+
+ def end_document implicit_end = !streaming?
+ @last.implicit_end = implicit_end
+ @block.call pop
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/json/ruby_events.rb b/ext/psych/lib/psych/json/ruby_events.rb
new file mode 100644
index 0000000..6b73249
--- /dev/null
+++ b/ext/psych/lib/psych/json/ruby_events.rb
@@ -0,0 +1,19 @@
+module Psych
+ module JSON
+ module RubyEvents # :nodoc:
+ def visit_Time o
+ formatted = format_time o
+ @emitter.scalar formatted, nil, nil, false, true, Nodes::Scalar::DOUBLE_QUOTED
+ end
+
+ def visit_DateTime o
+ visit_Time o.to_time
+ end
+
+ def visit_String o
+ @emitter.scalar o.to_s, nil, nil, false, true, Nodes::Scalar::DOUBLE_QUOTED
+ end
+ alias :visit_Symbol :visit_String
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/json/stream.rb b/ext/psych/lib/psych/json/stream.rb
new file mode 100644
index 0000000..be1a0a8
--- /dev/null
+++ b/ext/psych/lib/psych/json/stream.rb
@@ -0,0 +1,15 @@
+require 'psych/json/ruby_events'
+require 'psych/json/yaml_events'
+
+module Psych
+ module JSON
+ class Stream < Psych::Visitors::JSONTree
+ include Psych::JSON::RubyEvents
+ include Psych::Streaming
+
+ class Emitter < Psych::Stream::Emitter # :nodoc:
+ include Psych::JSON::YAMLEvents
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/json/tree_builder.rb b/ext/psych/lib/psych/json/tree_builder.rb
new file mode 100644
index 0000000..b799c93
--- /dev/null
+++ b/ext/psych/lib/psych/json/tree_builder.rb
@@ -0,0 +1,12 @@
+require 'psych/json/yaml_events'
+
+module Psych
+ module JSON
+ ###
+ # Psych::JSON::TreeBuilder is an event based AST builder. Events are sent
+ # to an instance of Psych::JSON::TreeBuilder and a JSON AST is constructed.
+ class TreeBuilder < Psych::TreeBuilder
+ include Psych::JSON::YAMLEvents
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/json/yaml_events.rb b/ext/psych/lib/psych/json/yaml_events.rb
new file mode 100644
index 0000000..01d4660
--- /dev/null
+++ b/ext/psych/lib/psych/json/yaml_events.rb
@@ -0,0 +1,29 @@
+module Psych
+ module JSON
+ module YAMLEvents # :nodoc:
+ def start_document version, tag_directives, implicit
+ super(version, tag_directives, !streaming?)
+ end
+
+ def end_document implicit_end = !streaming?
+ super(implicit_end)
+ end
+
+ def start_mapping anchor, tag, implicit, style
+ super(anchor, nil, implicit, Nodes::Mapping::FLOW)
+ end
+
+ def start_sequence anchor, tag, implicit, style
+ super(anchor, nil, implicit, Nodes::Sequence::FLOW)
+ end
+
+ def scalar value, anchor, tag, plain, quoted, style
+ if "tag:yaml.org,2002:null" == tag
+ super('null', nil, nil, true, false, Nodes::Scalar::PLAIN)
+ else
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/nodes.rb b/ext/psych/lib/psych/nodes.rb
new file mode 100644
index 0000000..f3b33fe
--- /dev/null
+++ b/ext/psych/lib/psych/nodes.rb
@@ -0,0 +1,77 @@
+require 'psych/nodes/node'
+require 'psych/nodes/stream'
+require 'psych/nodes/document'
+require 'psych/nodes/sequence'
+require 'psych/nodes/scalar'
+require 'psych/nodes/mapping'
+require 'psych/nodes/alias'
+
+module Psych
+ ###
+ # = Overview
+ #
+ # When using Psych.load to deserialize a YAML document, the document is
+ # translated to an intermediary AST. That intermediary AST is then
+ # translated in to a Ruby object graph.
+ #
+ # In the opposite direction, when using Psych.dump, the Ruby object graph is
+ # translated to an intermediary AST which is then converted to a YAML
+ # document.
+ #
+ # Psych::Nodes contains all of the classes that make up the nodes of a YAML
+ # AST. You can manually build an AST and use one of the visitors (see
+ # Psych::Visitors) to convert that AST to either a YAML document or to a
+ # Ruby object graph.
+ #
+ # Here is an example of building an AST that represents a list with one
+ # scalar:
+ #
+ # # Create our nodes
+ # stream = Psych::Nodes::Stream.new
+ # doc = Psych::Nodes::Document.new
+ # seq = Psych::Nodes::Sequence.new
+ # scalar = Psych::Nodes::Scalar.new('foo')
+ #
+ # # Build up our tree
+ # stream.children << doc
+ # doc.children << seq
+ # seq.children << scalar
+ #
+ # The stream is the root of the tree. We can then convert the tree to YAML:
+ #
+ # stream.to_yaml => "---\n- foo\n"
+ #
+ # Or convert it to Ruby:
+ #
+ # stream.to_ruby => [["foo"]]
+ #
+ # == YAML AST Requirements
+ #
+ # A valid YAML AST *must* have one Psych::Nodes::Stream at the root. A
+ # Psych::Nodes::Stream node must have 1 or more Psych::Nodes::Document nodes
+ # as children.
+ #
+ # Psych::Nodes::Document nodes must have one and *only* one child. That child
+ # may be one of:
+ #
+ # * Psych::Nodes::Sequence
+ # * Psych::Nodes::Mapping
+ # * Psych::Nodes::Scalar
+ #
+ # Psych::Nodes::Sequence and Psych::Nodes::Mapping nodes may have many
+ # children, but Psych::Nodes::Mapping nodes should have an even number of
+ # children.
+ #
+ # All of these are valid children for Psych::Nodes::Sequence and
+ # Psych::Nodes::Mapping nodes:
+ #
+ # * Psych::Nodes::Sequence
+ # * Psych::Nodes::Mapping
+ # * Psych::Nodes::Scalar
+ # * Psych::Nodes::Alias
+ #
+ # Psych::Nodes::Scalar and Psych::Nodes::Alias are both terminal nodes and
+ # should not have any children.
+ module Nodes
+ end
+end
diff --git a/ext/psych/lib/psych/nodes/alias.rb b/ext/psych/lib/psych/nodes/alias.rb
new file mode 100644
index 0000000..5bd4df1
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/alias.rb
@@ -0,0 +1,18 @@
+module Psych
+ module Nodes
+ ###
+ # This class represents a {YAML Alias}[http://yaml.org/spec/1.1/#alias].
+ # It points to an +anchor+.
+ #
+ # A Psych::Nodes::Alias is a terminal node and may have no children.
+ class Alias < Psych::Nodes::Node
+ # The anchor this alias links to
+ attr_accessor :anchor
+
+ # Create a new Alias that points to an +anchor+
+ def initialize anchor
+ @anchor = anchor
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/nodes/document.rb b/ext/psych/lib/psych/nodes/document.rb
new file mode 100644
index 0000000..32014d6
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/document.rb
@@ -0,0 +1,60 @@
+module Psych
+ module Nodes
+ ###
+ # This represents a YAML Document. This node must be a child of
+ # Psych::Nodes::Stream. A Psych::Nodes::Document must have one child,
+ # and that child may be one of the following:
+ #
+ # * Psych::Nodes::Sequence
+ # * Psych::Nodes::Mapping
+ # * Psych::Nodes::Scalar
+ class Document < Psych::Nodes::Node
+ # The version of the YAML document
+ attr_accessor :version
+
+ # A list of tag directives for this document
+ attr_accessor :tag_directives
+
+ # Was this document implicitly created?
+ attr_accessor :implicit
+
+ # Is the end of the document implicit?
+ attr_accessor :implicit_end
+
+ ###
+ # Create a new Psych::Nodes::Document object.
+ #
+ # +version+ is a list indicating the YAML version.
+ # +tags_directives+ is a list of tag directive declarations
+ # +implicit+ is a flag indicating whether the document will be implicitly
+ # started.
+ #
+ # == Example:
+ # This creates a YAML document object that represents a YAML 1.1 document
+ # with one tag directive, and has an implicit start:
+ #
+ # Psych::Nodes::Document.new(
+ # [1,1],
+ # [["!", "tag:tenderlovemaking.com,2009:"]],
+ # true
+ # )
+ #
+ # == See Also
+ # See also Psych::Handler#start_document
+ def initialize version = [], tag_directives = [], implicit = false
+ super()
+ @version = version
+ @tag_directives = tag_directives
+ @implicit = implicit
+ @implicit_end = true
+ end
+
+ ###
+ # Returns the root node. A Document may only have one root node:
+ # http://yaml.org/spec/1.1/#id898031
+ def root
+ children.first
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/nodes/mapping.rb b/ext/psych/lib/psych/nodes/mapping.rb
new file mode 100644
index 0000000..5ba95ce
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/mapping.rb
@@ -0,0 +1,56 @@
+module Psych
+ module Nodes
+ ###
+ # This class represents a {YAML Mapping}[http://yaml.org/spec/1.1/#mapping].
+ #
+ # A Psych::Nodes::Mapping node may have 0 or more children, but must have
+ # an even number of children. Here are the valid children a
+ # Psych::Nodes::Mapping node may have:
+ #
+ # * Psych::Nodes::Sequence
+ # * Psych::Nodes::Mapping
+ # * Psych::Nodes::Scalar
+ # * Psych::Nodes::Alias
+ class Mapping < Psych::Nodes::Node
+ # Any Map Style
+ ANY = 0
+
+ # Block Map Style
+ BLOCK = 1
+
+ # Flow Map Style
+ FLOW = 2
+
+ # The optional anchor for this mapping
+ attr_accessor :anchor
+
+ # The optional tag for this mapping
+ attr_accessor :tag
+
+ # Is this an implicit mapping?
+ attr_accessor :implicit
+
+ # The style of this mapping
+ attr_accessor :style
+
+ ###
+ # Create a new Psych::Nodes::Mapping object.
+ #
+ # +anchor+ is the anchor associated with the map or +nil+.
+ # +tag+ is the tag associated with the map or +nil+.
+ # +implicit+ is a boolean indicating whether or not the map was implicitly
+ # started.
+ # +style+ is an integer indicating the mapping style.
+ #
+ # == See Also
+ # See also Psych::Handler#start_mapping
+ def initialize anchor = nil, tag = nil, implicit = true, style = BLOCK
+ super()
+ @anchor = anchor
+ @tag = tag
+ @implicit = implicit
+ @style = style
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/nodes/node.rb b/ext/psych/lib/psych/nodes/node.rb
new file mode 100644
index 0000000..0cefe44
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/node.rb
@@ -0,0 +1,53 @@
+require 'stringio'
+
+module Psych
+ module Nodes
+ ###
+ # The base class for any Node in a YAML parse tree. This class should
+ # never be instantiated.
+ class Node
+ include Enumerable
+
+ # The children of this node
+ attr_reader :children
+
+ # An associated tag
+ attr_reader :tag
+
+ # Create a new Psych::Nodes::Node
+ def initialize
+ @children = []
+ end
+
+ ###
+ # Iterate over each node in the tree. Yields each node to +block+ depth
+ # first.
+ def each &block
+ return enum_for :each unless block_given?
+ Visitors::DepthFirst.new(block).accept self
+ end
+
+ ###
+ # Convert this node to Ruby.
+ #
+ # See also Psych::Visitors::ToRuby
+ def to_ruby
+ Visitors::ToRuby.new.accept self
+ end
+ alias :transform :to_ruby
+
+ ###
+ # Convert this node to YAML.
+ #
+ # See also Psych::Visitors::Emitter
+ def yaml io = nil, options = {}
+ real_io = io || StringIO.new(''.encode('utf-8'))
+
+ Visitors::Emitter.new(real_io, options).accept self
+ return real_io.string unless io
+ io
+ end
+ alias :to_yaml :yaml
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/nodes/scalar.rb b/ext/psych/lib/psych/nodes/scalar.rb
new file mode 100644
index 0000000..1b1b25b
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/scalar.rb
@@ -0,0 +1,67 @@
+module Psych
+ module Nodes
+ ###
+ # This class represents a {YAML Scalar}[http://yaml.org/spec/1.1/#id858081].
+ #
+ # This node type is a terminal node and should not have any children.
+ class Scalar < Psych::Nodes::Node
+ # Any style scalar, the emitter chooses
+ ANY = 0
+
+ # Plain scalar style
+ PLAIN = 1
+
+ # Single quoted style
+ SINGLE_QUOTED = 2
+
+ # Double quoted style
+ DOUBLE_QUOTED = 3
+
+ # Literal style
+ LITERAL = 4
+
+ # Folded style
+ FOLDED = 5
+
+ # The scalar value
+ attr_accessor :value
+
+ # The anchor value (if there is one)
+ attr_accessor :anchor
+
+ # The tag value (if there is one)
+ attr_accessor :tag
+
+ # Is this a plain scalar?
+ attr_accessor :plain
+
+ # Is this scalar quoted?
+ attr_accessor :quoted
+
+ # The style of this scalar
+ attr_accessor :style
+
+ ###
+ # Create a new Psych::Nodes::Scalar object.
+ #
+ # +value+ is the string value of the scalar
+ # +anchor+ is an associated anchor or nil
+ # +tag+ is an associated tag or nil
+ # +plain+ is a boolean value
+ # +quoted+ is a boolean value
+ # +style+ is an integer idicating the string style
+ #
+ # == See Also
+ #
+ # See also Psych::Handler#scalar
+ def initialize value, anchor = nil, tag = nil, plain = true, quoted = false, style = ANY
+ @value = value
+ @anchor = anchor
+ @tag = tag
+ @plain = plain
+ @quoted = quoted
+ @style = style
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/nodes/sequence.rb b/ext/psych/lib/psych/nodes/sequence.rb
new file mode 100644
index 0000000..e4b833d
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/sequence.rb
@@ -0,0 +1,81 @@
+module Psych
+ module Nodes
+ ###
+ # This class represents a
+ # {YAML sequence}[http://yaml.org/spec/1.1/#sequence/syntax].
+ #
+ # A YAML sequence is basically a list, and looks like this:
+ #
+ # %YAML 1.1
+ # ---
+ # - I am
+ # - a Sequence
+ #
+ # A YAML sequence may have an anchor like this:
+ #
+ # %YAML 1.1
+ # ---
+ # &A [
+ # "This sequence",
+ # "has an anchor"
+ # ]
+ #
+ # A YAML sequence may also have a tag like this:
+ #
+ # %YAML 1.1
+ # ---
+ # !!seq [
+ # "This sequence",
+ # "has a tag"
+ # ]
+ #
+ # This class represents a sequence in a YAML document. A
+ # Psych::Nodes::Sequence node may have 0 or more children. Valid children
+ # for this node are:
+ #
+ # * Psych::Nodes::Sequence
+ # * Psych::Nodes::Mapping
+ # * Psych::Nodes::Scalar
+ # * Psych::Nodes::Alias
+ class Sequence < Psych::Nodes::Node
+ # Any Styles, emitter chooses
+ ANY = 0
+
+ # Block style sequence
+ BLOCK = 1
+
+ # Flow style sequence
+ FLOW = 2
+
+ # The anchor for this sequence (if any)
+ attr_accessor :anchor
+
+ # The tag name for this sequence (if any)
+ attr_accessor :tag
+
+ # Is this sequence started implicitly?
+ attr_accessor :implicit
+
+ # The sequece style used
+ attr_accessor :style
+
+ ###
+ # Create a new object representing a YAML sequence.
+ #
+ # +anchor+ is the anchor associated with the sequence or nil.
+ # +tag+ is the tag associated with the sequence or nil.
+ # +implicit+ a boolean indicating whether or not the sequence was
+ # implicitly started.
+ # +style+ is an integer indicating the list style.
+ #
+ # See Psych::Handler#start_sequence
+ def initialize anchor = nil, tag = nil, implicit = true, style = BLOCK
+ super()
+ @anchor = anchor
+ @tag = tag
+ @implicit = implicit
+ @style = style
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/nodes/stream.rb b/ext/psych/lib/psych/nodes/stream.rb
new file mode 100644
index 0000000..7cf5e03
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/stream.rb
@@ -0,0 +1,37 @@
+module Psych
+ module Nodes
+ ###
+ # Represents a YAML stream. This is the root node for any YAML parse
+ # tree. This node must have one or more child nodes. The only valid
+ # child node for a Psych::Nodes::Stream node is Psych::Nodes::Document.
+ class Stream < Psych::Nodes::Node
+
+ # Encodings supported by Psych (and libyaml)
+
+ # Any encoding
+ ANY = Psych::Parser::ANY
+
+ # UTF-8 encoding
+ UTF8 = Psych::Parser::UTF8
+
+ # UTF-16LE encoding
+ UTF16LE = Psych::Parser::UTF16LE
+
+ # UTF-16BE encoding
+ UTF16BE = Psych::Parser::UTF16BE
+
+ # The encoding used for this stream
+ attr_accessor :encoding
+
+ ###
+ # Create a new Psych::Nodes::Stream node with an +encoding+ that
+ # defaults to Psych::Nodes::Stream::UTF8.
+ #
+ # See also Psych::Handler#start_stream
+ def initialize encoding = UTF8
+ super()
+ @encoding = encoding
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/omap.rb b/ext/psych/lib/psych/omap.rb
new file mode 100644
index 0000000..6286270
--- /dev/null
+++ b/ext/psych/lib/psych/omap.rb
@@ -0,0 +1,4 @@
+module Psych
+ class Omap < ::Hash
+ end
+end
diff --git a/ext/psych/lib/psych/parser.rb b/ext/psych/lib/psych/parser.rb
new file mode 100644
index 0000000..84085f1
--- /dev/null
+++ b/ext/psych/lib/psych/parser.rb
@@ -0,0 +1,51 @@
+module Psych
+ ###
+ # YAML event parser class. This class parses a YAML document and calls
+ # events on the handler that is passed to the constructor. The events can
+ # be used for things such as constructing a YAML AST or deserializing YAML
+ # documents. It can even be fed back to Psych::Emitter to emit the same
+ # document that was parsed.
+ #
+ # See Psych::Handler for documentation on the events that Psych::Parser emits.
+ #
+ # Here is an example that prints out ever scalar found in a YAML document:
+ #
+ # # Handler for detecting scalar values
+ # class ScalarHandler < Psych::Handler
+ # def scalar value, anchor, tag, plain, quoted, style
+ # puts value
+ # end
+ # end
+ #
+ # parser = Psych::Parser.new(ScalarHandler.new)
+ # parser.parse(yaml_document)
+ #
+ # Here is an example that feeds the parser back in to Psych::Emitter. The
+ # YAML document is read from STDIN and written back out to STDERR:
+ #
+ # parser = Psych::Parser.new(Psych::Emitter.new($stderr))
+ # parser.parse($stdin)
+ #
+ # Psych uses Psych::Parser in combination with Psych::TreeBuilder to
+ # construct an AST of the parsed YAML document.
+
+ class Parser
+ class Mark < Struct.new(:index, :line, :column)
+ end
+
+ # The handler on which events will be called
+ attr_accessor :handler
+
+ # Set the encoding for this parser to +encoding+
+ attr_writer :external_encoding
+
+ ###
+ # Creates a new Psych::Parser instance with +handler+. YAML events will
+ # be called on +handler+. See Psych::Parser for more details.
+
+ def initialize handler = Handler.new
+ @handler = handler
+ @external_encoding = ANY
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/scalar_scanner.rb b/ext/psych/lib/psych/scalar_scanner.rb
new file mode 100644
index 0000000..fa2d385
--- /dev/null
+++ b/ext/psych/lib/psych/scalar_scanner.rb
@@ -0,0 +1,128 @@
+require 'strscan'
+
+module Psych
+ ###
+ # Scan scalars for built in types
+ class ScalarScanner
+ # Taken from http://yaml.org/type/timestamp.html
+ TIME = /^\d{4}-\d{1,2}-\d{1,2}([Tt]|\s+)\d{1,2}:\d\d:\d\d(\.\d*)?(\s*Z|[-+]\d{1,2}(:\d\d)?)?/
+
+ # Taken from http://yaml.org/type/float.html
+ FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9.]*([eE][-+][0-9]+)?(?# base 10)
+ |[-+]?[0-9][0-9_,]*(:[0-5]?[0-9])+\.[0-9_]*(?# base 60)
+ |[-+]?\.(inf|Inf|INF)(?# infinity)
+ |\.(nan|NaN|NAN)(?# not a number))$/x
+
+ # Create a new scanner
+ def initialize
+ @string_cache = {}
+ end
+
+ # Tokenize +string+ returning the ruby object
+ def tokenize string
+ return nil if string.empty?
+ return string if @string_cache.key?(string)
+
+ case string
+ when /^[A-Za-z~]/
+ if string.length > 5
+ @string_cache[string] = true
+ return string
+ end
+
+ case string
+ when /^[^ytonf~]/i
+ @string_cache[string] = true
+ string
+ when '~', /^null$/i
+ nil
+ when /^(yes|true|on)$/i
+ true
+ when /^(no|false|off)$/i
+ false
+ else
+ @string_cache[string] = true
+ string
+ end
+ when TIME
+ parse_time string
+ when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/
+ require 'date'
+ begin
+ Date.strptime(string, '%Y-%m-%d')
+ rescue ArgumentError
+ string
+ end
+ when /^\.inf$/i
+ 1 / 0.0
+ when /^-\.inf$/i
+ -1 / 0.0
+ when /^\.nan$/i
+ 0.0 / 0.0
+ when /^:./
+ if string =~ /^:(["'])(.*)\1/
+ $2.sub(/^:/, '').to_sym
+ else
+ string.sub(/^:/, '').to_sym
+ end
+ when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/
+ i = 0
+ string.split(':').each_with_index do |n,e|
+ i += (n.to_i * 60 ** (e - 2).abs)
+ end
+ i
+ when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]*$/
+ i = 0
+ string.split(':').each_with_index do |n,e|
+ i += (n.to_f * 60 ** (e - 2).abs)
+ end
+ i
+ when FLOAT
+ begin
+ return Float(string.gsub(/[,_]/, ''))
+ rescue ArgumentError
+ end
+
+ @string_cache[string] = true
+ string
+ else
+ if string.count('.') < 2
+ begin
+ return Integer(string.gsub(/[,_]/, ''))
+ rescue ArgumentError
+ end
+ end
+
+ @string_cache[string] = true
+ string
+ end
+ end
+
+ ###
+ # Parse and return a Time from +string+
+ def parse_time string
+ date, time = *(string.split(/[ tT]/, 2))
+ (yy, m, dd) = date.split('-').map { |x| x.to_i }
+ md = time.match(/(\d+:\d+:\d+)(?:\.(\d*))?\s*(Z|[-+]\d+(:\d\d)?)?/)
+
+ (hh, mm, ss) = md[1].split(':').map { |x| x.to_i }
+ us = (md[2] ? Rational("0.#{md[2]}") : 0) * 1000000
+
+ time = Time.utc(yy, m, dd, hh, mm, ss, us)
+
+ return time if 'Z' == md[3]
+ return Time.at(time.to_i, us) unless md[3]
+
+ tz = md[3].match(/^([+\-]?\d{1,2})\:?(\d{1,2})?$/)[1..-1].compact.map { |digit| Integer(digit, 10) }
+ offset = tz.first * 3600
+
+ if offset < 0
+ offset -= ((tz[1] || 0) * 60)
+ else
+ offset += ((tz[1] || 0) * 60)
+ end
+
+ Time.at((time - offset).to_i, us)
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/set.rb b/ext/psych/lib/psych/set.rb
new file mode 100644
index 0000000..6793a8e
--- /dev/null
+++ b/ext/psych/lib/psych/set.rb
@@ -0,0 +1,4 @@
+module Psych
+ class Set < ::Hash
+ end
+end
diff --git a/ext/psych/lib/psych/stream.rb b/ext/psych/lib/psych/stream.rb
new file mode 100644
index 0000000..567c1bb
--- /dev/null
+++ b/ext/psych/lib/psych/stream.rb
@@ -0,0 +1,36 @@
+module Psych
+ ###
+ # Psych::Stream is a streaming YAML emitter. It will not buffer your YAML,
+ # but send it straight to an IO.
+ #
+ # Here is an example use:
+ #
+ # stream = Psych::Stream.new($stdout)
+ # stream.start
+ # stream.push({:foo => 'bar'})
+ # stream.finish
+ #
+ # YAML will be immediately emitted to $stdout with no buffering.
+ #
+ # Psych::Stream#start will take a block and ensure that Psych::Stream#finish
+ # is called, so you can do this form:
+ #
+ # stream = Psych::Stream.new($stdout)
+ # stream.start do |em|
+ # em.push(:foo => 'bar')
+ # end
+ #
+ class Stream < Psych::Visitors::YAMLTree
+ class Emitter < Psych::Emitter # :nodoc:
+ def end_document implicit_end = !streaming?
+ super
+ end
+
+ def streaming?
+ true
+ end
+ end
+
+ include Psych::Streaming
+ end
+end
diff --git a/ext/psych/lib/psych/streaming.rb b/ext/psych/lib/psych/streaming.rb
new file mode 100644
index 0000000..c6fa109
--- /dev/null
+++ b/ext/psych/lib/psych/streaming.rb
@@ -0,0 +1,22 @@
+module Psych
+ module Streaming
+ ###
+ # Create a new streaming emitter. Emitter will print to +io+. See
+ # Psych::Stream for an example.
+ def initialize io
+ super({}, self.class.const_get(:Emitter).new(io))
+ end
+
+ ###
+ # Start streaming using +encoding+
+ def start encoding = Nodes::Stream::UTF8
+ super.tap { yield self if block_given? }
+ ensure
+ finish if block_given?
+ end
+
+ private
+ def register target, obj
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/syntax_error.rb b/ext/psych/lib/psych/syntax_error.rb
new file mode 100644
index 0000000..f79743d
--- /dev/null
+++ b/ext/psych/lib/psych/syntax_error.rb
@@ -0,0 +1,19 @@
+module Psych
+ class SyntaxError < ::SyntaxError
+ attr_reader :file, :line, :column, :offset, :problem, :context
+
+ def initialize file, line, col, offset, problem, context
+ err = [problem, context].compact.join ' '
+ filename = file || '<unknown>'
+ message = "(%s): %s at line %d column %d" % [filename, err, line, col]
+
+ @file = file
+ @line = line
+ @column = col
+ @offset = offset
+ @problem = problem
+ @context = context
+ super(message)
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/tree_builder.rb b/ext/psych/lib/psych/tree_builder.rb
new file mode 100644
index 0000000..c8f3447
--- /dev/null
+++ b/ext/psych/lib/psych/tree_builder.rb
@@ -0,0 +1,96 @@
+require 'psych/handler'
+
+module Psych
+ ###
+ # This class works in conjunction with Psych::Parser to build an in-memory
+ # parse tree that represents a YAML document.
+ #
+ # == Example
+ #
+ # parser = Psych::Parser.new Psych::TreeBuilder.new
+ # parser.parse('--- foo')
+ # tree = parser.handler.root
+ #
+ # See Psych::Handler for documentation on the event methods used in this
+ # class.
+ class TreeBuilder < Psych::Handler
+ # Returns the root node for the built tree
+ attr_reader :root
+
+ # Create a new TreeBuilder instance
+ def initialize
+ @stack = []
+ @last = nil
+ @root = nil
+ end
+
+ %w{
+ Sequence
+ Mapping
+ }.each do |node|
+ class_eval %{
+ def start_#{node.downcase}(anchor, tag, implicit, style)
+ n = Nodes::#{node}.new(anchor, tag, implicit, style)
+ @last.children << n
+ push n
+ end
+
+ def end_#{node.downcase}
+ pop
+ end
+ }
+ end
+
+ ###
+ # Handles start_document events with +version+, +tag_directives+,
+ # and +implicit+ styling.
+ #
+ # See Psych::Handler#start_document
+ def start_document version, tag_directives, implicit
+ n = Nodes::Document.new version, tag_directives, implicit
+ @last.children << n
+ push n
+ end
+
+ ###
+ # Handles end_document events with +version+, +tag_directives+,
+ # and +implicit+ styling.
+ #
+ # See Psych::Handler#start_document
+ def end_document implicit_end = !streaming?
+ @last.implicit_end = implicit_end
+ pop
+ end
+
+ def start_stream encoding
+ @root = Nodes::Stream.new(encoding)
+ push @root
+ end
+
+ def end_stream
+ pop
+ end
+
+ def scalar value, anchor, tag, plain, quoted, style
+ s = Nodes::Scalar.new(value,anchor,tag,plain,quoted,style)
+ @last.children << s
+ s
+ end
+
+ def alias anchor
+ @last.children << Nodes::Alias.new(anchor)
+ end
+
+ private
+ def push value
+ @stack.push value
+ @last = value
+ end
+
+ def pop
+ x = @stack.pop
+ @last = @stack.last
+ x
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/visitors.rb b/ext/psych/lib/psych/visitors.rb
new file mode 100644
index 0000000..cc98b10
--- /dev/null
+++ b/ext/psych/lib/psych/visitors.rb
@@ -0,0 +1,6 @@
+require 'psych/visitors/visitor'
+require 'psych/visitors/to_ruby'
+require 'psych/visitors/emitter'
+require 'psych/visitors/yaml_tree'
+require 'psych/visitors/json_tree'
+require 'psych/visitors/depth_first'
diff --git a/ext/psych/lib/psych/visitors/depth_first.rb b/ext/psych/lib/psych/visitors/depth_first.rb
new file mode 100644
index 0000000..c6eb814
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/depth_first.rb
@@ -0,0 +1,26 @@
+module Psych
+ module Visitors
+ class DepthFirst < Psych::Visitors::Visitor
+ def initialize block
+ @block = block
+ end
+
+ private
+
+ def nary o
+ o.children.each { |x| visit x }
+ @block.call o
+ end
+ alias :visit_Psych_Nodes_Stream :nary
+ alias :visit_Psych_Nodes_Document :nary
+ alias :visit_Psych_Nodes_Sequence :nary
+ alias :visit_Psych_Nodes_Mapping :nary
+
+ def terminal o
+ @block.call o
+ end
+ alias :visit_Psych_Nodes_Scalar :terminal
+ alias :visit_Psych_Nodes_Alias :terminal
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/visitors/emitter.rb b/ext/psych/lib/psych/visitors/emitter.rb
new file mode 100644
index 0000000..30db176
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/emitter.rb
@@ -0,0 +1,44 @@
+module Psych
+ module Visitors
+ class Emitter < Psych::Visitors::Visitor
+ def initialize io, options = {}
+ @handler = Psych::Emitter.new io
+ @handler.indentation = options[:indentation] if options[:indentation]
+ @handler.canonical = options[:canonical] if options[:canonical]
+ @handler.line_width = options[:line_width] if options[:line_width]
+ end
+
+ def visit_Psych_Nodes_Stream o
+ @handler.start_stream o.encoding
+ o.children.each { |c| accept c }
+ @handler.end_stream
+ end
+
+ def visit_Psych_Nodes_Document o
+ @handler.start_document o.version, o.tag_directives, o.implicit
+ o.children.each { |c| accept c }
+ @handler.end_document o.implicit_end
+ end
+
+ def visit_Psych_Nodes_Scalar o
+ @handler.scalar o.value, o.anchor, o.tag, o.plain, o.quoted, o.style
+ end
+
+ def visit_Psych_Nodes_Sequence o
+ @handler.start_sequence o.anchor, o.tag, o.implicit, o.style
+ o.children.each { |c| accept c }
+ @handler.end_sequence
+ end
+
+ def visit_Psych_Nodes_Mapping o
+ @handler.start_mapping o.anchor, o.tag, o.implicit, o.style
+ o.children.each { |c| accept c }
+ @handler.end_mapping
+ end
+
+ def visit_Psych_Nodes_Alias o
+ @handler.alias o.anchor
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/visitors/json_tree.rb b/ext/psych/lib/psych/visitors/json_tree.rb
new file mode 100644
index 0000000..0350dd1
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/json_tree.rb
@@ -0,0 +1,21 @@
+require 'psych/json/ruby_events'
+
+module Psych
+ module Visitors
+ class JSONTree < YAMLTree
+ include Psych::JSON::RubyEvents
+
+ def initialize options = {}, emitter = Psych::JSON::TreeBuilder.new
+ super
+ end
+
+ def accept target
+ if target.respond_to?(:encode_with)
+ dump_coder target
+ else
+ send(@dispatch_cache[target.class], target)
+ end
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb
new file mode 100644
index 0000000..2e082f9
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/to_ruby.rb
@@ -0,0 +1,317 @@
+require 'psych/scalar_scanner'
+
+unless defined?(Regexp::NOENCODING)
+ Regexp::NOENCODING = 32
+end
+
+module Psych
+ module Visitors
+ ###
+ # This class walks a YAML AST, converting each node to ruby
+ class ToRuby < Psych::Visitors::Visitor
+ def initialize ss = ScalarScanner.new
+ super()
+ @st = {}
+ @ss = ss
+ @domain_types = Psych.domain_types
+ end
+
+ def accept target
+ result = super
+ return result if @domain_types.empty? || !target.tag
+
+ key = target.tag.sub(/^[!\/]*/, '').sub(/(,\d+)\//, '\1:')
+ key = "tag:#{key}" unless key =~ /^(tag:|x-private)/
+
+ if @domain_types.key? key
+ value, block = @domain_types[key]
+ return block.call value, result
+ end
+
+ result
+ end
+
+ def deserialize o
+ if klass = Psych.load_tags[o.tag]
+ instance = klass.allocate
+
+ if instance.respond_to?(:init_with)
+ coder = Psych::Coder.new(o.tag)
+ coder.scalar = o.value
+ instance.init_with coder
+ end
+
+ return instance
+ end
+
+ return o.value if o.quoted
+ return @ss.tokenize(o.value) unless o.tag
+
+ case o.tag
+ when '!binary', 'tag:yaml.org,2002:binary'
+ o.value.unpack('m').first
+ when /^!(?:str|ruby\/string)(?::(.*))?/, 'tag:yaml.org,2002:str'
+ klass = resolve_class($1)
+ if klass
+ klass.allocate.replace o.value
+ else
+ o.value
+ end
+ when '!ruby/object:BigDecimal'
+ require 'bigdecimal'
+ BigDecimal._load o.value
+ when "!ruby/object:DateTime"
+ require 'date'
+ @ss.parse_time(o.value).to_datetime
+ when "!ruby/object:Complex"
+ Complex(o.value)
+ when "!ruby/object:Rational"
+ Rational(o.value)
+ when "!ruby/class", "!ruby/module"
+ resolve_class o.value
+ when "tag:yaml.org,2002:float", "!float"
+ Float(@ss.tokenize(o.value))
+ when "!ruby/regexp"
+ o.value =~ /^\/(.*)\/([mixn]*)$/
+ source = $1
+ options = 0
+ lang = nil
+ ($2 || '').split('').each do |option|
+ case option
+ when 'x' then options |= Regexp::EXTENDED
+ when 'i' then options |= Regexp::IGNORECASE
+ when 'm' then options |= Regexp::MULTILINE
+ when 'n' then options |= Regexp::NOENCODING
+ else lang = option
+ end
+ end
+ Regexp.new(*[source, options, lang].compact)
+ when "!ruby/range"
+ args = o.value.split(/([.]{2,3})/, 2).map { |s|
+ accept Nodes::Scalar.new(s)
+ }
+ args.push(args.delete_at(1) == '...')
+ Range.new(*args)
+ when /^!ruby\/sym(bol)?:?(.*)?$/
+ o.value.to_sym
+ else
+ @ss.tokenize o.value
+ end
+ end
+ private :deserialize
+
+ def visit_Psych_Nodes_Scalar o
+ register o, deserialize(o)
+ end
+
+ def visit_Psych_Nodes_Sequence o
+ if klass = Psych.load_tags[o.tag]
+ instance = klass.allocate
+
+ if instance.respond_to?(:init_with)
+ coder = Psych::Coder.new(o.tag)
+ coder.seq = o.children.map { |c| accept c }
+ instance.init_with coder
+ end
+
+ return instance
+ end
+
+ case o.tag
+ when '!omap', 'tag:yaml.org,2002:omap'
+ map = register(o, Psych::Omap.new)
+ o.children.each { |a|
+ map[accept(a.children.first)] = accept a.children.last
+ }
+ map
+ when /^!(?:seq|ruby\/array):(.*)$/
+ klass = resolve_class($1)
+ list = register(o, klass.allocate)
+ o.children.each { |c| list.push accept c }
+ list
+ else
+ list = register(o, [])
+ o.children.each { |c| list.push accept c }
+ list
+ end
+ end
+
+ def visit_Psych_Nodes_Mapping o
+ return revive(Psych.load_tags[o.tag], o) if Psych.load_tags[o.tag]
+ return revive_hash({}, o) unless o.tag
+
+ case o.tag
+ when /^!(?:str|ruby\/string)(?::(.*))?/, 'tag:yaml.org,2002:str'
+ klass = resolve_class($1)
+ members = Hash[*o.children.map { |c| accept c }]
+ string = members.delete 'str'
+
+ if klass
+ string = klass.allocate
+ string.replace string
+ end
+
+ init_with(string, members.map { |k,v| [k.to_s.sub(/^@/, ''),v] }, o)
+ when /^!ruby\/array:(.*)$/
+ klass = resolve_class($1)
+ list = register(o, klass.allocate)
+
+ members = Hash[o.children.map { |c| accept c }.each_slice(2).to_a]
+ list.replace members['internal']
+
+ members['ivars'].each do |ivar, v|
+ list.instance_variable_set ivar, v
+ end
+ list
+ when /^!ruby\/struct:?(.*)?$/
+ klass = resolve_class($1)
+
+ if klass
+ s = register(o, klass.allocate)
+
+ members = {}
+ struct_members = s.members.map { |x| x.to_sym }
+ o.children.each_slice(2) do |k,v|
+ member = accept(k)
+ value = accept(v)
+ if struct_members.include?(member.to_sym)
+ s.send("#{member}=", value)
+ else
+ members[member.to_s.sub(/^@/, '')] = value
+ end
+ end
+ init_with(s, members, o)
+ else
+ members = o.children.map { |c| accept c }
+ h = Hash[*members]
+ Struct.new(*h.map { |k,v| k.to_sym }).new(*h.map { |k,v| v })
+ end
+
+ when '!ruby/range'
+ h = Hash[*o.children.map { |c| accept c }]
+ register o, Range.new(h['begin'], h['end'], h['excl'])
+
+ when /^!ruby\/exception:?(.*)?$/
+ h = Hash[*o.children.map { |c| accept c }]
+
+ e = build_exception((resolve_class($1) || Exception),
+ h.delete('message'))
+ init_with(e, h, o)
+
+ when '!set', 'tag:yaml.org,2002:set'
+ set = Psych::Set.new
+ @st[o.anchor] = set if o.anchor
+ o.children.each_slice(2) do |k,v|
+ set[accept(k)] = accept(v)
+ end
+ set
+
+ when '!ruby/object:Complex'
+ h = Hash[*o.children.map { |c| accept c }]
+ register o, Complex(h['real'], h['image'])
+
+ when '!ruby/object:Rational'
+ h = Hash[*o.children.map { |c| accept c }]
+ register o, Rational(h['numerator'], h['denominator'])
+
+ when /^!ruby\/object:?(.*)?$/
+ name = $1 || 'Object'
+ obj = revive((resolve_class(name) || Object), o)
+ obj
+
+ when /^!map:(.*)$/, /^!ruby\/hash:(.*)$/
+ revive_hash resolve_class($1).new, o
+
+ else
+ revive_hash({}, o)
+ end
+ end
+
+ def visit_Psych_Nodes_Document o
+ accept o.root
+ end
+
+ def visit_Psych_Nodes_Stream o
+ o.children.map { |c| accept c }
+ end
+
+ def visit_Psych_Nodes_Alias o
+ @st.fetch(o.anchor) { raise BadAlias, "Unknown alias: #{o.anchor}" }
+ end
+
+ private
+ def register node, object
+ @st[node.anchor] = object if node.anchor
+ object
+ end
+
+ def revive_hash hash, o
+ @st[o.anchor] = hash if o.anchor
+
+ o.children.each_slice(2) { |k,v|
+ key = accept(k)
+
+ if key == '<<'
+ case v
+ when Nodes::Alias
+ hash.merge! accept(v)
+ when Nodes::Sequence
+ accept(v).reverse_each do |value|
+ hash.merge! value
+ end
+ else
+ hash[key] = accept(v)
+ end
+ else
+ hash[key] = accept(v)
+ end
+
+ }
+ hash
+ end
+
+ def revive klass, node
+ s = klass.allocate
+ @st[node.anchor] = s if node.anchor
+ h = Hash[*node.children.map { |c| accept c }]
+ init_with(s, h, node)
+ end
+
+ def init_with o, h, node
+ c = Psych::Coder.new(node.tag)
+ c.map = h
+
+ if o.respond_to?(:init_with)
+ o.init_with c
+ elsif o.respond_to?(:yaml_initialize)
+ if $VERBOSE
+ warn "Implementing #{o.class}#yaml_initialize is deprecated, please implement \"init_with(coder)\""
+ end
+ o.yaml_initialize c.tag, c.map
+ else
+ h.each { |k,v| o.instance_variable_set(:"@#{k}", v) }
+ end
+ o
+ end
+
+ # Convert +klassname+ to a Class
+ def resolve_class klassname
+ return nil unless klassname and not klassname.empty?
+
+ name = klassname
+ retried = false
+
+ begin
+ path2class(name)
+ rescue ArgumentError, NameError => ex
+ unless retried
+ name = "Struct::#{name}"
+ retried = ex
+ retry
+ end
+ raise retried
+ end
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/visitors/visitor.rb b/ext/psych/lib/psych/visitors/visitor.rb
new file mode 100644
index 0000000..4d7772f
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/visitor.rb
@@ -0,0 +1,19 @@
+module Psych
+ module Visitors
+ class Visitor
+ def accept target
+ visit target
+ end
+
+ private
+
+ DISPATCH = Hash.new do |hash, klass|
+ hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
+ end
+
+ def visit target
+ send DISPATCH[target.class], target
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/visitors/yaml_tree.rb b/ext/psych/lib/psych/visitors/yaml_tree.rb
new file mode 100644
index 0000000..80af046
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/yaml_tree.rb
@@ -0,0 +1,452 @@
+module Psych
+ module Visitors
+ ###
+ # YAMLTree builds a YAML ast given a ruby object. For example:
+ #
+ # builder = Psych::Visitors::YAMLTree.new
+ # builder << { :foo => 'bar' }
+ # builder.tree # => #<Psych::Nodes::Stream .. }
+ #
+ class YAMLTree < Psych::Visitors::Visitor
+ attr_reader :started, :finished
+ alias :finished? :finished
+ alias :started? :started
+
+ def initialize options = {}, emitter = TreeBuilder.new, ss = ScalarScanner.new
+ super()
+ @started = false
+ @finished = false
+ @emitter = emitter
+ @st = {}
+ @ss = ss
+ @options = options
+
+ @dispatch_cache = Hash.new do |h,klass|
+ method = "visit_#{(klass.name || '').split('::').join('_')}"
+
+ method = respond_to?(method) ? method : h[klass.superclass]
+
+ raise(TypeError, "Can't dump #{target.class}") unless method
+
+ h[klass] = method
+ end
+ end
+
+ def start encoding = Nodes::Stream::UTF8
+ @emitter.start_stream(encoding).tap do
+ @started = true
+ end
+ end
+
+ def finish
+ @emitter.end_stream.tap do
+ @finished = true
+ end
+ end
+
+ def tree
+ finish unless finished?
+ end
+
+ def push object
+ start unless started?
+ version = []
+ version = [1,1] if @options[:header]
+
+ case @options[:version]
+ when Array
+ version = @options[:version]
+ when String
+ version = @options[:version].split('.').map { |x| x.to_i }
+ else
+ version = [1,1]
+ end if @options.key? :version
+
+ @emitter.start_document version, [], false
+ accept object
+ @emitter.end_document
+ end
+ alias :<< :push
+
+ def accept target
+ # return any aliases we find
+ if @st.key? target.object_id
+ oid = target.object_id
+ node = @st[oid]
+ anchor = oid.to_s
+ node.anchor = anchor
+ return @emitter.alias anchor
+ end
+
+ if target.respond_to?(:to_yaml)
+ begin
+ loc = target.method(:to_yaml).source_location.first
+ if loc !~ /(syck\/rubytypes.rb|psych\/core_ext.rb)/
+ unless target.respond_to?(:encode_with)
+ if $VERBOSE
+ warn "implementing to_yaml is deprecated, please implement \"encode_with\""
+ end
+
+ target.to_yaml(:nodump => true)
+ end
+ end
+ rescue
+ # public_method or source_location might be overridden,
+ # and it's OK to skip it since it's only to emit a warning
+ end
+ end
+
+ if target.respond_to?(:encode_with)
+ dump_coder target
+ else
+ send(@dispatch_cache[target.class], target)
+ end
+ end
+
+ def visit_Psych_Omap o
+ seq = @emitter.start_sequence(nil, '!omap', false, Nodes::Sequence::BLOCK)
+ register(o, seq)
+
+ o.each { |k,v| visit_Hash k => v }
+ @emitter.end_sequence
+ end
+
+ def visit_Object o
+ tag = Psych.dump_tags[o.class]
+ unless tag
+ klass = o.class == Object ? nil : o.class.name
+ tag = ['!ruby/object', klass].compact.join(':')
+ end
+
+ map = @emitter.start_mapping(nil, tag, false, Nodes::Mapping::BLOCK)
+ register(o, map)
+
+ dump_ivars o
+ @emitter.end_mapping
+ end
+
+ def visit_Struct o
+ tag = ['!ruby/struct', o.class.name].compact.join(':')
+
+ register o, @emitter.start_mapping(nil, tag, false, Nodes::Mapping::BLOCK)
+ o.members.each do |member|
+ @emitter.scalar member.to_s, nil, nil, true, false, Nodes::Scalar::ANY
+ accept o[member]
+ end
+
+ dump_ivars o
+
+ @emitter.end_mapping
+ end
+
+ def visit_Exception o
+ tag = ['!ruby/exception', o.class.name].join ':'
+
+ @emitter.start_mapping nil, tag, false, Nodes::Mapping::BLOCK
+
+ {
+ 'message' => private_iv_get(o, 'mesg'),
+ 'backtrace' => private_iv_get(o, 'backtrace'),
+ }.each do |k,v|
+ next unless v
+ @emitter.scalar k, nil, nil, true, false, Nodes::Scalar::ANY
+ accept v
+ end
+
+ dump_ivars o
+
+ @emitter.end_mapping
+ end
+
+ def visit_Regexp o
+ register o, @emitter.scalar(o.inspect, nil, '!ruby/regexp', false, false, Nodes::Scalar::ANY)
+ end
+
+ def visit_DateTime o
+ formatted = format_time o.to_time
+ tag = '!ruby/object:DateTime'
+ register o, @emitter.scalar(formatted, nil, tag, false, false, Nodes::Scalar::ANY)
+ end
+
+ def visit_Time o
+ formatted = format_time o
+ @emitter.scalar formatted, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+
+ def visit_Rational o
+ register o, @emitter.start_mapping(nil, '!ruby/object:Rational', false, Nodes::Mapping::BLOCK)
+
+ [
+ 'denominator', o.denominator.to_s,
+ 'numerator', o.numerator.to_s
+ ].each do |m|
+ @emitter.scalar m, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+
+ @emitter.end_mapping
+ end
+
+ def visit_Complex o
+ register o, @emitter.start_mapping(nil, '!ruby/object:Complex', false, Nodes::Mapping::BLOCK)
+
+ ['real', o.real.to_s, 'image', o.imag.to_s].each do |m|
+ @emitter.scalar m, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+
+ @emitter.end_mapping
+ end
+
+ def visit_Integer o
+ @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+ alias :visit_TrueClass :visit_Integer
+ alias :visit_FalseClass :visit_Integer
+ alias :visit_Date :visit_Integer
+
+ def visit_Float o
+ if o.nan?
+ @emitter.scalar '.nan', nil, nil, true, false, Nodes::Scalar::ANY
+ elsif o.infinite?
+ @emitter.scalar((o.infinite? > 0 ? '.inf' : '-.inf'),
+ nil, nil, true, false, Nodes::Scalar::ANY)
+ else
+ @emitter.scalar o.to_s, nil, nil, true, false, Nodes::Scalar::ANY
+ end
+ end
+
+ def visit_BigDecimal o
+ @emitter.scalar o._dump, nil, '!ruby/object:BigDecimal', false, false, Nodes::Scalar::ANY
+ end
+
+ def binary? string
+ string.encoding == Encoding::ASCII_8BIT ||
+ string.index("\x00") ||
+ string.count("\x00-\x7F", "^ -~\t\r\n").fdiv(string.length) > 0.3
+ end
+ private :binary?
+
+ def visit_String o
+ plain = false
+ quote = false
+ style = Nodes::Scalar::ANY
+
+ if binary?(o)
+ str = [o].pack('m').chomp
+ tag = '!binary' # FIXME: change to below when syck is removed
+ #tag = 'tag:yaml.org,2002:binary'
+ style = Nodes::Scalar::LITERAL
+ else
+ str = o
+ tag = nil
+ quote = !(String === @ss.tokenize(o))
+ plain = !quote
+ end
+
+ ivars = find_ivars o
+
+ if ivars.empty?
+ unless o.class == ::String
+ tag = "!ruby/string:#{o.class}"
+ end
+ @emitter.scalar str, nil, tag, plain, quote, style
+ else
+ maptag = '!ruby/string'
+ maptag << ":#{o.class}" unless o.class == ::String
+
+ @emitter.start_mapping nil, maptag, false, Nodes::Mapping::BLOCK
+ @emitter.scalar 'str', nil, nil, true, false, Nodes::Scalar::ANY
+ @emitter.scalar str, nil, tag, plain, quote, style
+
+ dump_ivars o
+
+ @emitter.end_mapping
+ end
+ end
+
+ def visit_Module o
+ raise TypeError, "can't dump anonymous module: #{o}" unless o.name
+ register o, @emitter.scalar(o.name, nil, '!ruby/module', false, false, Nodes::Scalar::SINGLE_QUOTED)
+ end
+
+ def visit_Class o
+ raise TypeError, "can't dump anonymous class: #{o}" unless o.name
+ register o, @emitter.scalar(o.name, nil, '!ruby/class', false, false, Nodes::Scalar::SINGLE_QUOTED)
+ end
+
+ def visit_Range o
+ register o, @emitter.start_mapping(nil, '!ruby/range', false, Nodes::Mapping::BLOCK)
+ ['begin', o.begin, 'end', o.end, 'excl', o.exclude_end?].each do |m|
+ accept m
+ end
+ @emitter.end_mapping
+ end
+
+ def visit_Hash o
+ tag = o.class == ::Hash ? nil : "!ruby/hash:#{o.class}"
+ implicit = !tag
+
+ register(o, @emitter.start_mapping(nil, tag, implicit, Psych::Nodes::Mapping::BLOCK))
+
+ o.each do |k,v|
+ accept k
+ accept v
+ end
+
+ @emitter.end_mapping
+ end
+
+ def visit_Psych_Set o
+ register(o, @emitter.start_mapping(nil, '!set', false, Psych::Nodes::Mapping::BLOCK))
+
+ o.each do |k,v|
+ accept k
+ accept v
+ end
+
+ @emitter.end_mapping
+ end
+
+ def visit_Array o
+ if o.class == ::Array
+ register o, @emitter.start_sequence(nil, nil, true, Nodes::Sequence::BLOCK)
+ o.each { |c| accept c }
+ @emitter.end_sequence
+ else
+ visit_array_subclass o
+ end
+ end
+
+ def visit_NilClass o
+ @emitter.scalar('', nil, 'tag:yaml.org,2002:null', true, false, Nodes::Scalar::ANY)
+ end
+
+ def visit_Symbol o
+ @emitter.scalar ":#{o}", nil, nil, true, false, Nodes::Scalar::ANY
+ end
+
+ private
+ def visit_array_subclass o
+ tag = "!ruby/array:#{o.class}"
+ if o.instance_variables.empty?
+ node = @emitter.start_sequence(nil, tag, false, Nodes::Sequence::BLOCK)
+ register o, node
+ o.each { |c| accept c }
+ @emitter.end_sequence
+ else
+ node = @emitter.start_mapping(nil, tag, false, Nodes::Sequence::BLOCK)
+ register o, node
+
+ # Dump the internal list
+ accept 'internal'
+ @emitter.start_sequence(nil, nil, true, Nodes::Sequence::BLOCK)
+ o.each { |c| accept c }
+ @emitter.end_sequence
+
+ # Dump the ivars
+ accept 'ivars'
+ @emitter.start_mapping(nil, nil, true, Nodes::Sequence::BLOCK)
+ o.instance_variables.each do |ivar|
+ accept ivar
+ accept o.instance_variable_get ivar
+ end
+ @emitter.end_mapping
+
+ @emitter.end_mapping
+ end
+ end
+
+ def dump_list o
+ end
+
+ # '%:z' was no defined until 1.9.3
+ if RUBY_VERSION < '1.9.3'
+ def format_time time
+ formatted = time.strftime("%Y-%m-%d %H:%M:%S.%9N")
+
+ if time.utc?
+ formatted += " Z"
+ else
+ zone = time.strftime('%z')
+ formatted += " #{zone[0,3]}:#{zone[3,5]}"
+ end
+
+ formatted
+ end
+ else
+ def format_time time
+ if time.utc?
+ time.strftime("%Y-%m-%d %H:%M:%S.%9N Z")
+ else
+ time.strftime("%Y-%m-%d %H:%M:%S.%9N %:z")
+ end
+ end
+ end
+
+ # FIXME: remove this method once "to_yaml_properties" is removed
+ def find_ivars target
+ begin
+ loc = target.method(:to_yaml_properties).source_location.first
+ unless loc.start_with?(Psych::DEPRECATED) || loc.end_with?('rubytypes.rb')
+ if $VERBOSE
+ warn "#{loc}: to_yaml_properties is deprecated, please implement \"encode_with(coder)\""
+ end
+ return target.to_yaml_properties
+ end
+ rescue
+ # public_method or source_location might be overridden,
+ # and it's OK to skip it since it's only to emit a warning.
+ end
+
+ target.instance_variables
+ end
+
+ def register target, yaml_obj
+ @st[target.object_id] = yaml_obj
+ yaml_obj
+ end
+
+ def dump_coder o
+ tag = Psych.dump_tags[o.class]
+ unless tag
+ klass = o.class == Object ? nil : o.class.name
+ tag = ['!ruby/object', klass].compact.join(':')
+ end
+
+ c = Psych::Coder.new(tag)
+ o.encode_with(c)
+ emit_coder c
+ end
+
+ def emit_coder c
+ case c.type
+ when :scalar
+ @emitter.scalar c.scalar, nil, c.tag, c.tag.nil?, false, Nodes::Scalar::ANY
+ when :seq
+ @emitter.start_sequence nil, c.tag, c.tag.nil?, Nodes::Sequence::BLOCK
+ c.seq.each do |thing|
+ accept thing
+ end
+ @emitter.end_sequence
+ when :map
+ @emitter.start_mapping nil, c.tag, c.implicit, c.style
+ c.map.each do |k,v|
+ @emitter.scalar k, nil, nil, true, false, Nodes::Scalar::ANY
+ accept v
+ end
+ @emitter.end_mapping
+ when :object
+ accept c.object
+ end
+ end
+
+ def dump_ivars target
+ ivars = find_ivars target
+
+ ivars.each do |iv|
+ @emitter.scalar("#{iv.to_s.sub(/^@/, '')}", nil, nil, true, false, Nodes::Scalar::ANY)
+ accept target.instance_variable_get(iv)
+ end
+ end
+ end
+ end
+end