# frozen_string_literal: true module Micro module Attributes module With end module Features require 'micro/attributes/features/diff' require 'micro/attributes/features/accept' require 'micro/attributes/features/accept/strict' require 'micro/attributes/features/initialize' require 'micro/attributes/features/initialize/strict' require 'micro/attributes/features/keys_as_symbol' require 'micro/attributes/features/activemodel_validations' extend self module Name ALL = [ DIFF = 'diff'.freeze, ACCEPT = 'accept'.freeze, INITIALIZE = 'initialize'.freeze, KEYS_AS_SYMBOL = 'keys_as_symbol'.freeze, ACTIVEMODEL_VALIDATIONS = 'activemodel_validations'.freeze ].sort.freeze end module Options KEYS = [ DIFF = 'Diff'.freeze, INIT = 'Initialize'.freeze, ACCEPT = 'Accept'.freeze, INIT_STRICT = 'InitializeStrict'.freeze, ACCEPT_STRICT = 'AcceptStrict'.freeze, KEYS_AS_SYMBOL = 'KeysAsSymbol'.freeze, AM_VALIDATIONS = 'ActiveModelValidations'.freeze ].sort.freeze KEYS_TO_FEATURES = { DIFF => Features::Diff, INIT => Features::Initialize, ACCEPT => Features::Accept, INIT_STRICT => Features::Initialize::Strict, ACCEPT_STRICT => Features::Accept::Strict, KEYS_AS_SYMBOL => Features::KeysAsSymbol, AM_VALIDATIONS => Features::ActiveModelValidations }.freeze NAMES_TO_KEYS = { Name::DIFF => DIFF, Name::ACCEPT => ACCEPT, Name::INITIALIZE => INIT, Name::KEYS_AS_SYMBOL => KEYS_AS_SYMBOL, Name::ACTIVEMODEL_VALIDATIONS => AM_VALIDATIONS }.freeze INIT_INIT_STRICT = "#{INIT}_#{INIT_STRICT}".freeze ACCEPT_ACCEPT_STRICT = "#{ACCEPT}_#{ACCEPT_STRICT}".freeze BuildKey = -> combination do combination.sort.join('_') .sub(INIT_INIT_STRICT, INIT_STRICT) .sub(ACCEPT_ACCEPT_STRICT, ACCEPT_STRICT) end KEYS_TO_MODULES = begin combinations = (1..KEYS.size).map { |n| KEYS.combination(n).to_a }.flatten(1).sort_by { |i| "#{i.size}#{i.join}" } combinations.delete_if { |combination| combination.include?(INIT_STRICT) && !combination.include?(INIT) } combinations.delete_if { |combination| combination.include?(ACCEPT_STRICT) && !combination.include?(ACCEPT) } combinations.each_with_object({}) do |combination, features| included = [ 'def self.included(base)', ' base.send(:include, ::Micro::Attributes)', combination.map { |key| " base.send(:include, ::#{KEYS_TO_FEATURES[key].name})" }, 'end' ].flatten.join("\n") key = BuildKey.call(combination) With.const_set(key, Module.new.tap { |mod| mod.instance_eval(included) }) features[key] = With.const_get(key, false) end.freeze end ACTIVEMODEL_VALIDATION = 'activemodel_validation'.freeze def self.fetch_key(arg) if arg.is_a?(Hash) return ACCEPT_STRICT if arg[:accept] == :strict INIT_STRICT if arg[:initialize] == :strict else str = String(arg) name = str == ACTIVEMODEL_VALIDATION ? Name::ACTIVEMODEL_VALIDATIONS : str KEYS_TO_MODULES.key?(name) ? name : NAMES_TO_KEYS[name] end end INVALID_NAME = [ 'Invalid feature name! Available options: ', Name::ALL.map { |feature_name| ":#{feature_name}" }.join(', ') ].join def self.fetch_keys(args) keys = Array(args).dup.map { |name| fetch_key(name) } raise ArgumentError, INVALID_NAME if keys.empty? || !(keys - KEYS).empty? yield(keys) end def self.remove_base_if_has_strict(keys) keys.delete_if { |key| key == INIT } if keys.include?(INIT_STRICT) keys.delete_if { |key| key == ACCEPT } if keys.include?(ACCEPT_STRICT) end def self.without_keys(keys_to_exclude) keys = (KEYS - keys_to_exclude) keys.delete_if { |key| key == INIT || key == INIT_STRICT } if keys_to_exclude.include?(INIT) keys.delete_if { |key| key == ACCEPT || key == ACCEPT_STRICT } if keys_to_exclude.include?(ACCEPT) keys end def self.fetch_module_by_keys(combination) key = BuildKey.call(combination) KEYS_TO_MODULES.fetch(key) end private_constant :KEYS_TO_FEATURES, :NAMES_TO_KEYS, :INVALID_NAME private_constant :INIT_INIT_STRICT, :ACCEPT_ACCEPT_STRICT, :BuildKey end def all @all ||= self.with(Options::KEYS) end def with(names) Options.fetch_keys(names) do |keys| Options.remove_base_if_has_strict(keys) Options.fetch_module_by_keys(keys) end end def without(names) Options.fetch_keys(names) do |keys| keys_to_fetch = Options.without_keys(keys) return ::Micro::Attributes if keys_to_fetch.empty? Options.fetch_module_by_keys(keys_to_fetch) end end private_constant :Name end end end