GutenYe/optimism

View on GitHub
lib/optimism/parser/default.rb

Summary

Maintainability
B
5 hrs
Test Coverage
class Optimism
  module Parser
    class Default < Base
      class Filter
        attr_accessor :content

        # input any thing
        def initilaize(content)
          raise NotImplementedError, ""
        end

        # => any thing
        def evaluate
          raise NotImplementedError, ""
        end

      end

      # convert sugar syntax
      #
      #   develoment:
      #     database 'postgresql'
      #
      # to a pure ruby syntax
      #
      #   development do
      #     database 'postgresql'
      #   end
      #
      class StringBlock2RubyBlock < Filter
        INDENT="  "

        # the string data.
        attr_reader :content

        def initialize(content)
          @content = content
        end

        # compile sugar-syntax into ruby-syntax
        def evaluate
          script = ""
          indent_counts = 0
          block_indent_counts = []
          block_index = 0

          scan(content) { |token, statement|
            case token
            when :block_start
              block_indent_counts << indent_counts
              statement = statement.sub(/\s*:/, " do")
              block_index += 1
              script << statement << "\n"
            when :statement
              script << statement << "\n"
            when :indent
              indent_counts += 1
              script << INDENT*indent_counts
            when :undent
              script << INDENT*indent_counts
            when :dedent
              indent_counts -= 1
              if indent_counts == block_indent_counts[-1]
                block_index -= 1
                script << INDENT*(indent_counts) + "end\n"
                block_indent_counts.pop
              else
                script << INDENT*(indent_counts)
              end
            end
          }

          script
        end

      private

        def scan(content, &blk)
          return to_enum(:scan, content) unless blk
          last_indent = 0

          content.scan(/(.*?)(\n+|\Z)/).each { |line, newline|
            a = line.match(/^(\s*)(.*)/)
            _, indents, statement = line.match(/^(\s*)(.*)/).to_a

            # indent
            # a:
            #   b 1
            #   c:
            #     d 1
            #     e:
            #       f 1
            #   g 1
            indent = 
              if indents == ""
                0
              elsif indents =~ /^ +$/
              (indents.count(" ") / INDENT.length.to_f).ceil
              elsif indents =~ /^\t+$/
                indents.count("\t")
              else
                raise Error, "indent error -- #{indents.inspect}"
              end
            counts = indent - last_indent
            last_indent = indent

            if counts == 0
              blk.call :undent
            else
              counts.abs.times {
                blk.call counts>0 ? :indent : :dedent
              }
            end

            # statement
            if statement =~ /:\s*$/
              blk.call [:block_start, statement.gsub(/\s*:\s*$/, ':')]  # jruby need []
            else
              blk.call [:statement, statement]
            end
          }
        end
      end

      # convert variable assigment
      #
      #   a = 1
      #   b <<-OPTIMISM_EOF0
      #     c = 2
      #     _.c = 3
      #     foo.d = 4
      #   OPTIMISM_EOF0
      #   puts 1
      #   'a' == 'a'
      #   'a' =~ /./
      #  
      # to
      #
      #    _.a = 1
      #    b <<-OPTIMISM_EOF0
      #      _.c = 2
      #      _.c = 3
      #      _.foo.d = 4
      #   OPTIMISM_EOF0
      #   puts 1
      #   ...
      #
      class LocalVariable2Method < Filter
        LOCAL_VARIABLE_PAT = /^(\s*)([a-z0-9][a-zA-Z0-9_.]*\s*=[^~=])/

        def initialize(content)
          @content = content
        end

        def evaluate
          contents = content.split("\n").each.with_object([]) do |line, m|
            m << line.sub(LOCAL_VARIABLE_PAT, "\\1_.\\2")
          end

          contents.join("\n") + "\n"
        end
      end

      def self.parse(optimism, content, opts={}, &blk)
        new(optimism).parse!(content, opts, &blk)
      end

      def initialize(optimism)
        @optimism = optimism
      end

      def parse!(content=nil, opts={}, &blk)
        if content
          eval_string(content, opts)
        elsif blk
          eval_block(&blk)
        end
      end

      private

      # Eval a block
      #
      # @example
      #
      #   eval_block do |o|
      #     o.pi = 3.14
      #   end
      #
      def eval_block(&blk)
        @optimism.__send__ :instance_exec, @optimism, &blk

        @optimism
      end

      # Eval string
      #
      # @example
      #
      #   eval_string <<-EOF
      #     a = 1
      #     b:
      #       c = 2
      #   EOF
      #
      def eval_string(content, opts={})
        content = StringBlock2RubyBlock.new(content).evaluate 
        content = LocalVariable2Method.new(content).evaluate

        @optimism.instance_eval(content, opts[:filename] || "(eval)")

        @optimism
      end
    end
  end
end

Optimism.add_extension ".rb", Optimism::Parser::Default

# vim: fdn=4