class Ya2YAML

Author

Akira FUNAI

Copyright

Copyright © 2006-2010 Akira FUNAI

License

MIT License

Public Class Methods

new(opts = {}) click to toggle source
# File lib/ya2yaml.rb, line 8
def initialize(opts = {})
  options = opts.dup
  options[:indent_size] = 2          if options[:indent_size].to_i <= 0
  options[:minimum_block_length] = 0 if options[:minimum_block_length].to_i <= 0
  options.update(
    {
      :printable_with_syck  => true,
      :escape_b_specific    => true,
      :escape_as_utf8       => true,
    }
  ) if options[:syck_compatible]

  @options = options
end

Public Instance Methods

_ya2yaml(obj) click to toggle source
# File lib/ya2yaml.rb, line 23
def _ya2yaml(obj)
  raise 'set $KCODE to "UTF8".' if (RUBY_VERSION < '1.9.0') && ($KCODE != 'UTF8')
  '--- ' + emit(obj, 1) + "\n"
rescue SystemStackError
  raise ArgumentError, "ya2yaml can't handle circular references"
end

Private Instance Methods

emit(obj, level) click to toggle source
# File lib/ya2yaml.rb, line 32
def emit(obj, level)
  case obj
    when Array
      if (obj.length == 0)
        '[]'
      else
        indent = "\n" + s_indent(level - 1)
        obj.collect {|o|
          indent + '- ' + emit(o, level + 1)
        }.join('')
      end
    when Hash
      if (obj.length == 0)
        '{}'
      else
        indent = "\n" + s_indent(level - 1)
        hash_order = @options[:hash_order]
        if (hash_order && level == 1)
          hash_keys = obj.keys.sort {|x, y|
            x_order = hash_order.index(x) ? hash_order.index(x) : Float::MAX
            y_order = hash_order.index(y) ? hash_order.index(y) : Float::MAX
            o = (x_order <=> y_order)
            (o != 0) ? o : (x.to_s <=> y.to_s)
          }
        elsif @options[:preserve_order]
          hash_keys = obj.keys
        else
          hash_keys = obj.keys.sort {|x, y| x.to_s <=> y.to_s }
        end
        hash_keys.collect {|k|
          key = emit(k, level + 1)
          if (
            is_one_plain_line?(key) ||
            key =~ /\A(#{REX_BOOL}|#{REX_FLOAT}|#{REX_INT}|#{REX_NULL})\z/x
          )
            indent + key + ': ' + emit(obj[k], level + 1)
          else
            indent + '? ' + key +
            indent + ': ' + emit(obj[k], level + 1)
          end
        }.join('')
      end
    when NilClass
      '~'
    when String
      emit_string(obj, level)
    when TrueClass, FalseClass
      obj.to_s
    when Fixnum, Bignum, Float
      obj.to_s
    when Date
      obj.to_s
    when Time
      offset = obj.gmtoff
      off_hm = sprintf(
        '%+.2d:%.2d',
        (offset / 3600.0).to_i,
        (offset % 3600.0) / 60
      )
      u_sec = (obj.usec != 0) ? sprintf(".%.6d", obj.usec) : ''
      obj.strftime("%Y-%m-%d %H:%M:%S#{u_sec} #{off_hm}")
    when Symbol
      '!ruby/symbol ' + emit_string(obj.to_s, level)
    when Range
      '!ruby/range ' + obj.to_s
    when Regexp
      '!ruby/regexp ' + obj.inspect
    else
      case
        when obj.is_a?(Struct)
          struct_members = {}
          obj.each_pair{|k, v| struct_members[k.to_s] = v }
          '!ruby/struct:' + obj.class.to_s.sub(/^(Struct::(.+)|.*)$/, '\2') + ' ' +
          emit(struct_members, level + 1)
        else
          # serialized as a generic object
          object_members = {}
          obj.instance_variables.each{|k, v|
            object_members[k.to_s.sub(/^@/, '')] = obj.instance_variable_get(k)
          }
          '!ruby/object:' + obj.class.to_s + ' ' +
          emit(object_members, level + 1)
      end
  end
end
emit_base64_binary(str, level) click to toggle source
# File lib/ya2yaml.rb, line 181
def emit_base64_binary(str, level)
  indent = "\n" + s_indent(level)
  base64 = [str].pack('m')
  '!binary |' + indent + base64.gsub(/\n(?!\z)/, indent)
end
emit_block_string(str, level) click to toggle source
# File lib/ya2yaml.rb, line 141
def emit_block_string(str, level)
  str = normalize_line_break(str)

  indent = s_indent(level)
  indentation_indicator = (str =~ /\A /) ? indent.size.to_s : ''
  str =~ /(#{REX_NORMAL_LB}*)\z/
  chomping_indicator = case $1.length
    when 0
      '-'
    when 1
      ''
    else
      '+'
  end

  str.chomp!
  str.gsub!(/#{REX_NORMAL_LB}/) {
    $1 + indent
  }
  '|' + indentation_indicator + chomping_indicator + "\n" + indent + str
end
emit_quoted_string(str, level) click to toggle source
# File lib/ya2yaml.rb, line 163
def emit_quoted_string(str, level)
  str = yaml_escape(normalize_line_break(str))
  if (str.length < @options[:minimum_block_length])
    str.gsub!(/#{REX_NORMAL_LB}/) { ESCAPE_SEQ_LB[$1] }
  else
    str.gsub!(/#{REX_NORMAL_LB}$/) { ESCAPE_SEQ_LB[$1] }
    str.gsub!(/(#{REX_NORMAL_LB}+)(.)/) {
      trail_c = $3
      $1 + trail_c.sub(/([\t ])/) { ESCAPE_SEQ_WS[$1] }
    }
    indent = s_indent(level)
    str.gsub!(/#{REX_NORMAL_LB}/) {
      ESCAPE_SEQ_LB[$1] + "\\\n" + indent
    }
  end
  '"' + str + '"'
end
emit_simple_string(str, level) click to toggle source
# File lib/ya2yaml.rb, line 137
def emit_simple_string(str, level)
  str
end
emit_string(str, level) click to toggle source
# File lib/ya2yaml.rb, line 118
def emit_string(str, level)
  (is_string, is_printable, is_one_line, is_one_plain_line) = string_type(str)
  if is_string
    if is_printable
      if is_one_plain_line
        emit_simple_string(str, level)
      else
        (is_one_line || str.length < @options[:minimum_block_length]) ?
          emit_quoted_string(str, level) :
          emit_block_string(str, level)
      end
    else
      emit_quoted_string(str, level)
    end
  else
    emit_base64_binary(str, level)
  end
end
is_one_line?(str) click to toggle source
# File lib/ya2yaml.rb, line 224
def is_one_line?(str)
  str !~ /#{REX_ANY_LB}(?!\z)/
end
is_one_plain_line?(str) click to toggle source
# File lib/ya2yaml.rb, line 228
def is_one_plain_line?(str)
  # YAML 1.1 / 4.6.11.
  str !~ /^([\-\?:,\[\]\{\}\#&\*!\|>'"%@`\s]|---|\.\.\.)/    &&
  str !~ /[:\#\s\[\]\{\},]/                                  &&
  str !~ /#{REX_ANY_LB}/                                     &&
  str !~ /^(#{REX_BOOL}|#{REX_FLOAT}|#{REX_INT}|#{REX_MERGE}
    |#{REX_NULL}|#{REX_TIMESTAMP}|#{REX_VALUE})$/x
end
is_printable?(ucs_code) click to toggle source
# File lib/ya2yaml.rb, line 208
def is_printable?(ucs_code)
  # YAML 1.1 / 4.1.1.
  (
    [0x09, 0x0a, 0x0d, 0x85].include?(ucs_code)   ||
    (ucs_code <=     0x7e && ucs_code >=    0x20) ||
    (ucs_code <=   0xd7ff && ucs_code >=    0xa0) ||
    (ucs_code <=   0xfffd && ucs_code >=  0xe000) ||
    (ucs_code <= 0x10ffff && ucs_code >= 0x10000)
  ) &&
  !(
    # treat LS/PS as non-printable characters
    @options[:escape_b_specific] &&
    (ucs_code == 0x2028 || ucs_code == 0x2029)
  )
end
normalize_line_break(str) click to toggle source
# File lib/ya2yaml.rb, line 242
def normalize_line_break(str)
  # YAML 1.1 / 4.1.4.
  str.gsub(/(#{REX_CRLF}|#{REX_CR}|#{REX_NEL})/, "\n")
end
s_indent(level) click to toggle source
# File lib/ya2yaml.rb, line 237
def s_indent(level)
  # YAML 1.1 / 4.2.2.
  ' ' * (level * @options[:indent_size])
end
string_type(str) click to toggle source
# File lib/ya2yaml.rb, line 187
def string_type(str)
  if str.respond_to?(:encoding) && (!str.valid_encoding? || str.encoding == Encoding::ASCII_8BIT)
    return false, false, false, false
  end
  (ucs_codes = str.unpack('U*')) rescue (
    # ArgumentError -> binary data
    return false, false, false, false
  )
  if (
    @options[:printable_with_syck] &&
    str =~ /\A#{REX_ANY_LB}* | #{REX_ANY_LB}*\z|#{REX_ANY_LB}{2}\z/
  )
    # detour Syck bug
    return true, false, nil, false
  end
  ucs_codes.each {|ucs_code|
    return true, false, nil, false unless is_printable?(ucs_code)
  }
  return true, true, is_one_line?(str), is_one_plain_line?(str)
end
yaml_escape(str) click to toggle source
# File lib/ya2yaml.rb, line 247
def yaml_escape(str)
  # YAML 1.1 / 4.1.6.
  str.gsub(/[^a-zA-Z0-9]/u) {|c|
    ucs_code, = (c.unpack('U') rescue [??])
    case
      when ESCAPE_SEQ[c]
        ESCAPE_SEQ[c]
      when is_printable?(ucs_code)
        c
      when @options[:escape_as_utf8]
        c.respond_to?(:bytes) ?
          c.bytes.collect {|b| '\x%.2x' % b }.join :
          '\x' + c.unpack('H2' * c.size).join('\x')
      when ucs_code == 0x2028 || ucs_code == 0x2029
        ESCAPE_SEQ_LB[c]
      when ucs_code <= 0x7f
        sprintf('\x%.2x', ucs_code)
      when ucs_code <= 0xffff
        sprintf('\u%.4x', ucs_code)
      else
        sprintf('\U%.8x', ucs_code)
    end
  }
end