mtwilliams/ryb

View on GitHub
lib/ryb/dsl.rb

Summary

Maintainability
B
4 hrs
Test Coverage
module Ryb
  module DomainSpecificLanguage
    module Configurations
      def configuration(name, opts={}, &block)
        @spec.configurations ||= []

        existing_config = find_by_canonical_name(@spec.configurations, name)
        config = existing_config || Ryb::Configuration.new
        existing_pretty_name = config.name.pretty if existing_config
        config.name = Ryb::Name.new(name, :pretty => existing_pretty_name || opts[:pretty])

        @spec.configurations << config unless existing_config

        DomainSpecificLanguage.for(config).instance_eval(&block)
      end

      def platform(name, opts={}, &block)
        # TODO(mtwilliams): Refactor this.
        @spec.platforms ||= []

        existing_platform = find_by_canonical_name(@spec.platforms, name)
        platform = existing_platform || Ryb::Platform.new
        existing_pretty_name = platform.name.pretty if existing_platform
        platform.name ||= Ryb::Name.new(name, :pretty => existing_pretty_name || opts[:pretty])

        @spec.platforms << platform unless existing_platform

        DomainSpecificLanguage.for(platform).instance_eval(&block)
      end

      def architecture(name, opts={}, &block)
        # TODO(mtwilliams): Refactor this.
        @spec.architectures ||= []

        existing_arch = find_by_canonical_name(@spec.architectures, name)
        arch = existing_arch || Ryb::Architecture.new
        existing_pretty_name = arch.name.pretty if existing_arch
        arch.name ||= Ryb::Name.new(name, :pretty => existing_pretty_name || opts[:pretty])

        @spec.architectures << arch unless existing_arch

        DomainSpecificLanguage.for(arch).instance_eval(&block)
      end

      private
        def find_by_canonical_name(ary_of_named_things, name)
          (ary_of_named_things.select do |existing_thing|
              existing_thing.name.canonicalize == name.to_s
            end).first
        end
    end

    module Environment
      def add_include_path(path)
        @spec.paths ||= Paths.new
        @spec.paths.includes = @spec.paths.includes + [path]
      end

      def add_include_paths(*paths_and_patterns)
        [*paths_and_patterns].each do |path_or_pattern|
          [*(Dir.glob(path_or_pattern))].each do |path|
            add_include_path(path)
          end
        end
      end

      def add_library_path(path)
        @spec.paths ||= Paths.new
        @spec.paths.libraries = @spec.paths.libraries + [path]
      end

      def add_library_paths(*paths_and_patterns)
        [*paths_and_patterns].each do |path_or_pattern|
          [*(Dir.glob(path_or_pattern))].each do |path|
            add_library_path(path)
          end
        end
      end

      def add_binary_path(path)
        @spec.paths ||= Paths.new
        @spec.paths.binaries = @spec.paths.binaries + [path]
      end

      def add_binary_paths(*paths_and_patterns)
        [*paths_and_patterns].each do |path_or_pattern|
          [*(Dir.glob(path_or_pattern))].each do |path|
            add_binary_path(path)
          end
        end
      end
    end

    module Preprocessor
      def define(defines)
        @spec.defines ||= Hash.new
        @spec.defines = @spec.defines.merge(defines)
      end
    end

    module Flags
      def treat_warnings_as_errors=(flag)
        @spec.treat_warnings_as_errors = flag
      end

      def generate_debug_symbols=(flag)
        @spec.generate_debug_symbols = flag
      end

      def link_time_code_generation=(flag)
        @spec.link_time_code_generation = flag
      end

      def optimize=(goal)
        @spec.optimize = goal
      end
    end

    module Code
      def add_source_file(file, **options)
        case file
          when SourceFile
            @spec.sources ||= []
            @spec.sources = @spec.sources + [file]
          when String
            add_source_file(SourceFile.new(file, options))
          end
      end

      def add_source_files(*files_and_patterns, **options)
        [*files_and_patterns].each do |file_or_pattern|
          case file_or_pattern
            when SourceFile
              add_source_file(file_or_pattern, options)
            when String
              [*(Dir.glob(file_or_pattern))].each do |file|
                add_source_file(file, options)
              end
            end
        end
      end
    end

    module Dependencies
      def add_dependency(product)
        @spec.dependencies ||= []
        @spec.dependencies = @spec.dependencies + [Ryb::InternalDependency.new(product)]
      end

      def add_dependencies(products)
        [*products].each(&self.add_dependency)
      end

      def add_external_dependency(lib_or_framework, opts={})
        @spec.dependencies ||= []
        @spec.dependencies = @spec.dependencies + [Ryb::ExternalDependency.new(lib_or_framework, opts)]
      end

      def add_external_dependencies(libs_and_frameworks, opts={})
        [*libs_and_frameworks].each do |lib_or_framework|
          self.add_external_dependency(lib_or_framework, opts)
        end
      end
    end

    class Configuration
      def initialize(config)
        @spec = @config = config
      end

      def prefix=(prefix)
        @config.prefix = prefix
      end

      def suffix=(suffix)
        @config.suffix = suffix
      end

      include Environment
      include Preprocessor
      include Flags
      include Code
      include Dependencies
    end

    class Platform < Configuration
      def initialize(platform)
        super(platform)
        @platform = platform
      end
    end

    class Architecture < Configuration
      def initialize(arch)
        super(arch)
        @arch = arch
      end
    end

    class Product
      def initialize(product)
        @spec = @product = product
      end

      def author=(author)
        @product.author = author
      end

      def description=(description)
        @product.description = description
      end

      def license=(license)
        @product.license = license
      end

      def version=(version)
        @product.version = version
      end

      include Configurations
      include Environment
      include Preprocessor
      include Flags
      include Code
      include Dependencies
    end

    class Application < Product
      def initialize(app)
        super(app)
        @app = app
      end
    end

    class Library < Product
      def initialize(lib)
        super(lib)
        @lib = lib
      end

      def linkage=(linkage)
        @lib.linkage = linkage
      end
    end

    class Project
      def initialize(project)
        @spec = @project = project
      end

      include Configurations
      include Environment
      include Preprocessor

      def application(name, opts={}, &block)
        # TODO(mtwilliams): Verify uniqueness.
        app = Ryb::Application.new
        app.name = Ryb::Name.new(name, :pretty => opts[:pretty])
        DomainSpecificLanguage.for(app).instance_eval(&block)
        @project.products ||= []
        @project.products = @project.products + [app]
      end

      def library(name, opts={}, &block)
        # TODO(mtwilliams): Verify uniqueness.
        lib = Ryb::Library.new
        lib.name = Ryb::Name.new(name, :pretty => opts[:pretty])
        DomainSpecificLanguage.for(lib).instance_eval(&block)
        @project.products ||= []
        @project.products = @project.products + [lib]
      end
    end

    FOR = {Ryb::Configuration => Configuration,
           Ryb::Platform      => Platform,
           Ryb::Architecture  => Architecture,
           Ryb::Application   => Application,
           Ryb::Library       => Library,
           Ryb::Project       => Project}

    def self.for(obj)
      # TODO(mtwilliams): Use `__poured__` rather than manually building?
      # poured = spec.class_variable_get(:@@__poured__)

      # Hide behind a SimpleDelegator so users don't play with our internals.
      SimpleDelegator.new(FOR[obj.class].new(obj))
    end
  end
end