class Peml::Loader

Constants

ARRAY_ELEMENT
COMMAND_KEY
COMMENT_LINE
SCOPE_PATTERN
SLUG_BLACKLIST
START_KEY
WHITESPACE_PATTERN

~ Constants .….….….….….….….….….….….….….….……

Public Class Methods

new(options = {}) click to toggle source

# File lib/peml/loader.rb, line 21
def initialize(options = {})
  @data = @scope = {}

  @stack = []
  @stack_scope = nil

  @buffer_scope = @buffer_key = nil
  @buffer_string = ''
  @quote_string = ''

  @is_skipping = false
  @is_quoted = false
  @done_parsing = false
  @depth = 0

  @default_options = {
      comments: false
  }.merge(options)
end

Public Instance Methods

flush_buffer!() click to toggle source

# File lib/peml/loader.rb, line 280
def flush_buffer!
  result = @buffer_string.dup
  # puts "    flushed content = #{result.inspect}"
  @buffer_string = ''
  @buffer_key = nil
  return result
end
flush_buffer_into(key, options = {}) click to toggle source

# File lib/peml/loader.rb, line 290
def flush_buffer_into(key, options = {})
  existing_buffer_key = @buffer_key
  value = self.flush_buffer!

  if options[:replace]
    if @is_quoted
      @buffer_string = value
    else
      value = self.format_value(value, :replace).sub(/^\s*/, '')
      @buffer_string = value.match(/\s*\Z/)[0]
    end
    @buffer_key = existing_buffer_key
  else
    value = self.format_value(value, :append)
  end
  if !@is_quoted
    value = value.sub(/\s*\Z/, '')
  end
  # puts "    flushed content = #{value.inspect}"

  if key.class == Array
    key[key.length - 1] = '' if options[:replace]
    key[key.length - 1] += value

  else
    key_bits = key.split('.')
    @buffer_scope = @scope

    key_bits[0...-1].each do |bit|
      @buffer_scope[bit] = {} if @buffer_scope[bit].class == String # reset
      @buffer_scope = @buffer_scope[bit] ||= {}
    end

    @buffer_scope[key_bits.last] = '' if options[:replace]
    @buffer_scope[key_bits.last] += value
  end
end
format_value(value, type) click to toggle source

type can be either :replace or :append. If it's :replace, then the string is assumed to be the first line of a value, and no escaping takes place. If we're appending to a multi-line string, escape special punctuation by prepending the line with a backslash. (:, [, {, *, ) surrounding the first token of any line.

# File lib/peml/loader.rb, line 336
def format_value(value, type)
  # backslash-escaped leading characters have been removed in favor of
  # quoted values.
  #
  # if type == :append
  #  value.gsub!(/^(\s*)\\/, '\1')
  # end

  # puts "    after formatting = #{value.inspect}"
  value
end
increment_array_element(key) click to toggle source

# File lib/peml/loader.rb, line 255
def increment_array_element(key)
  # Special handling for arrays. If this is the start of the array, remember
  # which key was encountered first. If this is a duplicate encounter of
  # that key, start a new object.

  if @stack_scope && @stack_scope[:array]
    # If we're within a simple array, ignore
    @stack_scope[:array_type] ||= :complex
    return if @stack_scope[:array_type] == :simple

    # array_first_key may be either another key, or nil
    if @stack_scope[:array_first_key] == nil || @stack_scope[:array_first_key] == key
      @stack_scope[:array] << (@scope = {})
    end
    if (@stack_scope[:flags].match(/\+/))
      @scope[:type] = key
      # key = 'content'
    else
      @stack_scope[:array_first_key] ||= key
    end
  end
end
load(stream, options = {}) click to toggle source

# File lib/peml/loader.rb, line 43
def load(stream, options = {})
  @options = @default_options.merge(options)

  stream.each_line do |line|
    return @data if @done_parsing

    if @is_quoted
      if line.match(/^#{@quote_string}(?:\n|\r|$)/)
        # end quote
        self.parse_command_key('end')
        @is_quoted = false
        @quote_string = ''
      else
        self.parse_text(line)
      end

    elsif match = line.match(COMMENT_LINE)
      # just skip it!

    elsif match = line.match(COMMAND_KEY)
      self.parse_command_key(match[1].downcase)

    elsif @skipping
      # should we just ignore this text, instead of parsing it?
      self.parse_text(line)

    elsif (match = line.match(START_KEY)) &&
        (!@stack_scope || @stack_scope[:array_type] != :simple)
      self.parse_start_key(match[1], match[3], match[5] || '')

    elsif (match = line.match(ARRAY_ELEMENT)) && @stack_scope &&
      @stack_scope[:array] && (@stack_scope[:array_type] != :complex ) &&
      !@stack_scope[:flags].match(/\+/)
      self.parse_array_element(match[1])

    elsif match = line.match(SCOPE_PATTERN)
      self.parse_scope(match[1], match[2], match[3])

    else
      # just plain text
      self.parse_text(line)
    end
  end

  # Treat all keys as multi-line
  self.parse_command_key('end')

  self.flush_buffer!
  return @data
end
parse_array_element(value) click to toggle source

# File lib/peml/loader.rb, line 119
def parse_array_element(value)
  # Treat all keys as multi-line
  self.parse_command_key('end')

  self.flush_buffer!

  @stack_scope[:array_type] ||= :simple

  @stack_scope[:array] << ''
  @buffer_key = @stack_scope[:array]
  @buffer_string = value
  self.flush_buffer_into(@stack_scope[:array], replace: true)
end
parse_command_key(command) click to toggle source

# File lib/peml/loader.rb, line 135
def parse_command_key(command)
  if @is_skipping && !%w(endskip ignore).include?(command)
    return self.flush_buffer!
  end

  case command
    when "end"
      self.flush_buffer_into(@buffer_key, replace: false) if @buffer_key
      @buffer_key = nil
      return

    when "ignore"
      # If this occurs in the middle of a multi-line value, save what
      # has been accumulated so far
      self.flush_buffer_into(@buffer_key, replace: false) if @buffer_key
      return @done_parsing = true

    when "skip"
      # If this occurs in the middle of a multi-line value, save what
      # has been accumulated so far
      self.flush_buffer_into(@buffer_key, replace: false) if @buffer_key
      @is_skipping = true

    when "endskip"
      @is_skipping = false
  end

  self.flush_buffer!
end
parse_scope(scope_type, flags, scope_key) click to toggle source

# File lib/peml/loader.rb, line 167
def parse_scope(scope_type, flags, scope_key)
  # Treat all keys as multi-line
  self.parse_command_key('end')

  self.flush_buffer!

  if scope_key == ''
    last_stack_item = @stack.pop
    @scope = (last_stack_item ? last_stack_item[:scope] : @data) || @data
    @stack_scope = @stack.last

  elsif %w([ {).include?(scope_type)
    nesting = false
    key_scope = @data

    if flags.match(/^\./)
      self.increment_array_element(scope_key)
      nesting = true
      key_scope = @scope if @stack_scope
    else
      @scope = @data
      @stack = []
    end

    # Within freeforms, the `type` of nested objects and arrays is taken
    # verbatim from the `keyScope`.
    if @stack_scope && @stack_scope[:flags].match(/\+/)
      parsed_scope_key = scope_key

      # Outside of freeforms, dot-notation interpreted as nested data.
    else
      key_bits = scope_key.split('.')
      key_bits[0...-1].each do |bit|
        key_scope = key_scope[bit] ||= {}
      end
      parsed_scope_key = key_bits.last
    end

    # Content of nested scopes within a freeform should be stored under "value."
    if (@stack_scope && @stack_scope[:flags].match(/\+/) && flags.match(/\./))
      if scope_type == '['
        parsed_scope_key = 'value'
      elsif scope_type == '{'
        @scope = @scope[:value] = {}
      end
    end

    stack_scope_item = {
        array: nil,
        array_type: nil,
        array_first_key: nil,
        flags: flags,
        scope: @scope
    }
    if scope_type == '['
      stack_scope_item[:array] = key_scope[parsed_scope_key] = []
      stack_scope_item[:array_type] = :freeform if flags.match(/\+/)
      if nesting
        @stack << stack_scope_item
      else
        @stack = [stack_scope_item]
      end
      @stack_scope = @stack.last

    elsif scope_type == '{'
      if nesting
        @stack << stack_scope_item
      else
        @scope = key_scope[parsed_scope_key] = key_scope[parsed_scope_key].is_a?(Hash) ? key_scope[parsed_scope_key] : {}
        @stack = [stack_scope_item]
      end
      @stack_scope = @stack.last
    end
  end
end
parse_start_key(key, quote, rest_of_line) click to toggle source

# File lib/peml/loader.rb, line 96
def parse_start_key(key, quote, rest_of_line)
  # Treat all keys as multi-line
  self.parse_command_key('end')

  self.flush_buffer!

  self.increment_array_element(key)

  key = 'value' if (@stack_scope && @stack_scope[:flags].match(/\+/))

  @buffer_key = key
  @buffer_string = rest_of_line

  self.flush_buffer_into(key, replace: true)

  if !quote.nil?
    @is_quoted = true
    @quote_string = quote
  end
end
parse_text(text) click to toggle source

# File lib/peml/loader.rb, line 245
def parse_text(text)
  if @stack_scope && @stack_scope[:flags].match(/\+/) && text.match(/[^\n\r\s]/)
    @stack_scope[:array] << { "type" => "text", "value" => text.gsub(/(^\s*)|(\s*$)/, '') }
  else
    @buffer_string += text
  end
end