jannishuebl/orchparty

View on GitHub
lib/orchparty/dsl_parser.rb

Summary

Maintainability
B
5 hrs
Test Coverage
require 'pathname'
module Orchparty
  class DSLParser
    attr_reader :filename

    def initialize(filename)
      @filename = filename
    end

    def parse
      file_content = File.read(filename)
      builder = RootBuilder.new
      builder.instance_eval(file_content, filename)
      builder._build
    end
  end

  class Builder
    def self.build(*args, block)
      builder = self.new(*args)
      builder.instance_eval(&block)
      builder._build
    end

    def assign_or_merge(node, key, value)
      if node[key]
        node[key] = node[key].deep_merge_concat(value)
      else
        node[key] = value
      end
    end
  end

  class RootBuilder < Builder

    def initialize
      @root = AST.root
    end

    def import(rel_file)
      old_file_path = Pathname.new(caller[0][/[^:]+/]).parent
      rel_file_path = Pathname.new rel_file
      new_file_path = old_file_path + rel_file_path
      file_content = File.read(new_file_path)
      instance_eval(file_content, new_file_path.expand_path.to_s)
    end

    def application(name, &block)
      @root.applications[name] = ApplicationBuilder.build(name, block)
      self
    end

    def mixin(name, &block)
      @root._mixins[name] = MixinBuilder.build(name, block)
      self
    end

    def _build
      @root
    end
  end

  class MixinBuilder < Builder

    def initialize(name)
      @mixin = AST.mixin(name: name)
    end

    def service(name, &block)
      result = ServiceBuilder.build(name, block)
      @mixin.services[name] = result
      @mixin._mixins[name] = result
      self
    end

    def variables(&block)
      @mixin._variables = VariableBuilder.build(block)
      self
    end

    def mixin(name, &block)
      @mixin._mixins[name] = ServiceBuilder.build(name, block)
    end

    def volumes(&block)
      @mixin.volumes = HashBuilder.build(block)
    end

    def networks(&block)
      @mixin.networks = HashBuilder.build(block)
    end

    def _build
      @mixin
    end
  end

  class ApplicationBuilder < Builder

    def initialize(name)
      @application = AST.application(name: name)
    end

    def mix(name)
      @application._mix << name
    end

    def mixin(name, &block)
      @application._mixins[name] = ApplicationMixinBuilder.build(block)
      self
    end

    def all(&block)
      @application.all = AllBuilder.build(block)
      self
    end

    def variables(&block)
      @application._variables = VariableBuilder.build(block)
      self
    end

    def volumes(&block)
      @application.volumes = HashBuilder.build(block)
      self
    end

    def networks(&block)
      @application.networks = HashBuilder.build(block)
    end

    def service(name, &block)
      @application.services[name] = ServiceBuilder.build(name, block)
      self
    end

    def _build
      @application
    end
  end

  class HashBuilder < Builder

    def method_missing(_, *values, &block)
      if block_given?
        value = HashBuilder.build(block)
        if values.count == 1
          @hash ||= AST.hash
          @hash[values.first.to_sym] = value
        else
          @hash ||= AST.array
          @hash << value
        end
      else
        value = values.first
        if value.is_a? Hash
          @hash ||= AST.hash
          key, value = value.first
          @hash[key.to_sym] = value
        else
          @hash ||= AST.array
          @hash << value
        end
      end
      self
    end

    def _build
      @hash
    end
  end

  class VariableBuilder < HashBuilder
    def _build
      super || {}
    end
  end

  class CommonBuilder < Builder

    def initialize(node)
      @node = node
    end

    def mix(name)
      @node._mix << name
    end

    def method_missing(name, *values, &block)
      if block_given?
        assign_or_merge(@node, name,  HashBuilder.build(block))
      else
        assign_or_merge(@node, name, values.first)
      end
    end

    def _build
      @node
    end

    def variables(&block)
      @node._variables ||= {}
      @node._variables = @node._variables.merge(VariableBuilder.build(block))
      self
    end
  end

  class AllBuilder < CommonBuilder
    def initialize
      super AST.all
    end
  end

  class ApplicationMixinBuilder < CommonBuilder
    def initialize
      super AST.application_mixin
    end
  end

  class ServiceBuilder < CommonBuilder

    def initialize(name)
      super AST.service(name: name)
    end
  end
end