# frozen_string_literal: truemoduleYARDmoduleCodeObjects# A MacroObject represents a docstring defined through +@!macro NAME+ and can be# reused by specifying the tag +@!macro NAME+. You can also provide the# +attached+ type flag to the macro definition to have it attached to the# specific DSL method so it will be implicitly reused.## Macros are fully described in the {file:docs/Tags.md#macro Tags Overview}# document.## @example Creating a basic named macro# # @!macro prop# # @!method $1(${3-})# # @return [$2] the value of the $0# property :foo, String, :a, :b## # @!macro prop# property :bar, Numeric, :value## @example Creating a macro that is attached to the method call# # @!macro [attach] prop2# # @!method $1(value)# property :foo## # Extra data added to docstring# property :barclassMacroObject<BaseMACRO_MATCH=/(\\)?\$(?:\{(-?\d+|\*)(-)?(-?\d+)?\}|(-?\d+|\*))/class<<self# Creates a new macro and fills in the relevant properties.# @param [String] macro_name the name of the macro, must be unique.# @param [String] data the data the macro should expand when re-used# @param [CodeObjects::Base] method_object an object to attach this# macro to. If supplied, {#attached?} will be true# @return [MacroObject] the newly created objectdefcreate(macro_name,data,method_object=nil)obj=new(:root,macro_name)obj.macro_data=dataobj.method_object=method_objectobjend# Finds a macro using +macro_name+# @param [#to_s] macro_name the name of the macro# @return [MacroObject] if a macro is found# @return [nil] if there is no registered macro by that namedeffind(macro_name)Registry.at('.macro.'+macro_name.to_s)end# Parses a given docstring and determines if the macro is "new" or# not. If the macro has $variable names or if it has a @!macro tag# with the [new] or [attached] flag, it is considered new.## If a new macro is found, the macro is created and registered. Otherwise# the macro name is searched and returned. If a macro is not found,# nil is returned.## @param [#to_s] macro_name the name of the macro# @param [CodeObjects::Base] method_object an optional method to attach# the macro to. Only used if the macro is being created, otherwise# this argument is ignored.# @return [MacroObject] the newly created or existing macro, depending# on whether the @!macro tag was a new tag or not.# @return [nil] if the +data+ has no macro tag or if the macro is# not new and no macro by the macro name is found.deffind_or_create(macro_name,data,method_object=nil)find(name)||create(macro_name,data,method_object)endaliascreate_docstringfind_or_create# Expands +macro_data+ using the interpolation parameters.## Interpolation rules:# * $0, $1, $2, ... = the Nth parameter in +call_params+# * $* = the full statement source (excluding block)# * Also supports $!{N-M} ranges, as well as negative indexes on N or M# * Use \$ to escape the variable name in a macro.## @!macro [new] macro.expand# @param [Array<String>] call_params the method name and parameters# to the method call. These arguments will fill \$0-N# @param [String] full_source the full source line (excluding block)# interpolated as \$*# @param [String] block_source Currently unused. Will support# interpolating the block data as a variable.# @return [String] the expanded macro data# @param [String] macro_data the macro data to expand (taken from {#macro_data})defexpand(macro_data,call_params=[],full_source='',block_source='')# rubocop:disable Lint/UnusedMethodArgumentmacro_data=macro_data.allifmacro_data.is_a?(Docstring)macro_data.gsub(MACRO_MATCH)doescape=$1first=$2||$5last=$4rng=$3?true:falsenext$&[1..-1]ifescapeiffirst=='*'last?$&:full_sourceelsefirst_i=first.to_ilast_i=(last?last.to_i:call_params.size)last_i=first_iunlessrngparams=call_params[first_i..last_i]params?params.join(", "):''endendend# Applies a macro on a docstring by creating any macro data inside of# the docstring first. Equivalent to calling {find_or_create} and {apply_macro}# on the new macro object.## @param [Docstring] docstring the docstring to create a macro out of# @!macro macro.expand# @see find_or_createdefapply(docstring,call_params=[],full_source='',block_source='',_method_object=nil)# rubocop:disable Lint/UnusedMethodArgumentdocstring=docstring.allifDocstring===docstringparser=Docstring.parserhandler=OpenStruct.newhandler.call_params=call_params[1..-1]handler.caller_method=call_params.firsthandler.statement=OpenStruct.new(:source=>full_source)parser.parse(docstring,nil,handler).to_docstring.to_rawend# Applies a macro to a docstring, interpolating the macro's data on the# docstring and appending any extra local docstring data that was in# the original +docstring+ object.## @param [MacroObject] macro the macro object# @!macro macro.expanddefapply_macro(macro,docstring,call_params=[],full_source='',block_source='')# rubocop:disable Lint/UnusedMethodArgumentapply(docstring,call_params,full_source,block_source)endend# @return [String] the macro data stored on the objectattr_accessor:macro_data# @return [CodeObjects::Base] the method object that this macro is# attached to.attr_accessor:method_object# @return [Boolean] whether this macro is attached to a methoddefattached?;method_object?true:falseend# Overrides {Base#path} so the macro path is ".macro.MACRONAME"defpath;'.macro.'+name.to_send# Overrides the separator to be '.'defsep;'.'end# Expands the macro using# @param [Array<String>] call_params a list of tokens that are passed# to the method call# @param [String] full_source the full method call (not including the block)# @param [String] block_source the source passed in the block of the method# call, if there is a block.# @example Expanding a Macro# macro.expand(%w(property foo bar), 'property :foo, :bar', '') #=># "...macro data interpolating this line of code..."# @see expanddefexpand(call_params=[],full_source='',block_source='')self.class.expand(macro_data,call_params,full_source,block_source)endendendend