summaryrefslogtreecommitdiff
path: root/ext/psych
diff options
context:
space:
mode:
authornobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2010-03-30 10:46:06 +0000
committernobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2010-03-30 10:46:06 +0000
commitf1330fd12403ef4d664df7619d6ce312d525721a (patch)
treecfafd35d3d416b8529003d43db2ace98af20fdb1 /ext/psych
parent38af94c6abaaa20aeaa1ffafbe3f6ff962f63eaa (diff)
* ext/psych/lib: moved external library dependent files from lib.
[ruby-core:29129] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@27112 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'ext/psych')
-rw-r--r--ext/psych/lib/psych.rb238
-rw-r--r--ext/psych/lib/psych/coder.rb68
-rw-r--r--ext/psych/lib/psych/core_ext.rb22
-rw-r--r--ext/psych/lib/psych/emitter.rb4
-rw-r--r--ext/psych/lib/psych/handler.rb215
-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.rb40
-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.rb44
-rw-r--r--ext/psych/lib/psych/scalar_scanner.rb90
-rw-r--r--ext/psych/lib/psych/set.rb4
-rw-r--r--ext/psych/lib/psych/tree_builder.rb89
-rw-r--r--ext/psych/lib/psych/visitors.rb5
-rw-r--r--ext/psych/lib/psych/visitors/emitter.rb41
-rw-r--r--ext/psych/lib/psych/visitors/json_tree.rb37
-rw-r--r--ext/psych/lib/psych/visitors/to_ruby.rb252
-rw-r--r--ext/psych/lib/psych/visitors/visitor.rb18
-rw-r--r--ext/psych/lib/psych/visitors/yaml_tree.rb312
24 files changed, 1879 insertions, 0 deletions
diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb
new file mode 100644
index 0000000000..c14259373a
--- /dev/null
+++ b/ext/psych/lib/psych.rb
@@ -0,0 +1,238 @@
+require 'psych/psych'
+require 'psych/nodes'
+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'
+
+###
+# = 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.0.0'
+
+ # The version of libyaml Psych is using
+ LIBYAML_VERSION = Psych.libyaml_version.join '.'
+
+ ###
+ # Load +yaml+ in to a Ruby data structure. If multiple documents are
+ # provided, the object contained in the first document will be returned.
+ #
+ # Example:
+ #
+ # Psych.load("--- a") # => 'a'
+ # Psych.load("---\n - a\n - b") # => ['a', 'b']
+ def self.load yaml
+ result = parse(yaml)
+ result ? result.to_ruby : result
+ end
+
+ ###
+ # Parse a YAML string in +yaml+. Returns the first object of a YAML AST.
+ #
+ # Example:
+ #
+ # Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Sequence:0x00>
+ #
+ # See Psych::Nodes for more information about YAML AST.
+ def self.parse yaml
+ children = parse_stream(yaml).children
+ children.empty? ? false : children.first.children.first
+ end
+
+ ###
+ # Parse a file at +filename+. Returns the YAML AST.
+ def self.parse_file filename
+ File.open filename do |f|
+ parse f
+ 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+.
+ #
+ # Example:
+ #
+ # Psych.parse_stream("---\n - a\n - b") # => #<Psych::Nodes::Stream:0x00>
+ #
+ # See Psych::Nodes for more information about YAML AST.
+ def self.parse_stream yaml
+ parser = self.parser
+ parser.parse yaml
+ parser.handler.root
+ end
+
+ ###
+ # Dump Ruby object +o+ to a YAML string using +options+.
+ #
+ # Example:
+ #
+ # Psych.dump(['a', 'b']) # => "---\n- a\n- b\n"
+ def self.dump o, options = {}
+ visitor = Psych::Visitors::YAMLTree.new options
+ visitor << o
+ visitor.tree.to_yaml
+ 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.to_yaml
+ end
+
+ ###
+ # Dump Ruby object +o+ to a JSON string.
+ def self.to_json o
+ visitor = Psych::Visitors::JSONTree.new(:json => true)
+ visitor << o
+ visitor.tree.to_yaml
+ end
+
+ ###
+ # Load multiple documents given in +yaml+. Returns the parsed documents
+ # as a list. For example:
+ #
+ # Psych.load_documents("--- foo\n...\n--- bar\n...") # => ['foo', 'bar']
+ #
+ def self.load_stream yaml
+ parse_stream(yaml).children.map { |child| child.to_ruby }
+ 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
+
+ ###
+ # Load the document contained in +filename+. Returns the yaml contained in
+ # +filename+ as a ruby object
+ def self.load_file filename
+ self.load File.open(filename)
+ end
+
+ # :stopdoc:
+ @domain_types = {}
+ def self.add_domain_type domain, type_tag, &block
+ @domain_types[type_tag] = ["http://#{domain}", block]
+ end
+
+ def self.add_builtin_type type_tag, &block
+ @domain_types[type_tag] = ['yaml.org', 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 0000000000..79e46dab1b
--- /dev/null
+++ b/ext/psych/lib/psych/coder.rb
@@ -0,0 +1,68 @@
+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
+ attr_reader :type, :map, :scalar, :seq
+
+ def initialize tag
+ @map = {}
+ @seq = []
+ @implicit = false
+ @type = :map
+ @tag = tag
+ @style = Psych::Nodes::Mapping::BLOCK
+ @scalar = nil
+ 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 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
+
+ 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 0000000000..bd22219c07
--- /dev/null
+++ b/ext/psych/lib/psych/core_ext.rb
@@ -0,0 +1,22 @@
+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
+ #
+ # Convert an object to YAML
+ def psych_to_yaml options = {}
+ Psych.dump self, options
+ end
+ alias :to_yaml :psych_to_yaml
+end
+
+module Kernel
+ def y *objects
+ puts Psych.dump_stream(*objects)
+ end
+end
diff --git a/ext/psych/lib/psych/emitter.rb b/ext/psych/lib/psych/emitter.rb
new file mode 100644
index 0000000000..b06ad3ac49
--- /dev/null
+++ b/ext/psych/lib/psych/emitter.rb
@@ -0,0 +1,4 @@
+module Psych
+ class Emitter < Psych::Handler
+ end
+end
diff --git a/ext/psych/lib/psych/handler.rb b/ext/psych/lib/psych/handler.rb
new file mode 100644
index 0000000000..bfc62d701f
--- /dev/null
+++ b/ext/psych/lib/psych/handler.rb
@@ -0,0 +1,215 @@
+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
+ end
+end
diff --git a/ext/psych/lib/psych/nodes.rb b/ext/psych/lib/psych/nodes.rb
new file mode 100644
index 0000000000..9e5946daa9
--- /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 0000000000..5bd4df13d1
--- /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 0000000000..32014d60dc
--- /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 0000000000..5ba95ce4b6
--- /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 0000000000..0de768c7ac
--- /dev/null
+++ b/ext/psych/lib/psych/nodes/node.rb
@@ -0,0 +1,40 @@
+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
+ # The children of this node
+ attr_reader :children
+
+ # An associated tag
+ attr_reader :tag
+
+ # Create a new Psych::Nodes::Node
+ def initialize
+ @children = []
+ 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 to_yaml
+ io = StringIO.new
+ Visitors::Emitter.new(io).accept self
+ io.string
+ end
+ 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 0000000000..1b1b25b98a
--- /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 0000000000..e4b833d330
--- /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 0000000000..f4aab5a7dc
--- /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_reader :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 0000000000..6286270616
--- /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 0000000000..0e38a4ae7d
--- /dev/null
+++ b/ext/psych/lib/psych/parser.rb
@@ -0,0 +1,44 @@
+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
+ # The handler on which events will be called
+ attr_accessor :handler
+
+ ###
+ # 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
+ 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 0000000000..bee88de419
--- /dev/null
+++ b/ext/psych/lib/psych/scalar_scanner.rb
@@ -0,0 +1,90 @@
+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)?)?/
+
+ # 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
+ 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(md[2].sub(/^\./, '0.')) : 0) * 1000000
+
+ time = Time.utc(yy, m, dd, hh, mm, ss, us)
+
+ return time if 'Z' == md[3]
+
+ tz = md[3] ? Integer(md[3].split(':').first.sub(/([-+])0/, '\1')) : 0
+ Time.at((time - (tz * 3600)).to_i, us)
+ when /^\d{4}-\d{1,2}-\d{1,2}$/
+ require 'date'
+ Date.strptime(string, '%Y-%m-%d')
+ 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 /^[-+]?[1-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
+ else
+ return Integer(string.gsub(/[,_]/, '')) rescue ArgumentError
+ return Float(string.gsub(/[,_]/, '')) rescue ArgumentError
+ @string_cache[string] = true
+ string
+ end
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/set.rb b/ext/psych/lib/psych/set.rb
new file mode 100644
index 0000000000..6793a8ed1b
--- /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/tree_builder.rb b/ext/psych/lib/psych/tree_builder.rb
new file mode 100644
index 0000000000..2b16290b8f
--- /dev/null
+++ b/ext/psych/lib/psych/tree_builder.rb
@@ -0,0 +1,89 @@
+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
+ # Create a new TreeBuilder instance
+ def initialize
+ @stack = []
+ @last = nil
+ end
+
+ # Returns the root node for the built tree
+ def root
+ @stack.first
+ 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
+ @last.implicit_end = implicit_end
+ pop
+ end
+
+ def start_stream encoding
+ push Nodes::Stream.new(encoding)
+ end
+
+ def scalar value, anchor, tag, plain, quoted, style
+ @last.children << Nodes::Scalar.new(value,anchor,tag,plain,quoted,style)
+ end
+
+ def alias anchor
+ @last.children << Nodes::Alias.new(anchor)
+ end
+
+ private
+ def push value
+ @stack.push value
+ @last = value
+ end
+
+ def pop
+ @stack.pop
+ @last = @stack.last
+ end
+ end
+end
diff --git a/ext/psych/lib/psych/visitors.rb b/ext/psych/lib/psych/visitors.rb
new file mode 100644
index 0000000000..10ac4ce270
--- /dev/null
+++ b/ext/psych/lib/psych/visitors.rb
@@ -0,0 +1,5 @@
+require 'psych/visitors/visitor'
+require 'psych/visitors/to_ruby'
+require 'psych/visitors/emitter'
+require 'psych/visitors/yaml_tree'
+require 'psych/visitors/json_tree'
diff --git a/ext/psych/lib/psych/visitors/emitter.rb b/ext/psych/lib/psych/visitors/emitter.rb
new file mode 100644
index 0000000000..0768fbb528
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/emitter.rb
@@ -0,0 +1,41 @@
+module Psych
+ module Visitors
+ class Emitter < Psych::Visitors::Visitor
+ def initialize io
+ @handler = Psych::Emitter.new io
+ 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 0000000000..0440dc778d
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/json_tree.rb
@@ -0,0 +1,37 @@
+module Psych
+ module Visitors
+ class JSONTree < YAMLTree
+ def visit_Symbol o
+ append create_scalar o.to_s
+ end
+
+ def visit_NilClass o
+ scalar = Nodes::Scalar.new(
+ 'null', nil, nil, true, false, Nodes::Scalar::PLAIN)
+ append scalar
+ end
+
+ private
+ def create_document
+ doc = super
+ doc.implicit = true
+ doc.implicit_end = true
+ doc
+ end
+
+ def create_mapping
+ map = super
+ map.style = Nodes::Mapping::FLOW
+ map
+ end
+
+ def create_scalar value, anchor = nil, tag = nil, plain = false, quoted = true, style = Nodes::Scalar::ANY
+ super(value, anchor, tag, false, true, style)
+ end
+
+ def create_sequence anchor = nil, tag = nil, implicit = true, style = Nodes::Sequence::FLOW
+ super
+ 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 0000000000..2790cdeb3d
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/to_ruby.rb
@@ -0,0 +1,252 @@
+require 'psych/scalar_scanner'
+
+module Psych
+ module Visitors
+ ###
+ # This class walks a YAML AST, converting each node to ruby
+ class ToRuby < Psych::Visitors::Visitor
+ def initialize
+ super
+ @st = {}
+ @ss = ScalarScanner.new
+ @domain_types = Psych.domain_types
+ end
+
+ def accept target
+ result = super
+ return result if @domain_types.empty? || !target.tag
+
+ short_name = target.tag.sub(/^!/, '').split('/', 2).last
+ if Psych.domain_types.key? short_name
+ url, block = Psych.domain_types[short_name]
+ return block.call "#{url}:#{short_name}", result
+ end
+
+ result
+ end
+
+ def visit_Psych_Nodes_Scalar o
+ @st[o.anchor] = o.value if o.anchor
+
+ 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', 'tag:yaml.org,2002:str'
+ o.value
+ when "!ruby/object:Complex"
+ Complex(o.value)
+ when "!ruby/object:Rational"
+ Rational(o.value)
+ when "tag:yaml.org,2002:float", "!float"
+ Float(@ss.tokenize(o.value))
+ when "!ruby/regexp"
+ o.value =~ /^\/(.*)\/([mix]*)$/
+ 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
+ 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
+
+ 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 = Psych::Omap.new
+ @st[o.anchor] = map if o.anchor
+ o.children.each { |a|
+ map[accept(a.children.first)] = accept a.children.last
+ }
+ map
+ else
+ list = []
+ @st[o.anchor] = list if o.anchor
+ 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]
+
+ case o.tag
+ when '!str', 'tag:yaml.org,2002:str'
+ members = Hash[*o.children.map { |c| accept c }]
+ string = members.delete 'str'
+ init_with(string, members.map { |k,v| [k.to_s.sub(/^@/, ''),v] }, o)
+ when /^!ruby\/struct:?(.*)?$/
+ klass = resolve_class($1)
+
+ if klass
+ s = klass.allocate
+ @st[o.anchor] = s if o.anchor
+
+ 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 }]
+ 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 }]
+ Complex(h['real'], h['image'])
+
+ when '!ruby/object:Rational'
+ h = Hash[*o.children.map { |c| accept c }]
+ Rational(h['numerator'], h['denominator'])
+
+ when /^!ruby\/object:?(.*)?$/
+ name = $1 || 'Object'
+ obj = revive((resolve_class(name) || Object), o)
+ @st[o.anchor] = obj if o.anchor
+ obj
+ else
+ hash = {}
+ @st[o.anchor] = hash if o.anchor
+
+ o.children.each_slice(2) { |k,v|
+ key = accept(k)
+
+ if key == '<<' && Nodes::Alias === v
+ # FIXME: remove this when "<<" syntax is deprecated
+ if $VERBOSE
+ where = caller.find { |x| x !~ /psych/ }
+ warn where
+ warn "\"<<: *#{v.anchor}\" is no longer supported, please switch to \"*#{v.anchor}\""
+ end
+ return accept(v)
+ else
+ hash[key] = accept(v)
+ end
+
+ }
+ hash
+ 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[o.anchor]
+ end
+
+ private
+ def revive klass, node
+ s = klass.allocate
+ h = Hash[*node.children.map { |c| accept c }]
+ init_with(s, h, node)
+ end
+
+ def init_with o, h, node
+ if o.respond_to?(:init_with)
+ c = Psych::Coder.new(node.tag)
+ c.map = h
+ o.init_with c
+ 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 => ex
+ name = "Struct::#{name}"
+ unless retried
+ retried = true
+ retry
+ end
+ raise ex
+ 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 0000000000..ccd8c3bd55
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/visitor.rb
@@ -0,0 +1,18 @@
+module Psych
+ module Visitors
+ class Visitor
+ def accept target
+ case target
+ when Psych::Nodes::Scalar then visit_Psych_Nodes_Scalar target
+ when Psych::Nodes::Mapping then visit_Psych_Nodes_Mapping target
+ when Psych::Nodes::Sequence then visit_Psych_Nodes_Sequence target
+ when Psych::Nodes::Alias then visit_Psych_Nodes_Alias target
+ when Psych::Nodes::Document then visit_Psych_Nodes_Document target
+ when Psych::Nodes::Stream then visit_Psych_Nodes_Stream target
+ else
+ raise "Can't handle #{target}"
+ end
+ 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 0000000000..e1e89a0d41
--- /dev/null
+++ b/ext/psych/lib/psych/visitors/yaml_tree.rb
@@ -0,0 +1,312 @@
+module Psych
+ module Visitors
+ class YAMLTree < Psych::Visitors::Visitor
+ attr_reader :tree
+
+ def initialize options = {}
+ super()
+ @json = options[:json]
+ @tree = Nodes::Stream.new
+ @stack = []
+ @st = {}
+ @ss = ScalarScanner.new
+
+ @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 << object
+ doc = create_document
+ @stack << doc
+ @tree.children << doc
+ accept object
+ end
+
+ def accept target
+ # return any aliases we find
+ if node = @st[target.object_id]
+ node.anchor = target.object_id.to_s
+ return append Nodes::Alias.new target.object_id.to_s
+ 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 = Nodes::Sequence.new(nil, '!omap', false)
+ register(o, seq)
+
+ @stack.push append seq
+ o.each { |k,v| visit_Hash k => v }
+ @stack.pop
+ 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 = append Nodes::Mapping.new(nil, tag, false)
+ register(o, map)
+
+ @stack.push map
+ dump_ivars(o, map)
+ @stack.pop
+ end
+
+ def visit_Struct o
+ tag = ['!ruby/struct', o.class.name].compact.join(':')
+
+ map = register(o, Nodes::Mapping.new(nil, tag, false))
+
+ @stack.push append map
+
+ o.members.each do |member|
+ map.children << Nodes::Scalar.new("#{member}")
+ accept o[member]
+ end
+
+ dump_ivars(o, map)
+
+ @stack.pop
+ end
+
+ def visit_Exception o
+ tag = ['!ruby/exception', o.class.name].join ':'
+
+ map = append Nodes::Mapping.new(nil, tag, false)
+
+ @stack.push map
+
+ {
+ 'message' => private_iv_get(o, 'mesg'),
+ 'backtrace' => private_iv_get(o, 'backtrace'),
+ }.each do |k,v|
+ next unless v
+ map.children << Nodes::Scalar.new(k)
+ accept v
+ end
+
+ dump_ivars(o, map)
+
+ @stack.pop
+ end
+
+ def visit_Regexp o
+ append Nodes::Scalar.new(o.inspect, nil, '!ruby/regexp', false)
+ end
+
+ def visit_Time o
+ formatted = o.strftime("%Y-%m-%d %H:%M:%S")
+ if o.utc?
+ formatted += ".%06dZ" % [o.usec]
+ else
+ formatted += ".%06d %+.2d:00" % [o.usec, o.gmt_offset / 3600]
+ end
+
+ append Nodes::Scalar.new formatted
+ end
+
+ def visit_Rational o
+ map = append Nodes::Mapping.new(nil, '!ruby/object:Rational', false)
+ [
+ 'denominator', o.denominator.to_s,
+ 'numerator', o.numerator.to_s
+ ].each do |m|
+ map.children << Nodes::Scalar.new(m)
+ end
+ end
+
+ def visit_Complex o
+ map = append Nodes::Mapping.new(nil, '!ruby/object:Complex', false)
+
+ ['real', o.real.to_s, 'image', o.imag.to_s].each do |m|
+ map.children << Nodes::Scalar.new(m)
+ end
+ end
+
+ def visit_Integer o
+ append Nodes::Scalar.new o.to_s
+ end
+ alias :visit_TrueClass :visit_Integer
+ alias :visit_FalseClass :visit_Integer
+ alias :visit_Date :visit_Integer
+
+ def visit_Float o
+ if o.nan?
+ append Nodes::Scalar.new '.nan'
+ elsif o.infinite?
+ append Nodes::Scalar.new(o.infinite? > 0 ? '.inf' : '-.inf')
+ else
+ append Nodes::Scalar.new o.to_s
+ end
+ end
+
+ def visit_String o
+ plain = false
+ quote = false
+
+ if o.index("\x00") || o.count("^ -~\t\r\n").fdiv(o.length) > 0.3
+ str = [o].pack('m').chomp
+ tag = '!binary'
+ else
+ str = o
+ tag = nil
+ quote = !(String === @ss.tokenize(o))
+ plain = !quote
+ end
+
+ ivars = o.respond_to?(:to_yaml_properties) ?
+ o.to_yaml_properties :
+ o.instance_variables
+
+ scalar = create_scalar str, nil, tag, plain, quote
+
+ if ivars.empty?
+ append scalar
+ else
+ mapping = append Nodes::Mapping.new(nil, '!str', false)
+
+ mapping.children << Nodes::Scalar.new('str')
+ mapping.children << scalar
+
+ @stack.push mapping
+ dump_ivars o, mapping
+ @stack.pop
+ end
+ end
+
+ def visit_Class o
+ raise TypeError, "can't dump anonymous class #{o.class}"
+ end
+
+ def visit_Range o
+ @stack.push append Nodes::Mapping.new(nil, '!ruby/range', false)
+ ['begin', o.begin, 'end', o.end, 'excl', o.exclude_end?].each do |m|
+ accept m
+ end
+ @stack.pop
+ end
+
+ def visit_Hash o
+ @stack.push append register(o, create_mapping)
+
+ o.each do |k,v|
+ accept k
+ accept v
+ end
+
+ @stack.pop
+ end
+
+ def visit_Psych_Set o
+ @stack.push append register(o, Nodes::Mapping.new(nil, '!set', false))
+
+ o.each do |k,v|
+ accept k
+ accept v
+ end
+
+ @stack.pop
+ end
+
+ def visit_Array o
+ @stack.push append register(o, create_sequence)
+ o.each { |c| accept c }
+ @stack.pop
+ end
+
+ def visit_NilClass o
+ append Nodes::Scalar.new('', nil, 'tag:yaml.org,2002:null', false)
+ end
+
+ def visit_Symbol o
+ append create_scalar ":#{o}"
+ end
+
+ private
+ def append o
+ @stack.last.children << o
+ o
+ 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
+ append create_scalar(c.scalar, nil, c.tag, c.tag.nil?)
+ when :seq
+ @stack.push append create_sequence(nil, c.tag, c.tag.nil?)
+ c.seq.each do |thing|
+ accept thing
+ end
+ @stack.pop
+ when :map
+ map = append Nodes::Mapping.new(nil, c.tag, c.implicit, c.style)
+ @stack.push map
+ c.map.each do |k,v|
+ map.children << create_scalar(k)
+ accept v
+ end
+ @stack.pop
+ end
+ end
+
+ def dump_ivars target, map
+ ivars = target.respond_to?(:to_yaml_properties) ?
+ target.to_yaml_properties :
+ target.instance_variables
+
+ ivars.each do |iv|
+ map.children << create_scalar("#{iv.to_s.sub(/^@/, '')}")
+ accept target.instance_variable_get(iv)
+ end
+ end
+
+ def create_document
+ Nodes::Document.new
+ end
+
+ def create_mapping
+ Nodes::Mapping.new
+ end
+
+ def create_scalar value, anchor = nil, tag = nil, plain = true, quoted = false, style = Nodes::Scalar::ANY
+ Nodes::Scalar.new(value, anchor, tag, plain, quoted, style)
+ end
+
+ def create_sequence anchor = nil, tag = nil, implicit = true, style = Nodes::Sequence::BLOCK
+ Nodes::Sequence.new(anchor, tag, implicit, style)
+ end
+ end
+ end
+end