diff options
-rw-r--r-- | lib/psych.rb | 2 | ||||
-rw-r--r-- | lib/psych/handler.rb | 6 | ||||
-rw-r--r-- | lib/psych/json/tree_builder.rb | 2 | ||||
-rw-r--r-- | lib/psych/stream.rb | 53 | ||||
-rw-r--r-- | lib/psych/tree_builder.rb | 2 | ||||
-rw-r--r-- | lib/psych/visitors/visitor.rb | 9 | ||||
-rw-r--r-- | lib/psych/visitors/yaml_tree.rb | 28 | ||||
-rw-r--r-- | test/psych/test_stream.rb | 49 |
8 files changed, 141 insertions, 10 deletions
diff --git a/lib/psych.rb b/lib/psych.rb index a73b892..4b529c0 100644 --- a/lib/psych.rb +++ b/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/lib/psych/handler.rb b/lib/psych/handler.rb index bfc62d7..a2aa6bb 100644 --- a/lib/psych/handler.rb +++ b/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/lib/psych/json/tree_builder.rb b/lib/psych/json/tree_builder.rb index 91d7e6c..d0a7617 100644 --- a/lib/psych/json/tree_builder.rb +++ b/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/lib/psych/stream.rb b/lib/psych/stream.rb new file mode 100644 index 0000000..ac231aa --- /dev/null +++ b/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/lib/psych/tree_builder.rb b/lib/psych/tree_builder.rb index 54d87a0..8b4e972 100644 --- a/lib/psych/tree_builder.rb +++ b/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/lib/psych/visitors/visitor.rb b/lib/psych/visitors/visitor.rb index ccd8c3b..3471c43 100644 --- a/lib/psych/visitors/visitor.rb +++ b/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/lib/psych/visitors/yaml_tree.rb b/lib/psych/visitors/yaml_tree.rb index 4adb8d4..93ccc58 100644 --- a/lib/psych/visitors/yaml_tree.rb +++ b/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 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 - @emitter.end_stream + finish unless finished? end - def << object + 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 0000000..7e2f996 --- /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 |