class ENVied VERSION = "0.1.0" module Configurable require 'virtus' class VariableError < StandardError attr_reader :variable def initialize(variable) @variable = variable end def variable_type variable.type.to_s.split("::").last end end class VariableMissingError < VariableError def message "Please provide ENV['#{variable.name.to_s.upcase}'] of type #{variable_type}" end end class VariableTypeError < VariableError def message "ENV['#{variable.name.to_s.upcase}'] should be of type #{variable_type}" end end def self.included(base) base.class_eval do include Virtus.model end base.extend ClassMethods end module ClassMethods # Creates a configuration instance from env. # # Will raise VariableMissingError for variables not present in ENV. # # Will raise VariableTypeError for variables whose ENV-value can't be coerced to the configured type. # # @param env [Hash] the env def parse_env(env) atts = attribute_set.map(&:name).each_with_object({}) do |name, result| @variable = attribute_set[name] has_default = !!@variable.options[:default] var_value = env[name.to_s] || env[name.to_s.upcase] result[name] = var_value if var_value if !(result[name] || has_default) raise VariableMissingError, @variable end end new(atts) rescue Virtus::CoercionError => e raise VariableTypeError, @variable end def variable(name, type = :String, options = {}) options[:default] &&= flexible_arity(options[:default]) attribute name, type, { strict: true }.merge(options) end protected def flexible_arity(default) return default unless default.respond_to?(:call) case default.arity when 1 ->(env, _){ default[env] } when 0 ->(*){ default[] } else default end end end end class << self attr_accessor :configuration end def self.configure(&block) @configuration = Class.new { include Configurable }.tap(&block) # or define this thing as ENVied::Configuration? prolly not threadsafe ensure @instance = nil end def self.instance @instance ||= @configuration.parse_env(ENV.to_hash) end class << self alias_method :require!, :instance end def self.[](key) instance.attributes[key] #instance[key] # will raise and complain that > doesn't response; better? end def self.method_missing(method, *args, &block) respond_to_missing?(method) ? instance.public_send(method, *args, &block) : super end def self.respond_to_missing?(method, include_private = false) instance.attributes.key?(method) || super end end