summaryrefslogtreecommitdiff
path: root/lib/yaml/emitter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/yaml/emitter.rb')
-rw-r--r--lib/yaml/emitter.rb330
1 files changed, 330 insertions, 0 deletions
diff --git a/lib/yaml/emitter.rb b/lib/yaml/emitter.rb
new file mode 100644
index 0000000000..4b8541c2c3
--- /dev/null
+++ b/lib/yaml/emitter.rb
@@ -0,0 +1,330 @@
+#
+# Output classes and methods
+#
+
+require 'yaml/constants'
+require 'yaml/encoding'
+require 'yaml/error'
+
+module YAML
+
+ #
+ # Emit a set of values
+ #
+
+ class Emitter
+ attr_accessor :options
+ def initialize( opts )
+ opts = {} if opts.class != Hash
+ @options = YAML::DEFAULTS.dup.update( opts )
+ @headless = 0
+ @seq_map = false
+ @anchors = {}
+ @anchor_extras = {}
+ @active_anchors = []
+ @level = -1
+ self.clear
+ end
+
+ def clear
+ @buffer = []
+ end
+
+ #
+ # Version string
+ #
+ def version_s
+ " %YAML:#{@options[:Version]}" if @options[:UseVersion]
+ end
+
+ #
+ # Header
+ #
+ def header
+ if @headless.nonzero?
+ ""
+ else
+ "---#{version_s} "
+ end
+ end
+
+ #
+ # Emit binary data
+ #
+ def binary_base64( value )
+ self << "!binary "
+ self.node_text( [value].pack("m"), '|' )
+ end
+
+ #
+ # Emit plain, normal flowing text
+ #
+ def node_text( value, block = '>' )
+ valx = value.dup
+ if @options[:UseBlock]
+ block = '|'
+ elsif not @options[:UseFold] and valx =~ /\n[ \t]/ and not valx =~ /#{YAML::ESCAPE_CHAR}/
+ block = '|'
+ end
+ str = block.dup
+ if valx =~ /\n\Z\n/
+ str << "+"
+ elsif valx =~ /\Z\n/
+ else
+ str << "-"
+ end
+ if valx =~ /#{YAML::ESCAPE_CHAR}/
+ valx = YAML::escape( valx )
+ end
+ if valx =~ /\A[ \t#]/
+ str << @options[:Indent].to_s
+ end
+ if block == '>'
+ valx = fold( valx )
+ end
+ self << str + indent_text( valx ) + "\n"
+ end
+
+ #
+ # Emit a simple, unqouted string
+ #
+ def simple( value )
+ self << value.to_s
+ end
+
+ #
+ # Emit double-quoted string
+ #
+ def double( value )
+ "\"#{YAML.escape( value )}\""
+ end
+
+ #
+ # Emit single-quoted string
+ #
+ def single( value )
+ "'#{value}'"
+ end
+
+ #
+ # Write a text block with the current indent
+ #
+ def indent_text( text )
+ return "" if text.to_s.empty?
+ spacing = " " * ( @level * @options[:Indent] )
+ return "\n" + text.gsub( /^([^\n])/, "#{spacing}\\1" )
+ end
+
+ #
+ # Write a current indent
+ #
+ def indent
+ #p [ self.id, @level, :INDENT ]
+ return " " * ( @level * @options[:Indent] )
+ end
+
+ #
+ # Add indent to the buffer
+ #
+ def indent!
+ self << indent
+ end
+
+ #
+ # Folding paragraphs within a column
+ #
+ def fold( value )
+ value.gsub!( /\A\n+/, '' )
+ folded = $&.to_s
+ width = (0..@options[:BestWidth])
+ while not value.empty?
+ last = value.index( /(\n+)/ )
+ chop_s = false
+ if width.include?( last )
+ last += $1.length - 1
+ elsif width.include?( value.length )
+ last = value.length
+ else
+ last = value.rindex( /[ \t]/, @options[:BestWidth] )
+ chop_s = true
+ end
+ folded += value.slice!( 0, width.include?( last ) ? last + 1 : @options[:BestWidth] )
+ folded.chop! if chop_s
+ folded += "\n" unless value.empty?
+ end
+ folded
+ end
+
+ #
+ # Quick mapping
+ #
+ def map( type, &e )
+ val = Mapping.new
+ e.call( val )
+ self << "#{type} " if type.length.nonzero?
+
+ #
+ # Empty hashes
+ #
+ if val.length.zero?
+ self << "{}"
+ else
+ if @buffer.length == 1 and @options[:UseHeader] == false and type.length.zero?
+ @headless = 1
+ end
+
+ defkey = @options.delete( :DefaultKey )
+ if defkey
+ seq_map_shortcut
+ self << "= : "
+ defkey.to_yaml( :Emitter => self )
+ end
+
+ #
+ # Emit the key and value
+ #
+ val.each { |v|
+ seq_map_shortcut
+ if v[0].is_complex_yaml?
+ self << "? "
+ end
+ v[0].to_yaml( :Emitter => self )
+ if v[0].is_complex_yaml?
+ self << "\n"
+ indent!
+ end
+ self << ": "
+ v[1].to_yaml( :Emitter => self )
+ }
+ end
+ end
+
+ def seq_map_shortcut
+ if @seq_map
+ @anchor_extras[@buffer.length - 1] = "\n" + indent
+ @seq_map = false
+ else
+ self << "\n"
+ indent!
+ end
+ end
+
+ #
+ # Quick sequence
+ #
+ def seq( type, &e )
+ val = Sequence.new
+ e.call( val )
+ self << "#{type} " if type.length.nonzero?
+
+ #
+ # Empty arrays
+ #
+ if val.length.zero?
+ self << "[]"
+ else
+ if @buffer.length == 1 and @options[:UseHeader] == false and type.length.zero?
+ @headless = 1
+ end
+ #
+ # Emit the key and value
+ #
+ val.each { |v|
+ self << "\n"
+ indent!
+ self << "- "
+ @seq_map = true if v.class == Hash
+ v.to_yaml( :Emitter => self )
+ }
+ end
+ end
+
+ #
+ # Concatenate to the buffer
+ #
+ def <<( str )
+ #p [ self.id, @level, str ]
+ @buffer.last << str
+ end
+
+ #
+ # Monitor objects and allow references
+ #
+ def start_object( oid )
+ @level += 1
+ @buffer.push( "" )
+ #p [ self.id, @level, :OPEN ]
+ idx = nil
+ if oid
+ if @anchors.has_key?( oid )
+ idx = @active_anchors.index( oid )
+ unless idx
+ idx = @active_anchors.length
+ af_str = "&#{@options[:AnchorFormat]} " % [ idx + 1 ]
+ af_str += @anchor_extras[ @anchors[ oid ] ].to_s
+ @buffer[ @anchors[ oid ] ][0,0] = af_str
+ @headless = 0 if @anchors[ oid ].zero?
+ end
+ idx += 1
+ @active_anchors.push( oid )
+ else
+ @anchors[ oid ] = @buffer.length - 1
+ end
+ end
+ return idx
+ end
+
+ #
+ # Output method
+ #
+ def end_object
+ @level -= 1
+ @buffer.push( "" )
+ #p [ self.id, @level, :END ]
+ if @level < 0
+ YAML.internal_to_utf( header + @buffer.to_s[@headless..-1], @options[:Encoding] )
+ end
+ end
+ end
+
+ #
+ # Emitter helper classes
+ #
+ class Mapping < Array
+ def add( k, v )
+ push [k, v]
+ end
+ end
+
+ class Sequence < Array
+ def add( v )
+ push v
+ end
+ end
+
+ #
+ # Allocate an Emitter if needed
+ #
+ def YAML.quick_emit( oid, opts = {}, &e )
+ old_opt = nil
+ if opts[:Emitter].is_a? YAML::Emitter
+ out = opts.delete( :Emitter )
+ old_opt = out.options.dup
+ out.options.update( opts )
+ else
+ out = YAML::Emitter.new( opts )
+ end
+ aidx = out.start_object( oid )
+ if aidx
+ out.simple( "*#{out.options[:AnchorFormat]} " % [ aidx ] )
+ else
+ e.call( out )
+ end
+ if old_opt.is_a? Hash
+ out.options = old_opt
+ end
+ out.end_object
+ end
+
+end
+