From cc383e6d1e7c3a2dde5ab91dcf5b6fa7aca53121 Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com" Date: Sat, 10 Jun 2023 08:58:04 +0100 Subject: [PATCH] Refactor function evaluation code to avoid adding evaluateVariable method Adding the new widget method was causing backwards compatibility issues. For example, see this discussion: https://talk.tiddlywiki.org/t/tw-v5-3-0-pre-problem-with-autocomplete-plugin/6958 --- core/modules/filters.js | 3 +- core/modules/filters/function.js | 8 ++- core/modules/filters/unknown.js | 10 ++-- core/modules/widgets/transclude.js | 17 +----- core/modules/widgets/widget.js | 87 ++++++++++-------------------- 5 files changed, 43 insertions(+), 82 deletions(-) diff --git a/core/modules/filters.js b/core/modules/filters.js index b705c994c..d4fd355c6 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -269,7 +269,8 @@ exports.compileFilter = function(filterString) { operand.value = self.getTextReference(operand.text,"",currTiddlerTitle); } else if(operand.variable) { var varTree = $tw.utils.parseFilterVariable(operand.text); - operand.value = widget.evaluateVariable(varTree.name,{params: varTree.params, source: source})[0] || ""; + var variableInfo = widget.getVariableInfo(varTree.name,{params: varTree.params, source: source}); + operand.value = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || ""; } else { operand.value = operand.text; } diff --git a/core/modules/filters/function.js b/core/modules/filters/function.js index f6a8c034d..79210fb78 100644 --- a/core/modules/filters/function.js +++ b/core/modules/filters/function.js @@ -17,9 +17,13 @@ Export our filter function */ exports.function = function(source,operator,options) { var functionName = operator.operands[0], - variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName); + params = []; + $tw.utils.each(operator.operands.slice(1),function(param) { + params.push({value: param}); + }); + var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName,{params: params, source: source}); if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) { - return options.widget.evaluateVariable(functionName,{params: operator.operands.slice(1), source: source}); + return variableInfo.resultList ? variableInfo.resultList : [variableInfo.text]; } // Return the input list if the function wasn't found var results = []; diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js index 1cc5d16e2..6ae5baaf0 100644 --- a/core/modules/filters/unknown.js +++ b/core/modules/filters/unknown.js @@ -22,9 +22,13 @@ Export our filter function exports["[unknown]"] = function(source,operator,options) { // Check for a user defined filter operator if(operator.operator.indexOf(".") !== -1) { - var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator); - if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) { - var list = options.widget.evaluateVariable(operator.operator,{params: operator.operands, source: source}); + var params = []; + $tw.utils.each(operator.operands,function(param) { + params.push({value: param}); + }); + var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator,{params: params, source: source}); + if(variableInfo && variableInfo.srcVariable) { + var list = variableInfo.resultList ? variableInfo.resultList : [variableInfo.text]; if(operator.prefix === "!") { var results = []; source(function(tiddler,title) { diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js index f50e32c64..1117598de 100755 --- a/core/modules/widgets/transclude.js +++ b/core/modules/widgets/transclude.js @@ -179,22 +179,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() { if(variableInfo.text) { if(srcVariable.isFunctionDefinition) { // Function to return parameters by name or position - var fnGetParam = function(name,index) { - // Parameter names starting with dollar must be escaped to double dollars - if(name.charAt(0) === "$") { - name = "$" + name; - } - // Look for the parameter by name - if(self.hasAttribute(name)) { - return self.getAttribute(name); - // Look for the parameter by index - } else if(self.hasAttribute(index + "")) { - return self.getAttribute(index + ""); - } else { - return undefined; - } - }, - result = this.evaluateVariable(this.transcludeVariable,{params: fnGetParam})[0] || ""; + var result = (variableInfo.resultList ? variableInfo.resultList[0] : variableInfo.text) || ""; parser = { tree: [{ type: "text", diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 6f9a8e4e1..8d9c05950 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -112,14 +112,18 @@ Get the prevailing value of a context variable name: name of variable options: see below Options include + params: array of {name:, value:} for each parameter defaultValue: default value if the variable is not defined +source: optional source iterator for evaluating function invocations allowSelfAssigned: if true, includes the current widget in the context chain instead of just the parent Returns an object with the following fields: -params: array of {name:,value:} of parameters passed to wikitext variables +params: array of {name:,value:} or {value:} of parameters to be applied text: text of variable, with parameters properly substituted +resultList: result of variable evaluation as an array +srcVariable: reference to the object defining the variable */ Widget.prototype.getVariableInfo = function(name,options) { options = options || {}; @@ -135,7 +139,8 @@ Widget.prototype.getVariableInfo = function(name,options) { if(variable) { var originalValue = variable.value, value = originalValue, - params = []; + params = [], + resultList = [value]; // Only substitute parameter and variable references if this variable was defined with the \define pragma if(variable.isMacroDefinition) { params = self.resolveVariableParameters(variable.params,actualParams); @@ -144,10 +149,28 @@ Widget.prototype.getVariableInfo = function(name,options) { value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value); }); value = self.substituteVariableReferences(value,options); + resultList = [value]; + } else if(variable.isFunctionDefinition) { + // Function evaluations + params = self.resolveVariableParameters(variable.params,actualParams); + var variables = Object.create(null); + // Apply default parameter values + $tw.utils.each(variable.params,function(param,index) { + if(param["default"]) { + variables[param.name] = param["default"]; + } + }); + // Parameters are an array of {value:} or {name:, value:} pairs + $tw.utils.each(params,function(param) { + variables[param.name] = param.value; + }); + resultList = this.wiki.filterTiddlers(value,this.makeFakeWidgetWithVariables(variables),options.source); + value = resultList[0] || ""; } return { text: value, params: params, + resultList: resultList, srcVariable: variable, isCacheable: originalValue === value }; @@ -159,6 +182,7 @@ Widget.prototype.getVariableInfo = function(name,options) { } return { text: text, + resultList: [text], srcVariable: {} }; }; @@ -317,62 +341,11 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) { }; }, makeFakeWidgetWithVariables: self.makeFakeWidgetWithVariables, - evaluateVariable: self.evaluateVariable, resolveVariableParameters: self.resolveVariableParameters, wiki: self.wiki }; }; -/* -Evaluate a variable and associated actual parameters and return the resulting array. -The way that the variable is evaluated depends upon its type: -* Functions are evaluated as parameterised filter strings -* Macros are returned as plain text with substitution of parameters -* Procedures and widgets are returned as plain text - -Options are: -params - the actual parameters – may be one of: - * an array of values that may be an anonymous string value, or a {name:, value:} pair - * a hashmap of {name: value} pairs - * a function invoked with parameters (name,index) that returns a parameter value by name or position -source - iterator for source tiddlers -*/ -Widget.prototype.evaluateVariable = function(name,options) { - options = options || {}; - var params = options.params || []; - // Get the details of the variable (includes processing text substitution for macros - var variableInfo = this.getVariableInfo(name,{params: params,defaultValue: ""}); - // Process function parameters - var variables = Object.create(null); - if(variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) { - // Apply default parameter values - $tw.utils.each(variableInfo.srcVariable.params,function(param,index) { - if(param["default"]) { - variables[param.name] = param["default"]; - } - }); - if($tw.utils.isArray(params)) { - // Parameters are an array of values or {name:, value:} pairs - $tw.utils.each(this.resolveVariableParameters(variableInfo.srcVariable.params,params),function(param) { - variables[param.name] = param.value; - }); - } else if(typeof params === "function") { - // Parameters are passed via a function - $tw.utils.each(variableInfo.srcVariable.params,function(param,index) { - variables[param.name] = params(param.name,index) || param["default"] || ""; - }); - } else { - // Parameters are a hashmap - $tw.utils.each(params,function(value,name) { - variables[name] = value; - }); - } - return this.wiki.filterTiddlers(variableInfo.text,this.makeFakeWidgetWithVariables(variables),options.source); - } else { - return [variableInfo.text]; - } -}; - /* Compute the current values of the attributes of the widget. Returns a hashmap of the names of the attributes that have changed. Options include: @@ -406,13 +379,7 @@ Widget.prototype.computeAttribute = function(attribute) { value = this.wiki.getTextReference(attribute.textReference,"",this.getVariable("currentTiddler")) || ""; } else if(attribute.type === "macro") { var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params}); - if(variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) { - // It is a function definition. Go through each of the defined parameters, and make a variable with the value of the corresponding provided parameter - var paramArray = this.resolveVariableParameters(variableInfo.srcVariable.params,attribute.value.params); - value = this.evaluateVariable(attribute.value.name,{params: paramArray})[0] || ""; - } else { - value = variableInfo.text; - } + value = variableInfo.text; } else { // String attribute value = attribute.value; }