lib/yaml2env.rb in yaml2env-0.1.2 vs lib/yaml2env.rb in yaml2env-0.2.0

- old
+ new

@@ -1,24 +1,49 @@ require 'yaml' require 'logger' +require 'active_support/string_inquirer' +require 'active_support/hash_with_indifferent_access' +require 'active_support/ordered_hash' +require 'active_support/core_ext/object/blank' +require 'pp' +begin + require 'awesome_print' +rescue LoadError + # optional +end module Yaml2env autoload :VERSION, 'yaml2env/version' class Error < ::StandardError end + class ArgumentError < ::ArgumentError + end + class DetectionFailedError < Error end class ConfigLoadingError < Error end class MissingConfigKeyError < Error end + class InvalidConfigValueError < Error + end + + class InvalidRootError < ArgumentError + end + + class AlreadyLoadedError < Error + end + + class HumanError < Error + end + # Hash tracking all of the loaded ENV-values via Yaml2env.load. LOADED_ENV = {} # Config root. # Default: (auto-detect if possible) @@ -30,27 +55,65 @@ # Logger to use for logging. # Default: +::Logger.new(::STDOUT)+ @@logger = ::Logger.new(::STDOUT) + # Loaded files and their values. + @@loaded = {} + + # Default env. + @@default_env = nil + class << self - [:root, :env, :logger].each do |name| + [:default_env, :logger].each do |name| define_method name do - class_variable_get "@@#{name}" + class_variable_get :"@@#{name}" end - define_method "#{name}=" do |value| - class_variable_set "@@#{name}", value + define_method :"#{name}=" do |value| + class_variable_set :"@@#{name}", value end end + define_method :'root' do + class_variable_get :'@@root' + end + define_method :'root=' do |value| + if value.present? + value = File.expand_path(value) rescue value + value = Pathname.new(value) rescue value + # FIXME: Makes sense, but need to rewrite specs somewhat - later. + # if Dir.exists?(value) + # value = Pathname.new(value).expand_path + # else + # raise InvalidRootError, "Trying to set Yaml2env.root to a path that don't exists: #{value.inspect}. Yaml2env.root must point to existing path." + # end + end + class_variable_set :'@@root', value + end + + define_method :'env' do + class_variable_get :'@@env' + end + define_method :'env=' do |value| + # FIXME: Specs "Yaml2env.env.must_equal" fails in really weird way with this line enabled. + # value = ActiveSupport::StringInquirer.new(value.to_s) unless value.is_a?(ActiveSupport::StringInquirer) + class_variable_set :'@@env', value + end + + define_method :'loaded' do + class_variable_get :'@@loaded' + end + alias :environment :env def defaults! @@root ||= nil @@env ||= nil @@logger ||= ::Logger.new(::STDOUT) + @@loaded ||= {} + @@default_env ||= nil end def configure yield self end @@ -60,43 +123,76 @@ self.detect_env! config ||= {} begin - config_path = File.expand_path(File.join(self.root, config_path)).to_s + unless File.exists?(config_path) + config_path = File.expand_path(File.join(self.root, config_path)).to_s + end config = self.load_config_for_env(config_path, self.env) rescue raise ConfigLoadingError, "Failed to load required config for environment '#{self.env}': #{config_path}" end # Merge required + optional keys. keys_values = optional_keys.merge(required_keys) + loaded_key_values = ActiveSupport::OrderedHash.new # Stash found keys from the config into ENV. keys_values.each do |extected_env_key, extected_yaml_key| - ::Yaml2env::LOADED_ENV[extected_env_key.to_s] = ::ENV[extected_env_key.to_s] = config[extected_yaml_key.to_s] - self.logger.info ":: ENV[#{extected_env_key.inspect}] = #{::ENV[extected_env_key.to_s].inspect}" if self.logger? + config_value = config[extected_yaml_key.to_s] + ::Yaml2env::LOADED_ENV[extected_env_key.to_s] = ::ENV[extected_env_key.to_s] = config_value + info ":: ENV[#{extected_env_key.inspect}] = #{::ENV[extected_env_key.to_s].inspect}" end + self.loaded[config_path] = config + # Raise error if any credentials are missing. required_keys.keys.each do |env_key| ::Yaml2env::LOADED_ENV[env_key.to_s] || raise(MissingConfigKeyError, "ENV variable '#{env_key}' needs to be set. Query: #{keys_values.inspect}. Found: #{config.inspect}") end + + true end def load(config_path, required_keys = {}, optional_keys = {}) + args = [config_path, required_keys, optional_keys] + begin self.load!(config_path, required_keys, optional_keys) rescue Error => e - if self.logger? - ::Yaml2env.logger.warn("[Yaml2env]: #{e} -- called from: #{__FILE__})") - end + warn "[Yaml2env]: #{e} -- arguments: #{args.inspect})" end true end + public # force public - we are overriding private Ruby method here, but should be all good in the hood. >:) + + def require!(config_path, required_keys = {}, optional_keys = {}) + self.detect_root! + config_path = File.expand_path(File.join(self.root, config_path)).to_s + raise AlreadyLoadedError, "Already loaded:" if self.loaded_files.include?(config_path) + self.load!(config_path, required_keys, optional_keys) + end + + def require(*args) + begin + self.require!(*args) + true + rescue AlreadyLoadedError => e + false + rescue Error => e + warn "[Yaml2env]: #{e} -- arguments: #{args.inspect})" + false + end + end + + def loaded_files + self.loaded.keys + end + def loaded?(*constant_names) constant_names.all? { |cn| ::Yaml2env::LOADED_ENV.key?(cn.to_s) } end def detect_root! @@ -110,32 +206,170 @@ raise DetectionFailedError, "Failed to auto-detect Yaml.root (config root). Specify root before loading any configs/initializers using Yaml2env, e.g. Yaml2env.root = '~/projects/my_app'." end end def detect_env! - self.env ||= if ::ENV.key?('RACK_ENV') + self.env ||= if ::ENV['RACK_ENV'].present? ::ENV['RACK_ENV'] elsif defined?(::Rails) ::Rails.env elsif defined?(::Sinatra::Application) ::Sinatra::Application.environment + elsif self.default_env.present? + self.default_env else - raise DetectionFailedError, "Failed to auto-detect Yaml2env.root (config root). Specify environment before loading any configs/initializers using Yaml2env, e.g. Yaml2env.env = 'development'." + raise DetectionFailedError, "Failed to auto-detect Yaml2env.env (config environment). Specify environment before loading any configs/initializers using Yaml2env, e.g. Yaml2env.env = 'development'." end end def logger? self.logger.respond_to?(:info) end + def root? + self.root.present? + end + + def env?(*args) + if args.size > 0 + raise HumanError, "Seriously, what are you trying to do? *<:)" if args.size > 1 && args.count { |a| a.blank? } > 1 + args.any? do |arg| + arg.is_a?(Regexp) ? self.env.to_s =~ arg : self.env.to_s == arg.to_s + end + else + self.env.present? + end + end + + def default_env? + self.env == self.default_env + end + + def log_env + value = self.env.inspect + output = ":: Yaml2env.env = #{value}" + output << " (default)" if self.default_env? && self.default_env.present? + puts output + end + + def log_root + value = self.root.present? ? self.root.to_s.inspect : self.root.inspect + output = ":: Yaml2env.root = #{value}" + puts output + end + + def log_values(*args) + if args.blank? + should_include = proc { true } + elsif args.first.is_a?(Regexp) + key_pattern = args.shift + should_include = proc { |key| key =~ key_pattern } + else + should_include = proc { |key| args.any? { |key_string| key == key_string; } } + end + key_values = {} + ::ENV.keys.sort.each do |k,v| + key_values[k] = ENV[k] if should_include.call(k) + end + print ":: ENV = " + puts format_output key_values + end + + def assert_keys!(*required_keys) + raise ArgumentError, "Expected ENV-keys, but got: #{required_keys.inspect}" if required_keys.blank? + raise ArgumentError, "Expected ENV-keys, but got: #{required_keys.inspect}" unless required_keys.first.is_a?(String) || required_keys.first.is_a?(Symbol) + + required_keys = required_keys.collect { |k| k.to_s } + missing_keys = required_keys - ::ENV.keys + + if missing_keys.size == 0 + true + else + raise MissingConfigKeyError, "Assertion failed, no such ENV-keys loaded: #{missing_keys}" + end + end + + def assert_keys(*required_keys) + begin + self.assert_keys!(*required_keys) + true + rescue Error => e + print "[Yaml2env] WARN: Assertion failed, no such ENV-keys loaded: " + puts format_output required_keys + false + end + end + + def assert_values!(key_values) + raise ArgumentError, "Expected hash, but got: #{key_values.inspect}" unless key_values.is_a?(Hash) && key_values.present? + raise ArgumentError, "Expected hash with string-regexp values, but got: #{key_values.inspect}" unless key_values.all? { |k, v| v.is_a?(Regexp) } + + self.assert_keys! *key_values.keys + + failed_assertions = {} + + key_values.each do |k, v| + k = k.to_s + failed_assertions[k] = ::ENV[k] unless ::ENV[k] =~ v + end + + if failed_assertions.keys.size == 0 + true + else + raise InvalidConfigValueError, "Assertion failed, invalid values: #{failed_assertions}" + end + end + + def assert_values(key_values) + begin + self.assert_values!(key_values) + true + rescue Error => e + print "[Yaml2env] WARN: Assertion failed, invalid values: " + puts format_output key_values + false + end + end + protected + + # Work around helpers to make output specs pass on different Ruby version (ordered hash problem). Grrr... + def format_output(array_or_hash) + if array_or_hash.is_a?(Array) + array_or_hash.collect(&:to_s).sort.collect { |k| "#{k.inspect}" }.join(", ") + elsif array_or_hash.is_a?(Hash) + array_or_hash = ActiveSupport::HashWithIndifferentAccess.new(array_or_hash) + array_or_hash.keys.collect(&:to_s).sort.collect { |k| "#{k.inspect} => #{array_or_hash[k].inspect}" }.join(", ") + end + end + + def pretty(*args) + begin + ap *args + rescue + pp *args + end + end + + def info(message) + self.logger.info message if self.logger? + # puts message + end + + # FIXME: Should show filepath for calle. + def warn(message) + self.logger.warn(message) if self.logger? + # puts message + end + def load_config(config_file) YAML.load(File.open(config_file)) end def load_config_for_env(config_file, env) config = self.load_config(config_file) config[env] end + end end