lib/anyway/config.rb in anyway_config-1.4.4 vs lib/anyway/config.rb in anyway_config-2.0.0.pre

- old
+ new

@@ -1,14 +1,16 @@ # frozen_string_literal: true -require 'anyway/ext/jruby' if defined? JRUBY_VERSION -require 'anyway/ext/deep_dup' -require 'anyway/ext/deep_freeze' -require 'anyway/ext/hash' -require 'anyway/ext/string_serialize' -require 'anyway/option_parser_builder' +require "anyway/optparse_config" +require "anyway/dynamic_config" +require "anyway/ext/jruby" if defined? JRUBY_VERSION +require "anyway/ext/deep_dup" +require "anyway/ext/deep_freeze" +require "anyway/ext/hash" +require "anyway/ext/string_serialize" + module Anyway # :nodoc: if defined? JRUBY_VERSION using Anyway::Ext::JRuby else using Anyway::Ext::DeepDup @@ -19,112 +21,120 @@ # Base config class # Provides `attr_config` method to describe # configuration parameters and set defaults class Config - class << self - attr_reader :defaults, :config_attributes, :option_parser_extension + include OptparseConfig + include DynamicConfig + class << self def attr_config(*args, **hargs) - @defaults ||= {} - @config_attributes ||= [] - new_defaults = hargs.deep_dup new_defaults.stringify_keys! + defaults.merge! new_defaults new_keys = (args + new_defaults.keys) - config_attributes - @config_attributes += new_keys + config_attributes.push(*new_keys) attr_accessor(*new_keys) end - def config_name(val = nil) - return (@config_name = val.to_s) unless val.nil? + def defaults + return @defaults if instance_variable_defined?(:@defaults) - @config_name = underscore_name unless defined?(@config_name) - @config_name + @defaults = + if superclass < Anyway::Config + superclass.defaults.deep_dup + else + {} + end end - def ignore_options(*args) - args.each do |name| - option_parser_descriptors[name.to_s][:ignore] = true - end - end + def config_attributes + return @config_attributes if instance_variable_defined?(:@config_attributes) - def describe_options(**hargs) - hargs.each do |name, desc| - option_parser_descriptors[name.to_s][:desc] = desc - end + @config_attributes = + if superclass < Anyway::Config + superclass.config_attributes.dup + else + [] + end end - def flag_options(*args) - args.each do |name| - option_parser_descriptors[name.to_s][:flag] = true - end - end + def config_name(val = nil) + return (@explicit_config_name = val.to_s) unless val.nil? - def extend_options(&block) - @option_parser_extension = block + return @config_name if instance_variable_defined?(:@config_name) + + @config_name = explicit_config_name || build_config_name end - def option_parser_options - config_attributes.each_with_object({}) do |key, result| - descriptor = option_parser_descriptors[key.to_s] - next if descriptor[:ignore] == true + def explicit_config_name + return @explicit_config_name if instance_variable_defined?(:@explicit_config_name) - result[key] = descriptor - end + @explicit_config_name = + if superclass.respond_to?(:explicit_config_name) + superclass.explicit_config_name + end end + def explicit_config_name? + !explicit_config_name.nil? + end + def env_prefix(val = nil) - return (@env_prefix = val.to_s) unless val.nil? + return (@env_prefix = val.to_s.upcase) unless val.nil? - @env_prefix - end + return @env_prefix if instance_variable_defined?(:@env_prefix) - # Load config as Hash by any name - # - # Example: - # - # my_config = Anyway::Config.for(:my_app) - # # will load data from config/my_app.yml, secrets.my_app, ENV["MY_APP_*"] - def for(name) - new(name: name, load: false).load_from_sources + @env_prefix = + if superclass < Anyway::Config && superclass.explicit_config_name? + superclass.env_prefix + else + config_name.upcase + end end private - def option_parser_descriptors - @option_parser_descriptors ||= Hash.new { |h, k| h[k] = {} } - end + def build_config_name + unless name + raise "Please, specify config name explicitly for anonymous class " \ + "via `config_name :my_config`" + end - def underscore_name - return unless name + # handle two cases: + # - SomeModule::Config => "some_module" + # - SomeConfig => "some" + unless name =~ /^(\w+)(\:\:)?Config$/ + raise "Couldn't infer config name, please, specify it explicitly" \ + "via `config_name :my_config`" + end - word = name[/^(\w+)/] - word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') - word.downcase! - word + Regexp.last_match[1].tap(&:downcase!) end end attr_reader :config_name, :env_prefix - # Instantiate config with specified name, loads the data and applies overrides + # Instantiate config instance. # # Example: # - # my_config = Anyway::Config.new(name: :my_app, load: true, overrides: { some: :value }) + # my_config = Anyway::Config.new() # - def initialize(name: nil, load: true, overrides: {}) - @config_name = name || self.class.config_name + # # provide some values explicitly + # my_config = Anyway::Config.new({some: :value}) + # + def initialize(overrides = {}) + @config_name = self.class.config_name raise ArgumentError, "Config name is missing" unless @config_name - @env_prefix = self.class.env_prefix || @config_name + @env_prefix = self.class.env_prefix - self.load(overrides) if load + load(overrides) end def reload(overrides = {}) clear load(overrides) @@ -137,74 +147,82 @@ end self end def load(overrides = {}) - config = load_from_sources((self.class.defaults || {}).deep_dup) + base_config = (self.class.defaults || {}).deep_dup - config.merge!(overrides) unless overrides.nil? - config.each do |key, val| + base_config.deep_merge!( + load_from_sources( + name: config_name, + env_prefix: env_prefix, + config_path: resolve_config_path(config_name, env_prefix) + ) + ) + + base_config.merge!(overrides) unless overrides.nil? + + base_config.each do |key, val| set_value(key, val) end end - def load_from_sources(config = {}) - # Handle anonymous configs - return config unless config_name - - load_from_file(config) - load_from_env(config) + def load_from_sources(**options) + base_config = {} + each_source(options) do |config| + base_config.deep_merge!(config) if config + end + base_config end - def load_from_file(config) - config.deep_merge!(parse_yml(config_path) || {}) if config_path && File.file?(config_path) - config + def each_source(options) + yield load_from_file(options) + yield load_from_env(options) end - def load_from_env(config) - config.deep_merge!(env_part) - config - end + def load_from_file(name:, env_prefix:, config_path:, **_options) + file_config = load_from_yml(config_path) - def option_parser - @option_parser ||= begin - parser = OptionParserBuilder.call(self.class.option_parser_options) do |key, arg| - set_value(key, arg.is_a?(String) ? arg.serialize : arg) - end - self.class.option_parser_extension&.call(parser, self) || parser + if Anyway::Settings.use_local_files + local_config_path = config_path.sub(/\.yml/, ".local.yml") + file_config.deep_merge!(load_from_yml(local_config_path)) end + + file_config end - def parse_options!(options) - option_parser.parse!(options) + def load_from_env(name:, env_prefix:, **_options) + Anyway.env.fetch(env_prefix) end def to_h self.class.config_attributes.each_with_object({}) do |key, obj| obj[key.to_sym] = send(key) end.deep_dup.deep_freeze end + def resolve_config_path(name, env_prefix) + Anyway.env.fetch(env_prefix).delete("conf") || default_config_path(name) + end + private - def env_part - Anyway.env.fetch(env_prefix) + def set_value(key, val) + send("#{key}=", val) if respond_to?(key) end - def config_path - env_part.delete('conf') || default_config_path - end + def load_from_yml(path) + return {} unless File.file?(path) - def default_config_path - "./config/#{config_name}.yml" + parse_yml(path) end - def set_value(key, val) - send("#{key}=", val) if respond_to?(key) + def default_config_path(name) + "./config/#{name}.yml" end def parse_yml(path) - require 'yaml' + require "yaml" if defined?(ERB) YAML.safe_load(ERB.new(File.read(path)).result, [], [], true) else YAML.load_file(path) end