summaryrefslogtreecommitdiff
path: root/ext/psych/lib/psych/visitors
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/lib/psych/visitors
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/lib/psych/visitors')
-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
5 files changed, 660 insertions, 0 deletions
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