# frozen_string_literal: truerequire"set"moduleDocile# @api private## A proxy object with a primary receiver as well as a secondary# fallback receiver.## Will attempt to forward all method calls first to the primary receiver,# and then to the fallback receiver if the primary does not handle that# method.## This is useful for implementing DSL evaluation in the context of an object.## @see Docile.dsl_eval## rubocop:disable Style/MissingRespondToMissingclassFallbackContextProxy# The set of methods which will **not** be proxied, but instead answered# by this object directly.NON_PROXIED_METHODS=Set[:__send__,:object_id,:__id__,:==,:equal?,:!,:!=,:instance_exec,:instance_variables,:instance_variable_get,:instance_variable_set,:remove_instance_variable]# The set of methods which will **not** fallback from the block's context# to the dsl object.NON_FALLBACK_METHODS=Set[:class,:self,:respond_to?,:instance_of?]# The set of instance variables which are local to this object and hidden.# All other instance variables will be copied in and out of this object# from the scope in which this proxy was created.NON_PROXIED_INSTANCE_VARIABLES=Set[:@__receiver__,:@__fallback__]# Undefine all instance methods except those in {NON_PROXIED_METHODS}instance_methods.eachdo|method|undef_method(method)unlessNON_PROXIED_METHODS.include?(method.to_sym)end# @param [Object] receiver the primary proxy target to which all methods# initially will be forwarded# @param [Object] fallback the fallback proxy target to which any methods# not handled by `receiver` will be forwardeddefinitialize(receiver,fallback)@__receiver__=receiver@__fallback__=fallback# Enables calling DSL methods from helper methods in the block's contextunlessfallback.respond_to?(:method_missing)# NOTE: We could switch to {#define_singleton_method} on current Rubiessingleton_class=(class<<fallback;self;end)# instrument {#method_missing} on the block's context to fallback to# the DSL object. This allows helper methods in the block's context to# contain calls to methods on the DSL object.singleton_class.send(:define_method,:method_missing)do|method,*args,&block|m=method.to_symif!NON_FALLBACK_METHODS.member?(m)&&!fallback.respond_to?(m)&&receiver.respond_to?(m)receiver.__send__(method.to_sym,*args,&block)elsesuper(method,*args,&block)endendifsingleton_class.respond_to?(:ruby2_keywords,true)singleton_class.send(:ruby2_keywords,:method_missing)end# instrument a helper method to remove the above instrumentationsingleton_class.send(:define_method,:__docile_undo_fallback__)dosingleton_class.send(:remove_method,:method_missing)singleton_class.send(:remove_method,:__docile_undo_fallback__)endendend# @return [Array<Symbol>] Instance variable names, excluding# {NON_PROXIED_INSTANCE_VARIABLES}definstance_variablessuper.reject{|v|NON_PROXIED_INSTANCE_VARIABLES.include?(v)}end# Proxy all methods, excluding {NON_PROXIED_METHODS}, first to `receiver`# and then to `fallback` if not found.defmethod_missing(method,*args,&block)if@__receiver__.respond_to?(method.to_sym)@__receiver__.__send__(method.to_sym,*args,&block)elsebegin@__fallback__.__send__(method.to_sym,*args,&block)rescueNoMethodError=>ee.extend(BacktraceFilter)raiseeendendendruby2_keywords:method_missingifrespond_to?(:ruby2_keywords,true)end# rubocop:enable Style/MissingRespondToMissingend