class EcoRake module Base module SymbolResolver include EcoRake::Base::MethodHelpers # i.e. value = `:default_value` -> `'DEFAULT_VALUE'` def constant_name(value) case value when String value.strip.upcase when Symbol constant_name(value.to_s) end end # Wraps a symbol to be resolved lazyly. # @note the resolution process goes in this order # 1. First try on the `invoker` and its class. # 2. Final on the `definer` and its class. # @return [Proc] a callback that will resolve the symbol. def symbol_resolver(sym, as: %i[method const]) definer = self proc do |invoker| resolution = invoker.instance_eval do definer.resolve_symbol(sym, source: invoker, as: as) end resolution ||= definer.resolve_symbol(sym, source: definer, as: as) resolution end end # @param sym [Symbol, String] that we try to resolve to a value of a constant. # @param as [Symbol, Array] the order in which it should try to look up. # @return [Method, Value, NilClass] either the value of constant, a method or `nil` # if not found. def resolve_symbol(sym, source: self, as: %i[method const]) as = [as].flatten.compact as.reduce(nil) do |value, type| case type when :const value || resolve_const(sym, source: source) when :method value || resolve_method(sym, source: source) end end end # @param sym [Symbol, String] that we try to resolve to a value of a constant. # @return [Value, NilClass] `nil` when unresolved, the value of the constant otherwise. def resolve_const(sym, source: self) return nil unless sym.is_a?(Symbol) || sym.is_a?(String) sym = constant_name(sym) value = nil value ||= safe_const_get(sym, klass: source) value ||= safe_const_get(sym, klass: Kernel) value rescue NameError nil end # @note it does not raise ErrorName # @return [Value, NilClass] returns `nil` if const not defined or the value of the constant. def safe_const_get(name, klass: self) klass = klass.class unless klass.is_a?(Class) || klass == Kernel klass.const_get(name) if klass.const_defined?(name) rescue NameError nil end # @param sym [Symbol, String] that we try to resolve to a method. # @return [Method, NilClass] `nil` when unresolved, the `method` otherwise. def resolve_method(sym, source: self) return nil unless sym.is_a?(Symbol) || sym.is_a?(String) meth = source.method(sym) if source.respond_to?(sym, true) return meth if meth return nil if source.is_a?(Class) return nil unless source.class.respond_to?(sym, true) source.class.method(sym) end end end end