require 'envied/version' require 'envied/cli' require 'envied/env_proxy' require 'envied/coercer' require 'envied/coercer/envied_string' require 'envied/variable' require 'envied/configuration' require 'envied/env_interceptor' class ENVied class << self attr_reader :env, :config alias_method :required?, :env end def self.require(*args, **options) requested_groups = (args && !args.empty?) ? args : ENV['ENVIED_GROUPS'] env!(requested_groups, options) error_on_duplicate_variables!(options) error_on_missing_variables!(options) error_on_uncoercible_variables!(options) intercept_env_vars! ensure_spring_after_fork_require(args, options) end def self.env!(requested_groups, **options) @config = options.fetch(:config) { Configuration.load } @env = EnvProxy.new(@config, groups: required_groups(*requested_groups)) end def self.error_on_duplicate_variables!(options) var_types_by_name = env.variables.reduce({}) do |acc, v| (acc[v.name] ||= []).push v.type acc end dups = var_types_by_name.select {|name, types| types.uniq.count > 1 } if dups.any? raise "The following variables are defined more than once with different types: #{dups.keys.join(', ')}" end end def self.error_on_missing_variables!(**options) names = env.missing_variables.map(&:name).uniq.sort if names.any? msg = "The following environment variables should be set: #{names.join(', ')}." msg << "\nPlease make sure to stop Spring before retrying." if spring_enabled? && !options[:via_spring] raise msg end end def self.error_on_uncoercible_variables!(**options) errors = env.uncoercible_variables.map do |v| format("%{name} with %{value} (%{type})", name: v.name, value: env.value_to_coerce(v).inspect, type: v.type) end if errors.any? msg = "The following environment variables are not coercible: #{errors.join(", ")}." msg << "\nPlease make sure to stop Spring before retrying." if spring_enabled? && !options[:via_spring] raise msg end end def self.required_groups(*groups) splitter = ->(group){ group.is_a?(String) ? group.split(/ *, */) : group } result = groups.compact.map(&splitter).flatten result.any? ? result.map(&:to_sym) : [:default] end def self.env_keys_to_intercept env.variables.select{|v| v.type == :env }.map{|var| var.name.to_s } end def self.intercept_env_vars! if (keys = env_keys_to_intercept).any? ENV.instance_eval { @__envied_env_keys = keys } class << ENV prepend(ENVied::EnvInterceptor) end end end private_class_method :intercept_env_vars!, :env_keys_to_intercept def self.ensure_spring_after_fork_require(args, **options) if spring_enabled? && !options[:via_spring] Spring.after_fork { ENVied.require(args, options.merge(via_spring: true)) } end end def self.springify(&block) if defined?(ActiveSupport::Deprecation.warn) && !required? ActiveSupport::Deprecation.warn(<<~MSG) It's no longer recommended to `ENVied.require` within ENVied.springify's block. Please re-run `envied init:rails` to upgrade. MSG end if spring_enabled? Spring.after_fork(&block) else block.call end end def self.spring_enabled? defined?(Spring) && Spring.respond_to?(:watcher) end def self.method_missing(method, *args, &block) respond_to_missing?(method) ? (env && env[method.to_s]) : super end def self.respond_to_missing?(method, include_private = false) (env && env.has_key?(method)) || super end end