step1profit/yamlr

View on GitHub
lib/yamlr/reader/builder.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module Yamlr
  module Reader
    module Builder
      #
      # Abbreviations:
      # adr = address
      # phs = parsed hash
      # hsh = hash
      # lst = list
      # met = meta
      # cur = current
      # spc = spaces
      # idt = indent
      # idx = index
      #
      # dcs = doc_start
      # dct = doc_term
      # hpr = hsh_pair
      # hky = hsh_key
      # arr = array
      # com = comment
      # mal = malformed
      # msg = message
      # bla = blank
      #
      class RootNodeError < StandardError; end

      ##
      # adds parsed_hash of line to hash
      #
      def self.build(hsh, phs)
        add = false
        upd = nil
        msg = phs[:msg]
        self.doc_new(hsh) if hsh.empty? && msg != "doc"
        val = phs[:val]
        lst = hsh[:lst]

        unless [:com, :mal, :bla].include?(msg)
          cur = self.cur(hsh)
          key = phs[:key]
          spc = phs[:spc]
          idt = phs[:opt][:indent]
          adr = hsh[:adr]
          idx = self.index(spc, idt)
          upd = self.update(adr, idx)
          las = self.adr_obj(hsh, hsh[:adr])
          add = true
        end

        case msg
        when :dcs then self.doc_start(hsh)
        when :dct then self.doc_term(hsh)
        when :hpr then self.hsh_pair(las, key, val)
        when :hky then self.hsh_key(las, adr, key)
        when :bla then self.blank(lst)
        when :arr
          raise RootNodeError if cur.is_a?(Hash) && !cur.empty? && spc == 0
          if las.is_a?(Hash) && las.empty?
            self.adr_obj_to_array(hsh, hsh[:adr])
            las = self.adr_obj(hsh, hsh[:adr])
          end
          self.arr_parse(las, adr, val, upd)
        when :com then self.comment(lst, val)
        when :mal then self.malformed(lst, val)
        end

        self.add_to_list(lst, adr) if add
      end

      ##
      # sub-parses array message, and logic from update-method
      #
      def self.arr_parse(las, adr, val, upd)
        case upd
        when "eq" then self.array_val(las, val)
        when "lt" then self.array_val(las, val)
        when "gt" then self.array_new(las, adr, val)
        else; raise "error"
        end
      end

      # current hash, returns array of "doc"
      #
      def self.cur(hsh)
        hsh[hsh.length - 2]
      end

      # address array to string usable in eval on root node
      #
      def self.to_adr(adr)
        m = adr.map {|x|
          case
          when x.is_a?(Symbol) then "[:#{x}]"
          when x.is_a?(String) then "['#{x}']"
          when x.is_a?(Integer) then "[#{x}]"
          end
        }
        m.join
      end

      # returns the actual object at an address in tree
      #
      def self.adr_obj(hsh, adr)
        m = self.to_adr(adr)
        eval("hsh#{m.to_s}")
      end

      # converts an object in tree to empty array
      #
      def self.adr_obj_to_array(hsh, adr)
        m = self.to_adr(adr)
        eval("hsh#{m.to_s} = []")
      end

      # calculates index based on spaces divided by indent unit
      #
      def self.index(spc, idt)
        ((spc % idt) != 0) ? 0 : spc / idt
      end

      # if indentation less than before, jump up tree, remove extra indices
      #
      def self.update(adr, idx)
        ret =  nil
        len = (adr.length - 1)
        if idx < len
          # remove indices after current index
          adr.replace(adr[0..idx])
          ret = "lt"
        elsif idx == len
          ret = "eq"
        elsif idx > len
          ret = "gt"
        end
        ret
      end

      # create keypair for new doc
      #
      def self.doc_new(hsh)
        hsh[:lst] ||= {}
        hsh[:adr] ||= []
        len = hsh.length - 1
        hsh[len] = {}
        hsh[:adr].clear
        hsh[:adr] << len
      end

      # new array in tree, provides logic for last modified object
      #
      def self.array_new(las, adr, val)
        case
        when las.is_a?(Array) && las.empty?
          x = [val]
          las = x
          adr << las.rindex(x)
        when las.is_a?(Array) && las.last.is_a?(String) && las.last.empty?
          x = [val]
          las[-1] = [val]
          adr << las.rindex(x)
        when las.is_a?(Array)
          x = [val]
          las << x
          adr << las.rindex(x)
        end
      end

      # start document
      #
      def self.doc_start(hsh)
        self.doc_new(hsh)
        self.add_to_list(hsh[:lst], "#DOC_START")
      end

      # document terminate
      #
      def self.doc_term(hsh)
        self.add_to_list(hsh[:lst], "#DOC_TERM")
      end

      # add val to array in tree
      #
      def self.array_val(las, val)
        case
        # when x is a hash, it's already addressed
        when las.is_a?(Array)
          las << val
        else
          raise las.inspect
        end
      end

      # add hashkey to tree
      #
      def self.hsh_key(las, adr, key)
        case
        when las.is_a?(Hash)
          las[key] = {}
        when las.is_a?(Array)
          x = {key => {}}
          las << x
          adr << las.rindex(x)
        end
        adr << key
      end

      # add hashpair to tree
      #
      def self.hsh_pair(las, key, val)
        case
        when las.is_a?(Array)
          x = {key => val}
          las << x
        when las.is_a?(Hash)
          las[key] = val
        end
      end

      # list stores hsh addresses of lines, comments, etc.
      #
      def self.add_to_list(lst, adr)
        case adr.class.to_s
        when "String" then x = adr
        when "Array" then  x = adr.join(",")
        end
        lst[lst.length + 1] = x
      end

      # add blank to list
      #
      def self.blank(lst)
        self.add_to_list(lst, "#BLANK")
      end

      # add comment to list
      #
      def self.comment(lst, val)
        lst[lst.length + 1] = "#COMMENT: #{val}"
      end

      # add malformed to list
      #
      def self.malformed(lst, val)
        lst[lst.length + 1] = "#MALFORMED: #{val}"
      end
    end
  end
end