dragosboca/devinstall

View on GitHub
lib/devinstall/settings.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require 'yaml'
require 'devinstall/deep_symbolize'
require 'singleton'
require 'pp'

class Hash
  include DeepSymbolizable
end


module Devinstall

  class KeyNotDefinedError < RuntimeError
  end
  class UnknownKeyError < RuntimeError
  end

  class Settings
    include Singleton

    FILES     = []
    SETTINGS  = {}
    MDEFS     = {
        local:    [:folder, :temp],
        build:    [:folder, :command, :provider, :type, :arch, :target],
        install:  [:folder, :command, :provider, :type, :arch],
        tests:    [:folder, :command, :provider],
        repos:    [:folder, :provider, :type, :arch],
        defaults: [:type, :env]
    }
    PROVIDERS = {}
    class Action
      include Enumerable

      def initialize(m, pkg, type, env)
        @method, @pkg, @type, @env = (m.to_sym rescue m), (pkg.to_sym rescue pkg), (type.to_sym rescue type), (env.to_sym rescue env)
      end

      def has_key?(key)
        Settings.instance.send(@method, key, pkg: @pkg, type: @type, env: @env) rescue false
      end

      def [](key)
        Settings.instance.send(@method, key, pkg: @pkg, type: @type, env: @env)
      end

      def each
        config=Settings.instance
        Settings::MDEFS[@method].each do |key|
          yield(key, config.send(@method, key, pkg: @pkg, type: @type, env: @env)) if block_given?
        end
      end
    end ## Class Action

    def load! (filename)
       if FILES.include? filename
          true
       else
          FILES << filename
          data   = YAML::load_file(filename).deep_symbolize
          merger = proc do |_, v1, v2|
             Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
          end
          SETTINGS.merge! data, &merger
       end if File.exist?(File.expand_path(filename))
      FILES.include? filename
    end

    def method_missing (method, *args)
      raise UnknownKeyError, "Undefined section '#{method}'" unless method_defined? method
      key  = (args.shift or {})
      rest = (Hash === key) ? key : (args.shift or {})
      pkg  = rest[:pkg]
      if pkg.nil?
        raise UnknownKeyError, "Unknown key #{key}" unless key_defined? method, key
        return SETTINGS[method][key] rescue raise "#{method}: Package must be defined"
      end
      type = rest[:type] || defaults(:type)
      env  = rest[:env] || defaults(:env)
      return Action.new(method, pkg, type, env) if Hash === key
      raise UnknownKeyError, "Unknown key #{key}" unless key_defined? method, key
      global_or_local(method, key, pkg, type, env) or
          raise KeyNotDefinedError, "Undefined key '#{method}:#{key}' or alternate for ['#{pkg}' '#{type}' '#{env}']"
    end

    def respond_to_missing?(method, _)
      method_defined? method
    end

    def register_provider(provider, methods)
      PROVIDERS[provider]=methods
    end

    def unregister_provider(provider)
      PROVIDERS.delete(provider)
    end

    private

    def key_chain(*keys)
      res=SETTINGS
      keys.each do |key|
        next if key.nil?
        return nil unless res.has_key? key.to_sym
        res=res[key.to_sym]
      end
      res
    end

    def global_or_local(section, key, pkg, type, env)
      key_chain(:packages, pkg, type, section, env, key) ||
          key_chain(:packages, pkg, type, section, key) ||
          key_chain(section, env, key) ||
          key_chain(section, key)
    end

    def key_defined?(method, key)
      method, key = (method.to_sym rescue method), (key.to_sym rescue key)
      method_defined? method and
          (MDEFS[method].include? key rescue false) or
          PROVIDERS.inject(false) { |res, (_, v)| res or (v[method].include? key rescue false) }
    end

    def method_defined?(method)
      method = (method.to_sym rescue method)
      (MDEFS.has_key?(method) or
          PROVIDERS.inject(false) { |res, (k, _)| res or PROVIDERS[k].has_key? method }) and
          SETTINGS.has_key? method
    end

  end
end