From 5256d1026505b01f75669c1087c74c04dacd05ba Mon Sep 17 00:00:00 2001 From: tenderlove Date: Thu, 20 May 2010 04:03:47 +0000 Subject: [PATCH] * ext/psych/lib/psych/stream.rb: adding YAML streaming API for infinite length streams. * ext/psych/lib/psych.rb: refactoring for streaming API * ext/psych/lib/psych/{handler, stream, tree_builder}.rb: ditto git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@27912 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 9 ++++ ext/psych/lib/psych.rb | 2 + ext/psych/lib/psych/handler.rb | 6 +++ ext/psych/lib/psych/json/tree_builder.rb | 2 +- ext/psych/lib/psych/stream.rb | 53 +++++++++++++++++++++++ ext/psych/lib/psych/tree_builder.rb | 2 +- ext/psych/lib/psych/visitors/visitor.rb | 9 ++++ ext/psych/lib/psych/visitors/yaml_tree.rb | 30 +++++++++---- test/psych/test_stream.rb | 49 +++++++++++++++++++++ 9 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 ext/psych/lib/psych/stream.rb create mode 100644 test/psych/test_stream.rb diff --git a/ChangeLog b/ChangeLog index a3fc88a3dc..7df46b0b1a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +Thu May 20 12:59:49 2010 Aaron Patterson + + * ext/psych/lib/psych/stream.rb: adding YAML streaming API for + infinite length streams. + + * ext/psych/lib/psych.rb: refactoring for streaming API + + * ext/psych/lib/psych/{handler, stream, tree_builder}.rb: ditto + Thu May 20 02:12:20 2010 Aaron Patterson * ext/psych/emitter.c: output strings are automatically transcoded diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb index a73b89212c..4b529c0904 100644 --- a/ext/psych/lib/psych.rb +++ b/ext/psych/lib/psych.rb @@ -97,6 +97,8 @@ module Psych class Exception < RuntimeError end + autoload :Stream, 'psych/stream' + ### # Load +yaml+ in to a Ruby data structure. If multiple documents are # provided, the object contained in the first document will be returned. diff --git a/ext/psych/lib/psych/handler.rb b/ext/psych/lib/psych/handler.rb index bfc62d701f..a2aa6bb178 100644 --- a/ext/psych/lib/psych/handler.rb +++ b/ext/psych/lib/psych/handler.rb @@ -211,5 +211,11 @@ module Psych # 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/json/tree_builder.rb b/ext/psych/lib/psych/json/tree_builder.rb index 91d7e6cad6..d0a76177bf 100644 --- a/ext/psych/lib/psych/json/tree_builder.rb +++ b/ext/psych/lib/psych/json/tree_builder.rb @@ -8,7 +8,7 @@ module Psych super(version, tag_directives, true) end - def end_document implicit_end + def end_document implicit_end = !streaming? super(true) end diff --git a/ext/psych/lib/psych/stream.rb b/ext/psych/lib/psych/stream.rb new file mode 100644 index 0000000000..ac231aafdc --- /dev/null +++ b/ext/psych/lib/psych/stream.rb @@ -0,0 +1,53 @@ +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 + + ### + # Create a new streaming emitter. Emitter will print to +io+. See + # Psych::Stream for an example. + def initialize io + super({}, 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/tree_builder.rb b/ext/psych/lib/psych/tree_builder.rb index 54d87a0b61..8b4e972314 100644 --- a/ext/psych/lib/psych/tree_builder.rb +++ b/ext/psych/lib/psych/tree_builder.rb @@ -57,7 +57,7 @@ module Psych # and +implicit+ styling. # # See Psych::Handler#start_document - def end_document implicit_end + def end_document implicit_end = !streaming? @last.implicit_end = implicit_end pop end diff --git a/ext/psych/lib/psych/visitors/visitor.rb b/ext/psych/lib/psych/visitors/visitor.rb index ccd8c3bd55..3471c43dfa 100644 --- a/ext/psych/lib/psych/visitors/visitor.rb +++ b/ext/psych/lib/psych/visitors/visitor.rb @@ -1,6 +1,15 @@ module Psych module Visitors class Visitor + attr_reader :started, :finished + alias :finished? :finished + alias :started? :started + + def initialize + @started = false + @finished = false + end + def accept target case target when Psych::Nodes::Scalar then visit_Psych_Nodes_Scalar target diff --git a/ext/psych/lib/psych/visitors/yaml_tree.rb b/ext/psych/lib/psych/visitors/yaml_tree.rb index 4adb8d4c66..93ccc5832b 100644 --- a/ext/psych/lib/psych/visitors/yaml_tree.rb +++ b/ext/psych/lib/psych/visitors/yaml_tree.rb @@ -10,11 +10,9 @@ module Psych class YAMLTree < Psych::Visitors::Visitor def initialize options = {}, emitter = Psych::TreeBuilder.new super() - @emitter = emitter - @st = {} - @ss = ScalarScanner.new - - @emitter.start_stream Psych::Nodes::Stream::UTF8 + @emitter = emitter + @st = {} + @ss = ScalarScanner.new @dispatch_cache = Hash.new do |h,klass| method = "visit_#{(klass.name || '').split('::').join('_')}" @@ -27,15 +25,29 @@ module Psych end end - def tree - @emitter.end_stream + def start encoding = Nodes::Stream::UTF8 + @emitter.start_stream(encoding).tap do + @started = true + end end - def << object + def finish + @emitter.end_stream.tap do + @finished = true + end + end + + def tree + finish unless finished? + end + + def push object + start unless started? @emitter.start_document [], [], false accept object - @emitter.end_document true + @emitter.end_document end + alias :<< :push def accept target # return any aliases we find diff --git a/test/psych/test_stream.rb b/test/psych/test_stream.rb new file mode 100644 index 0000000000..7e2f996708 --- /dev/null +++ b/test/psych/test_stream.rb @@ -0,0 +1,49 @@ +require_relative 'helper' + +module Psych + class TestStream < TestCase + def test_explicit_documents + io = StringIO.new + stream = Psych::Stream.new(io) + stream.start + stream.push({ 'foo' => 'bar' }) + + assert !stream.finished?, 'stream not finished' + stream.finish + assert stream.finished?, 'stream finished' + + assert_match(/^---/, io.string) + assert_match(/\.\.\.$/, io.string) + end + + def test_start_takes_block + io = StringIO.new + stream = Psych::Stream.new(io) + stream.start do |emitter| + emitter.push({ 'foo' => 'bar' }) + end + + assert stream.finished?, 'stream finished' + assert_match(/^---/, io.string) + assert_match(/\.\.\.$/, io.string) + end + + def test_no_backreferences + io = StringIO.new + stream = Psych::Stream.new(io) + stream.start do |emitter| + x = { 'foo' => 'bar' } + emitter.push x + emitter.push x + end + + assert stream.finished?, 'stream finished' + assert_match(/^---/, io.string) + assert_match(/\.\.\.$/, io.string) + assert_equal 2, io.string.scan('---').length + assert_equal 2, io.string.scan('...').length + assert_equal 2, io.string.scan('foo').length + assert_equal 2, io.string.scan('bar').length + end + end +end