diff options
author | nobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-03-30 10:46:06 +0000 |
---|---|---|
committer | nobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-03-30 10:46:06 +0000 |
commit | f1330fd12403ef4d664df7619d6ce312d525721a (patch) | |
tree | cfafd35d3d416b8529003d43db2ace98af20fdb1 /ext/psych/lib/psych/visitors | |
parent | 38af94c6abaaa20aeaa1ffafbe3f6ff962f63eaa (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.rb | 41 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors/json_tree.rb | 37 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors/to_ruby.rb | 252 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors/visitor.rb | 18 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors/yaml_tree.rb | 312 |
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 |