UnderpantsGnome/config_reader-gem

View on GitHub
lib/config_reader.rb

Summary

Maintainability
A
0 mins
Test Coverage
require "config_reader/version"
require "config_reader/config_hash"
require "psych"

begin
  require "sekrets"
rescue LoadError
end

begin
  require "erb"
rescue LoadError
  warn "ERB not found, you won't be able to use ERB in your config"
end

class ConfigReader
  class << self
    attr_reader :envs

    def [](key)
      config[key.to_sym]
    end

    def config
      @config = nil unless defined?(@config)
      @config ||= reload
    end

    def configuration
      @configuration ||= Configuration.new
    end

    def configure
      self.configuration ||= Configuration.new
      yield(configuration)
    end

    def deep_merge(hash, other_hash)
      hash.merge(other_hash) do |_key, this_val, other_val|
        if this_val.is_a?(Hash) && other_val.is_a?(Hash)
          deep_merge(this_val, other_val)
        else
          other_val
        end
      end
    end

    def dig(*args)
      args.map!(&:to_sym) if args.respond_to?(:map!)

      config.dig(*args)
    end

    def find_config
      return configuration.config_file if File.exist?(configuration.config_file)

      %w[. config].each do |dir|
        config_file = File.join(dir, configuration.config_file)
        return config_file if File.exist?(config_file)
      end

      nil
    end

    def inspect
      puts config.inspect
    end

    def load_config
      raise "No config file set" unless find_config

      conf = load_yaml

      raise "No config found" unless conf

      conf
    end

    def load_sekrets
      if configuration.sekrets_file
        if !defined?(::Sekrets)
          raise ArgumentError,
                "You specified a sekrets_file, but the sekrets gem isn't available."
        else
          ::Sekrets.settings_for(configuration.sekrets_file) ||
            raise("No sekrets found")
        end
      end
    end

    def load_yaml
      permitted_classes = configuration.permitted_classes.to_a + [Symbol]

      if defined?(ERB)
        Psych.safe_load(
          ERB.new(File.read(find_config)).result,
          aliases: true,
          permitted_classes: permitted_classes
        )
      else
        Psych.safe_load_file(
          File.read(find_config),
          aliases: true,
          permitted_classes: permitted_classes
        )
      end
    end

    def merge_all_configs(conf, defaults, sekrets)
      @envs = {}

      (conf.keys - ["defaults"]).each do |env|
        key_hash = deep_merge(defaults, conf[env]) if conf[env]
        key_hash = deep_merge(defaults, sekrets[env]) if sekrets&.[](env)

        @envs[env] = ConfigHash.convert_hash(
          key_hash,
          configuration.ignore_missing_keys
        )
      end
    end

    def merge_configs(conf, sekrets)
      defaults = conf["defaults"]

      if sekrets&.[]("defaults")
        defaults = deep_merge(defaults, sekrets["defaults"])
      end

      merge_all_configs(conf, defaults, sekrets)

      @envs[configuration.environment]
    end

    def method_missing(key, *_args, &_block)
      if key.to_s.end_with?("=")
        raise ArgumentError.new("ConfigReader is immutable")
      end

      config[key] || nil
    end

    def reload
      merge_configs(load_config, load_sekrets)
    end

    def respond_to_missing?(m, *)
      config.key?(m)
    end
  end

  class Configuration
    attr_accessor :config_file,
                  :environment,
                  :ignore_missing_keys,
                  :permitted_classes,
                  :sekrets_file

    def initialize
      @config_file = nil
      @environment = nil
      @ignore_missing_keys = false
      @permitted_classes = []
      @sekrets_file = nil
    end
  end
end