lib/cfoo/el_parser.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'rubygems'
require 'parslet'
require 'cfoo/array'

module Cfoo
    class ElParser < Parslet::Parser

        def self.parse(string)
            return string if string.empty?

            parser = ElParser.new
            transform = ElTransform.new

            tree = parser.parse(string)
            transform.apply(tree)
        #rescue Parslet::ParseFailed => failure
        #    #TODO: handle this properly
        end

        rule(:space) { match('\s').repeat(1) }
        rule(:space?) { space.maybe }
        rule(:escaped_dollar) { str('\$').as(:escaped_dollar) }
        rule(:lone_backslash) { str('\\').as(:lone_backslash) }
        rule(:lparen) { str('(') >> space? }
        rule(:rparen) { str(')') }
        rule(:lbracket) { str('[') }
        rule(:rbracket) { str(']') }
        rule(:dot) { str('.') }
        rule(:comma) { str(",") >> space? }
        rule(:text_character) { match['^\\\\$'] }
        rule(:identifier) { match['a-zA-Z0-9._\-:'].repeat(1).as(:identifier) >> space? }
        rule(:text) { text_character.repeat(1).as(:text) }

        rule(:attribute_reference) do
            (
                expression.as(:reference) >> (
                    str("[") >> expression.as(:attribute) >> str("]")
                )
            ).as(:attribute_reference)
        end
        rule(:mapping) do
            (
                expression.as(:map) >>
                str("[") >> expression.as(:key) >> str("]") >>
                str("[") >> expression.as(:value) >> str("]")
            ).as(:mapping)
        end
        rule(:function_call) do
            (
                identifier.as(:function) >>
                lparen >>
                (expression >> (comma >> expression).repeat).as(:arguments).maybe >>
                rparen
            ).as(:function_call)
        end
        rule(:reference) do
            expression.as(:reference)
        end

        rule(:expression) { el | identifier }
        rule(:el) do
            str("$(") >> ( mapping | attribute_reference | function_call | reference ) >> str(")")
        end
        rule(:string) do
            ( escaped_dollar | lone_backslash | el | text ).repeat.as(:string)
        end

        root(:string)
    end

    class ElTransform < Parslet::Transform

        rule(:escaped_dollar => simple(:dollar)) { "$" }
        rule(:lone_backslash => simple(:backslash)) { "\\" }

        rule(:identifier => simple(:identifier)) do
            identifier.str
        end

        rule(:text => simple(:text)) do
            text.str
        end

        rule(:reference => subtree(:reference)) do
            { "Ref" => reference }
        end

        rule(:attribute_reference => { :reference => subtree(:reference), :attribute => subtree(:attribute)}) do
            { "Fn::GetAtt" => [ reference, attribute ] }
        end

        rule(:function_call => { :function => simple(:function) }) do
            { "Fn::#{function}" => "" }
        end

        rule(:function_call => { :function => simple(:function), :arguments => simple(:argument) }) do
            { "Fn::#{function}" => argument }
        end

        rule(:function_call => { :function => simple(:function), :arguments => subtree(:arguments) }) do
            { "Fn::#{function}" => arguments }
        end

        rule(:mapping => { :map => subtree(:map), :key => subtree(:key), :value => subtree(:value)}) do
            { "Fn::FindInMap" => [map, key, value] }
        end

        rule(:string => subtree(:string_parts)) do
            # EL is parsed separately from other strings
            parts = string_parts.join_adjacent_strings

            if parts.size == 1
                parts.first
            else
                { "Fn::Join" => [ "", parts ] }
            end
        end
    end
end