require'sass/script/functions'moduleSassmoduleScript# A SassScript parse node representing a function call.## A function call either calls one of the functions in {Script::Functions},# or if no function with the given name exists# it returns a string representation of the function call.classFuncall<Node# The name of the function.## @return [String]attr_reader:name# The arguments to the function.## @return [Array<Script::Node>]attr_reader:args# The keyword arguments to the function.## @return [{String => Script::Node}]attr_reader:keywords# @param name [String] See \{#name}# @param args [Array<Script::Node>] See \{#args}# @param keywords [{String => Script::Node}] See \{#keywords}definitialize(name,args,keywords)@name=name@args=args@keywords=keywordssuper()end# @return [String] A string representation of the function calldefinspectargs=@args.map{|a|a.inspect}.join(', ')keywords=@keywords.sort_by{|k,v|k}.map{|k,v|"$#{k}: #{v.inspect}"}.join(', ')"#{name}(#{args}#{', 'unlessargs.empty?||keywords.empty?}#{keywords})"end# @see Node#to_sassdefto_sass(opts={})args=@args.map{|a|a.to_sass(opts)}.join(', ')keywords=@keywords.sort_by{|k,v|k}.map{|k,v|"$#{dasherize(k,opts)}: #{v.to_sass(opts)}"}.join(', ')"#{dasherize(name,opts)}(#{args}#{', 'unlessargs.empty?||keywords.empty?}#{keywords})"end# Returns the arguments to the function.## @return [Array<Node>]# @see Node#childrendefchildren@args+@keywords.valuesend# @see Node#deep_copydefdeep_copynode=dupnode.instance_variable_set('@args',args.map{|a|a.deep_copy})node.instance_variable_set('@keywords',Hash[keywords.map{|k,v|[k,v.deep_copy]}])nodeendprotected# Evaluates the function call.## @param environment [Sass::Environment] The environment in which to evaluate the SassScript# @return [Literal] The SassScript object that is the value of the function call# @raise [Sass::SyntaxError] if the function call raises an ArgumentErrordef_perform(environment)args=@args.map{|a|a.perform(environment)}iffn=environment.function(@name)keywords=Sass::Util.map_hash(@keywords){|k,v|[k,v.perform(environment)]}returnperform_sass_fn(fn,args,keywords)endruby_name=@name.tr('-','_')args=construct_ruby_args(ruby_name,args,environment)unlessFunctions.callable?(ruby_name)opts(to_literal(args))elseopts(Functions::EvaluationContext.new(environment.options).send(ruby_name,*args))endrescueArgumentError=>eraiseeunlesse.backtrace.any?{|t|t=~/:in `(block in )?(#{name}|perform)'$/}raiseSass::SyntaxError.new("#{e.message} for `#{name}'")end# This method is factored out from `_perform` so that compass can override# it with a cross-browser implementation for functions that require vendor prefixes# in the generated css.defto_literal(args)Script::String.new("#{name}(#{args.join(', ')})")endprivatedefconstruct_ruby_args(name,args,environment)unlesssignature=Functions.signature(name.to_sym,args.size,@keywords.size)returnargsifkeywords.empty?raiseSass::SyntaxError.new("Function #{name} doesn't support keyword arguments")endkeywords=Sass::Util.map_hash(@keywords){|k,v|[k,v.perform(environment)]}# If the user passes more non-keyword args than the function expects,# but it does expect keyword args, Ruby's arg handling won't raise an error.# Since we don't want to make functions think about this,# we'll handle it for them here.ifsignature.var_kwargs&&!signature.var_args&&args.size>signature.args.sizeraiseSass::SyntaxError.new("#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'")elsifkeywords.empty?returnargsendargs=args+signature.args[args.size..-1].mapdo|argname|ifkeywords.has_key?(argname)keywords.delete(argname)elseraiseSass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")endendifkeywords.size>0ifsignature.var_kwargsargs<<keywordselseraiseSass::SyntaxError.new("Function #{name} doesn't take an argument named $#{keywords.keys.sort.first}")endendargsenddefperform_sass_fn(function,args,keywords)# TODO: merge with mixin arg evaluation?keywords.eachdo|name,value|# TODO: Make this fastunlessfunction.args.find{|(var,default)|var.underscored_name==name}raiseSass::SyntaxError.new("Function #{@name} doesn't have an argument named $#{name}")endendifargs.size>function.args.sizeraiseArgumentError.new("Wrong number of arguments (#{args.size} for #{function.args.size})")endenvironment=function.args.zip(args).inject(Sass::Environment.new(function.environment))do|env,((var,default),value)|env.set_local_var(var.name,value||keywords[var.underscored_name]||(default&&default.perform(env)))raiseSass::SyntaxError.new("Function #{@name} is missing parameter #{var.inspect}.")unlessenv.var(var.name)envendval=catch:_sass_returndofunction.tree.each{|c|Sass::Tree::Visitors::Perform.visit(c,environment)}raiseSass::SyntaxError.new("Function #{@name} finished without @return")endvalendendendend