1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-23 03:14:40 +00:00

Compare commits

...

45 Commits

Author SHA1 Message Date
Jeremy Ruston
ae68650e93 Docs updates 2025-11-22 12:25:59 +00:00
Jeremy Ruston
91fc7bb6cd Update let.js 2025-11-22 12:03:53 +00:00
Jeremy Ruston
5d8f613df8 Typo 2025-11-22 12:03:46 +00:00
Jeremy Ruston
5cce338206 Fix filenames of tests 2025-11-22 12:03:34 +00:00
Jeremy Ruston
1c1547fe59 Add change note 2025-11-22 11:37:54 +00:00
Jeremy Ruston
a5648f829d Merge branch 'master' into let-filter-run-prefix 2025-11-22 11:31:37 +00:00
Jeremy Ruston
5209f212f4 Another incorrect version number 2025-11-22 11:31:31 +00:00
Jeremy Ruston
f2d5c2d83d Fix version numbers of from-version procedures 2025-11-22 11:20:15 +00:00
Jeremy Ruston
ac7837a98f Happy linter happy life 2025-11-06 22:45:26 +00:00
Jeremy Ruston
eaaa36fb41 Fix typos 2025-11-06 22:43:22 +00:00
Jeremy Ruston
d4684c2811 Merge branch 'master' into let-filter-run-prefix 2025-11-06 22:43:08 +00:00
Jeremy Ruston
f2b60d4803 Docs updates 2025-04-30 17:03:40 +01:00
Jeremy Ruston
f371a59c75 Merge branch 'master' into let-filter-run-prefix 2025-04-30 17:03:00 +01:00
Jeremy Ruston
c6b499af98 Update action-log widget to log multi-valued attributes 2025-03-30 21:44:43 +01:00
Jeremy Ruston
f3f232e634 Remove duplicate test 2025-03-30 19:42:39 +01:00
Jeremy Ruston
2c50345653 Extend title operator to allow negated form to use multi-valued variables 2025-03-29 15:35:31 +00:00
Jeremy Ruston
9453e3e2e2 Simplify title operator 2025-03-29 14:45:35 +00:00
Jeremy Ruston
d20b02e606 Allow functions to take multivalued parameters 2025-03-29 14:13:23 +00:00
Jeremy Ruston
22eea56b53 Fix bug with round brackets for 2nd parameter onwards 2025-03-29 12:50:43 +00:00
Jeremy Ruston
e8a2b3eefa Merge branch 'master' into let-filter-run-prefix 2025-03-29 12:32:27 +00:00
Jeremy Ruston
b6a171cf5e Fix bug with missing variable attributes
See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2752792329
2025-03-26 20:32:35 +00:00
Jeremy Ruston
f15e9e309e Merge branch 'master' into let-filter-run-prefix 2025-03-21 17:23:27 +00:00
Jeremy Ruston
f914aa6605 Fix bug exposed in "Filter Operators" tiddler
See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2740003414
2025-03-20 17:46:12 +00:00
Jeremy Ruston
bb5061c6ff Introduce => as a shortcut syntax for the let filter run prefix
Also relax the requirement for a filter run prefix to be followed by an opening square bracket
2025-03-20 09:29:15 +00:00
Jeremy Ruston
0c24e2f5f1 Introduce round brackets for multi-valued filter operands
Allowing us to drop the varlist operator
2025-03-19 16:14:54 +00:00
Jeremy Ruston
938e72dc34 Bug fix 2025-03-19 11:00:03 +00:00
Jeremy Ruston
7640135a7e Docs update 2025-03-18 16:29:11 +00:00
Jeremy Ruston
ac134ac549 Missed off 211b135265 2025-03-18 15:54:51 +00:00
Jeremy Ruston
211b135265 Refactor the let filter run prefix to assign the input list to the variable named by the filter run
These semantics are much simpler, and allow the variable name to be computed.
2025-03-18 15:53:28 +00:00
Jeremy Ruston
0db188f365 Dispense with the letlist widget
What this PR actually does is rename the letlist widget to "let". The result is the same as using the letlist widget, but it is backwards compatible by virtue of the fact that all existing ways to access variables will only see the single value for the variable.
2025-03-18 12:46:31 +00:00
Jeremy Ruston
a31df85894 varlist operator: fixed crash accessing non-existent variable
See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2712068743
2025-03-11 13:59:19 +00:00
Jeremy Ruston
d69ecf4639 Test for multivalued functions 2025-03-11 13:30:17 +00:00
Jeremy Ruston
efce6731e1 Docs update 2025-03-11 12:49:40 +00:00
Jeremy Ruston
a9a9a745d4 Switch to triple brace syntax for assigning filtered lists 2025-03-11 12:47:01 +00:00
Jeremy Ruston
4cfa758d51 Fixed varlist crash with empty parameter 2025-03-10 15:31:28 +00:00
Jeremy Ruston
c5ad429493 Update DefaultTiddlers to highlight the new docs 2025-03-10 12:45:10 +00:00
Jeremy Ruston
76e1c2124b Docs update 2025-03-09 18:21:36 +00:00
Jeremy Ruston
78a7aedd4b Introduce letlist widget for assigning lists to variables
Terrible name. Annoyingly, we can't overload the existing let or set widgets.
2025-03-09 17:50:42 +00:00
Jeremy Ruston
2a1542c4e5 Docs update 2025-03-09 17:01:31 +00:00
Jeremy Ruston
7397f4fa3a Support for saving result lists in a variable
Extend let filter run prefix to store list of results, and add varlist operator for accessing variables as a list.

We already had partial support for variables returning a list of values in order for functions to work, now we extend it so that any variable can be used to store a list

We should extend the set widget so that it returns a result list that can be accessed with the varlist operator
2025-03-09 16:50:48 +00:00
Jeremy Ruston
e7b713c277 Cleanup 2025-03-09 14:56:15 +00:00
Jeremy Ruston
8e9b30fec8 Fix tests 2025-03-08 09:05:59 +00:00
Jeremy Ruston
db1ed0e66b Fix tests 2025-03-08 09:00:32 +00:00
Jeremy Ruston
1acef48a10 Get rid of the special behaviour for all[]
Not needed because the input to the filter run is available
2025-03-08 08:56:12 +00:00
Jeremy Ruston
b8bdb0aeeb Introduce let filter run prefix for assigning filter run result to a variable 2025-03-07 21:48:36 +00:00
33 changed files with 530 additions and 95 deletions

View File

@@ -0,0 +1,41 @@
/*\
title: $:/core/modules/filterrunprefixes/let.js
type: application/javascript
module-type: filterrunprefix
Assign a value to a variable
\*/
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter prefix function
*/
exports.let = function(operationSubFunction,options) {
// Return the filter run prefix function
return function(results,source,widget) {
// Save the result list
var resultList = results.toArray();
// Clear the results
results.clear();
// Evaluate the subfunction to get the variable name
var subFunctionResults = operationSubFunction(source,widget);
if(subFunctionResults.length === 0) {
return;
}
var name = subFunctionResults[0];
if(typeof name !== "string" || name.length === 0) {
return;
}
// Assign the result of the subfunction to the variable
var variables = {};
variables[name] = resultList;
// Return the variables
return {
variables: variables
};
};
};

View File

@@ -35,7 +35,7 @@ function parseFilterOperation(operators,filterString,p) {
operator.prefix = filterString.charAt(p++);
}
// Get the operator name
nextBracketPos = filterString.substring(p).search(/[\[\{<\/]/);
nextBracketPos = filterString.substring(p).search(/[\[\{<\/\(]/);
if(nextBracketPos === -1) {
throw "Missing [ in filter expression";
}
@@ -79,6 +79,10 @@ function parseFilterOperation(operators,filterString,p) {
operand.variable = true;
nextBracketPos = filterString.indexOf(">",p);
break;
case "(": // Round brackets
operand.multiValuedVariable = true;
nextBracketPos = filterString.indexOf(")",p);
break;
case "/": // regexp brackets
var rex = /^((?:[^\\\/]|\\.)*)\/(?:\(([mygi]+)\))?/g,
rexMatch = rex.exec(filterString.substring(p));
@@ -112,7 +116,7 @@ function parseFilterOperation(operators,filterString,p) {
// Check for multiple operands
while(filterString.charAt(p) === ",") {
p++;
if(/^[\[\{<\/]/.test(filterString.substring(p))) {
if(/^[\[\{<\/\(]/.test(filterString.substring(p))) {
nextBracketPos = p;
p++;
parseOperand(filterString.charAt(nextBracketPos));
@@ -141,7 +145,15 @@ exports.parseFilter = function(filterString) {
p = 0, // Current position in the filter string
match;
var whitespaceRegExp = /(\s+)/mg,
operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
// Groups:
// 1 - entire filter run prefix
// 2 - filter run prefix itself
// 3 - filter run prefix suffixes
// 4 - opening square bracket following filter run prefix
// 5 - double quoted string following filter run prefix
// 6 - single quoted string following filter run prefix
// 7 - anything except for whitespace and square brackets
operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
while(p < filterString.length) {
// Skip any whitespace
whitespaceRegExp.lastIndex = p;
@@ -152,38 +164,45 @@ exports.parseFilter = function(filterString) {
// Match the start of the operation
if(p < filterString.length) {
operandRegExp.lastIndex = p;
match = operandRegExp.exec(filterString);
if(!match || match.index !== p) {
throw $tw.language.getString("Error/FilterSyntax");
}
var operation = {
prefix: "",
operators: []
};
if(match[1]) {
operation.prefix = match[1];
p = p + operation.prefix.length;
if(match[2]) {
operation.namedPrefix = match[2];
}
if(match[3]) {
operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) {
operation.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
if(entry) {
operation.suffixes[operation.suffixes.length -1].push(entry);
}
match = operandRegExp.exec(filterString);
if(match && match.index === p) {
// If there is a filter run prefix
if(match[1]) {
operation.prefix = match[1];
p = p + operation.prefix.length;
// Name for named prefixes
if(match[2]) {
operation.namedPrefix = match[2];
}
// Suffixes for filter run prefix
if(match[3]) {
operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) {
operation.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
if(entry) {
operation.suffixes[operation.suffixes.length -1].push(entry);
}
});
});
});
}
}
// Opening square bracket
if(match[4]) {
p = parseFilterOperation(operation.operators,filterString,p);
} else {
p = match.index + match[0].length;
}
}
if(match[4]) { // Opening square bracket
p = parseFilterOperation(operation.operators,filterString,p);
} else {
p = match.index + match[0].length;
// No filter run prefix
p = parseFilterOperation(operation.operators,filterString,p);
}
// Quoted strings and unquoted title
if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title
operation.operators.push(
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
@@ -251,6 +270,8 @@ exports.compileFilter = function(filterString) {
results = [];
$tw.utils.each(operation.operators,function(operator) {
var operands = [],
multiValueOperands = [],
isMultiValueOperand = [],
operatorFunction;
if(!operator.operator) {
// Use the "title" operator if no operator is specified
@@ -266,13 +287,29 @@ exports.compileFilter = function(filterString) {
if(operand.indirect) {
var currTiddlerTitle = widget && widget.getVariable("currentTiddler");
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
operand.multiValue = [operand.value];
} else if(operand.variable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || "";
operand.multiValue = [operand.value];
} else if(operand.multiValuedVariable) {
var varTree = $tw.utils.parseFilterVariable(operand.text);
var resultList = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source});
if((resultList.length > 0 && resultList[0] !== undefined) || resultList.length === 0) {
operand.multiValue = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source}) || [];
operand.value = operand.multiValue[0] || "";
} else {
operand.value = "";
operand.multiValue = [];
}
operand.isMultiValueOperand = true;
} else {
operand.value = operand.text;
operand.multiValue = [operand.value];
}
operands.push(operand.value);
multiValueOperands.push(operand.multiValue);
isMultiValueOperand.push(!!operand.isMultiValueOperand);
});
// Invoke the appropriate filteroperator module
@@ -280,6 +317,8 @@ exports.compileFilter = function(filterString) {
operator: operator.operator,
operand: operands.length > 0 ? operands[0] : undefined,
operands: operands,
multiValueOperands: multiValueOperands,
isMultiValueOperand: isMultiValueOperand,
prefix: operator.prefix,
suffix: operator.suffix,
suffixes: operator.suffixes,
@@ -319,6 +358,8 @@ exports.compileFilter = function(filterString) {
return filterRunPrefixes["and"](operationSubFunction, options);
case "~": // This operation is unioned into the result only if the main result so far is empty
return filterRunPrefixes["else"](operationSubFunction, options);
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
return filterRunPrefixes["let"](operationSubFunction, options);
default:
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
@@ -345,7 +386,13 @@ exports.compileFilter = function(filterString) {
self.filterRecursionCount = (self.filterRecursionCount || 0) + 1;
if(self.filterRecursionCount < MAX_FILTER_DEPTH) {
$tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,widget);
var operationResult = operationFunction(results,source,widget);
if(operationResult) {
if(operationResult.variables) {
// If the filter run prefix has returned variables, create a new fake widget with those variables
widget = widget.makeFakeWidgetWithVariables(operationResult.variables);
}
}
});
} else {
results.push("/**-- Excessive filter recursion --**/");

View File

@@ -16,8 +16,8 @@ exports.function = function(source,operator,options) {
var functionName = operator.operands[0],
params = [],
results;
$tw.utils.each(operator.operands.slice(1),function(param) {
params.push({value: param});
$tw.utils.each(operator.multiValueOperands.slice(1),function(paramList) {
params.push({value: paramList[0] || "",multiValue: paramList});
});
// console.log(`Calling ${functionName} with params ${JSON.stringify(params)}`);
var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName,{params: params, source: source});

View File

@@ -16,12 +16,13 @@ exports.title = function(source,operator,options) {
var results = [];
if(operator.prefix === "!") {
source(function(tiddler,title) {
if(tiddler && tiddler.fields.title !== operator.operand) {
var titleList = operator.multiValueOperands[0] || [];
if(tiddler && titleList.indexOf(tiddler.fields.title) === -1) {
results.push(title);
}
});
} else {
results.push(operator.operand);
Array.prototype.push.apply(results,operator.multiValueOperands[0]);
}
return results;
};

View File

@@ -20,8 +20,8 @@ exports["[unknown]"] = function(source,operator,options) {
// Check for a user defined filter operator
if(operator.operator.indexOf(".") !== -1) {
var params = [];
$tw.utils.each(operator.operands,function(param) {
params.push({value: param});
$tw.utils.each(operator.multiValueOperands,function(paramList) {
params.push({value: paramList[0] || "",multiValue: paramList});
});
var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator,{params: params, source: source});
if(variableInfo && variableInfo.srcVariable) {

View File

@@ -49,14 +49,26 @@ exports.warning = function(text) {
};
/*
Log a table of name: value pairs
Log a table of name: value or name: [values...] pairs
*/
exports.logTable = function(data) {
if(console.table) {
var hasArrays = false;
$tw.utils.each(data,function(value,name) {
if($tw.utils.isArray(value)) {
hasArrays = true;
}
});
if(console.table && !hasArrays) {
console.table(data);
} else {
$tw.utils.each(data,function(value,name) {
console.log(name + ": " + value);
if($tw.utils.isArray(value)) {
for(var t=0; t<value.length; t++) {
console.log(`${name}[${t}]: ${value[t]}`);
}
} else {
console.log(`${name}: ${value}`);
}
});
}
}

View File

@@ -51,23 +51,29 @@ LogWidget.prototype.invokeAction = function(triggeringWidget,event) {
};
LogWidget.prototype.log = function() {
var data = {},
var self = this,
data = {}, // Hashmap by attribute name with string or array of string values
dataCount,
allVars = {},
allVars = {}, // Hashmap by variable name with string or array of string values
filteredVars;
$tw.utils.each(this.attributes,function(attribute,name) {
// Collect the attributes to be logged
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(name.substring(0,2) !== "$$") {
data[name] = attribute;
var resultList = self.computeAttribute(attribute,{asList: true});
if(resultList.length <= 1) {
data[name] = resultList[0] || "";
} else {
data[name] = resultList;
}
}
});
// Collect values of all variables, using the source text for functions
for(var v in this.variables) {
var variable = this.parentWidget && this.parentWidget.variables[v];
if(variable && variable.isFunctionDefinition) {
allVars[v] = variable.value;
var variableInfo = this.getVariableInfo(v);
if(variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
allVars[v] = variableInfo.text;
} else {
allVars[v] = this.getVariable(v,{defaultValue:""});
allVars[v] = variableInfo.resultList.length > 1 ? variableInfo.resultList : variableInfo.text;
}
}
if(this.filter) {

View File

@@ -46,7 +46,7 @@ LetWidget.prototype.computeAttributes = function() {
self = this;
this.currentValueFor = Object.create(null);
$tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(this.parseTreeNode),function(attribute) {
var value = self.computeAttribute(attribute),
var value = self.computeAttribute(attribute,{asList: true}),
name = attribute.name;
// Now that it's prepped, we're allowed to look this variable up
// when defining later variables
@@ -56,7 +56,7 @@ LetWidget.prototype.computeAttributes = function() {
});
// Run through again, setting variables and looking for differences
$tw.utils.each(this.currentValueFor,function(value,name) {
if (self.attributes[name] !== value) {
if(!$tw.utils.isArrayEqual(self.attributes[name],value)) {
self.attributes[name] = value;
self.setVariable(name,value);
changedAttributes[name] = true;
@@ -69,8 +69,10 @@ LetWidget.prototype.getVariableInfo = function(name,options) {
// Special handling: If this variable exists in this very $let, we can
// use it, but only if it's been staged.
if ($tw.utils.hop(this.currentValueFor,name)) {
var value = this.currentValueFor[name];
return {
text: this.currentValueFor[name]
text: value[0] || "",
resultList: value
};
}
return Widget.prototype.getVariableInfo.call(this,name,options);

View File

@@ -80,7 +80,7 @@ Widget.prototype.execute = function() {
/*
Set the value of a context variable
name: name of the variable
value: value of the variable
value: value of the variable, can be a string or an array
params: array of {name:, default:} for each parameter
isMacroDefinition: true if the variable is set via a \define macro pragma (and hence should have variable substitution performed)
options includes:
@@ -90,8 +90,10 @@ options includes:
*/
Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) {
options = options || {};
var valueIsArray = $tw.utils.isArray(value);
this.variables[name] = {
value: value,
value: valueIsArray ? (value[0] || "") : value,
resultList: valueIsArray ? value : [value],
params: params,
isMacroDefinition: !!isMacroDefinition,
isFunctionDefinition: !!options.isFunctionDefinition,
@@ -114,7 +116,7 @@ allowSelfAssigned: if true, includes the current widget in the context chain ins
Returns an object with the following fields:
params: array of {name:,value:} or {value:} of parameters to be applied
params: array of {name:,value:,multiValue:} of parameters to be applied (name is optional)
text: text of variable, with parameters properly substituted
resultList: result of variable evaluation as an array
srcVariable: reference to the object defining the variable
@@ -140,7 +142,9 @@ Widget.prototype.getVariableInfo = function(name,options) {
params = self.resolveVariableParameters(variable.params,actualParams);
// Substitute any parameters specified in the definition
$tw.utils.each(params,function(param) {
value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value);
if("name" in param) {
value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value);
}
});
value = self.substituteVariableReferences(value,options);
resultList = [value];
@@ -154,13 +158,20 @@ Widget.prototype.getVariableInfo = function(name,options) {
variables[param.name] = param["default"];
}
});
// Parameters are an array of {value:} or {name:, value:} pairs
// Parameters are an array of {name:, value:, multivalue:} pairs (name and multivalue are optional)
$tw.utils.each(params,function(param) {
variables[param.name] = param.value;
if(param.multiValue) {
variables[param.name] = param.multiValue;
} else {
variables[param.name] = param.value || "";
}
});
resultList = this.wiki.filterTiddlers(value,this.makeFakeWidgetWithVariables(variables),options.source);
value = resultList[0] || "";
} else {
if(variable.resultList) {
resultList = variable.resultList;
}
params = variable.params;
}
return {
@@ -192,22 +203,24 @@ Widget.prototype.getVariable = function(name,options) {
/*
Maps actual parameters onto formal parameters, returning an array of {name:,value:} objects
formalParams - Array of {name:,default:} (default value is optional)
actualParams - Array of string values or {name:,value:} (name is optional)
actualParams - Array of string values or {name:,value:,multiValue} (name and multiValue is optional)
*/
Widget.prototype.resolveVariableParameters = function(formalParams,actualParams) {
formalParams = formalParams || [];
actualParams = actualParams || [];
var nextAnonParameter = 0, // Next candidate anonymous parameter in macro call
paramInfo, paramValue,
paramInfo, paramValue, paramMultiValue,
results = [];
// Step through each of the parameters in the macro definition
for(var p=0; p<formalParams.length; p++) {
// Check if we've got a macro call parameter with the same name
paramInfo = formalParams[p];
paramValue = undefined;
paramMultiValue = undefined;
for(var m=0; m<actualParams.length; m++) {
if(typeof actualParams[m] !== "string" && actualParams[m].name === paramInfo.name) {
paramValue = actualParams[m].value;
paramMultiValue = actualParams[m].multiValue || [paramValue]
}
}
// If not, use the next available anonymous macro call parameter
@@ -217,11 +230,13 @@ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams)
if(paramValue === undefined && nextAnonParameter < actualParams.length) {
var param = actualParams[nextAnonParameter++];
paramValue = typeof param === "string" ? param : param.value;
paramMultiValue = typeof param === "string" ? [param] : (param.multiValue || [paramValue]);
}
// If we've still not got a value, use the default, if any
paramValue = paramValue || paramInfo["default"] || "";
paramMultiValue = paramMultiValue || [paramValue];
// Store the parameter name and value
results.push({name: paramInfo.name, value: paramValue});
results.push({name: paramInfo.name, value: paramValue, multiValue: paramMultiValue});
}
return results;
};
@@ -310,7 +325,7 @@ Widget.prototype.getStateQualifier = function(name) {
};
/*
Make a fake widget with specified variables, suitable for variable lookup in filters
Make a fake widget with specified variables, suitable for variable lookup in filters. Each variable can be a string or an array of strings
*/
Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
var self = this,
@@ -318,7 +333,12 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
return {
getVariable: function(name,opts) {
if($tw.utils.hop(variables,name)) {
return variables[name];
var value = variables[name];
if($tw.utils.isArray(value)) {
return value[0];
} else {
return value;
}
} else {
opts = opts || {};
opts.variables = variables;
@@ -327,9 +347,18 @@ Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
},
getVariableInfo: function(name,opts) {
if($tw.utils.hop(variables,name)) {
return {
text: variables[name]
};
var value = variables[name];
if($tw.utils.isArray(value)) {
return {
text: value[0],
resultList: value
};
} else {
return {
text: value,
resultList: [value]
};
}
} else {
opts = opts || {};
opts.variables = $tw.utils.extend({},variables,opts.variables);
@@ -366,20 +395,45 @@ Widget.prototype.computeAttributes = function(options) {
return changedAttributes;
};
Widget.prototype.computeAttribute = function(attribute) {
/*
Compute the value of a single attribute. Options include:
asList: boolean if true returns results as an array instead of a single value
*/
Widget.prototype.computeAttribute = function(attribute,options) {
options = options || {};
var self = this,
value;
if(attribute.type === "filtered") {
value = this.wiki.filterTiddlers(attribute.filter,this)[0] || "";
value = this.wiki.filterTiddlers(attribute.filter,this);
if(!options.asList) {
value = value[0] || "";
}
} else if(attribute.type === "indirect") {
value = this.wiki.getTextReference(attribute.textReference,"",this.getVariable("currentTiddler")) || "";
value = this.wiki.getTextReference(attribute.textReference,"",this.getVariable("currentTiddler"));
if(value && options.asList) {
value = [value];
}
} else if(attribute.type === "macro") {
var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
value = variableInfo.text;
if(options.asList) {
value = variableInfo.resultList;
} else {
value = variableInfo.text;
}
} else if(attribute.type === "substituted") {
value = this.wiki.getSubstitutedText(attribute.rawValue,this) || "";
if(options.asList) {
value = [value];
}
} else { // String attribute
value = attribute.value;
if(options.asList) {
if(value === undefined) {
value = [];
} else {
value = [value];
}
}
}
return value;
};

View File

@@ -2,12 +2,5 @@ created: 20131127215321439
modified: 20140912135951542
title: $:/DefaultTiddlers
[[Hire the founder of TiddlyWiki]]
[[TiddlyWiki Pre-release]]
HelloThere
[[Quick Start]]
[[Find Out More]]
[[TiddlyWiki on the Web]]
[[Testimonials and Reviews]]
GettingStarted
Community
[[Multi-Valued Variables]]
[[Let Filter Run Prefix]]

View File

@@ -0,0 +1,12 @@
title: LetFilterRunPrefix/ResultList
description: Using the "let" filter run prefix to store result lists, not just single values
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$text text={{{ [all[tiddlers]] :let[[varname]] [(varname)sort[]join[,]] }}}/>
+
title: ExpectedResult
<p>$:/core,ExpectedResult,Output</p>

View File

@@ -0,0 +1,12 @@
title: LetFilterRunPrefix/ResultListUnnamedVariable
description: Using the "let" filter run prefix to store result lists, not just single values
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$text text={{{ [all[tiddlers]] :let[all[]tag[nothing]] [(varname)sort[]join[,]] }}}/>
+
title: ExpectedResult
<p></p>

View File

@@ -0,0 +1,12 @@
title: LetFilterRunPrefix/ShortcutSyntax
description: Simple usage of "let" filter run prefix
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$text text={{{ [[magpie]] =>varname [<varname>] +[join[-]] }}}/>
+
title: ExpectedResult
<p>magpie</p>

View File

@@ -0,0 +1,12 @@
title: LetFilterRunPrefix/Simple
description: Simple usage of "let" filter run prefix
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$text text={{{ [[magpie]] :let[[varname]] [<varname>] +[join[-]] }}}/>
+
title: ExpectedResult
<p>magpie</p>

View File

@@ -0,0 +1,18 @@
title: MultiValuedVariables/Function
description: Multi-valued functions
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\function myfunc() [all[tiddlers]sort[]]
<$let varname=<<myfunc>>>
<$text text={{{ [(varname)] +[join[-]] }}}/>
</$let>
+
title: ExpectedResult
<p>
$:/core-ExpectedResult-Output
</p>

View File

@@ -0,0 +1,12 @@
title: MultiValuedVariables/MissingVariable
description: Using multivalued operands with a missing variable
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$text text={{{ [(varname)] }}}/>
+
title: ExpectedResult
<p></p>

View File

@@ -0,0 +1,18 @@
title: MultiValuedVariables/NegatedTitle
description: Multi-valued operands
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$let
exclude={{{ $:/core Output }}}
>
<$text text={{{ [all[tiddlers]!title(exclude)] +[join[-]] }}}/>
</$let>
+
title: ExpectedResult
<p>
ExpectedResult
</p>

View File

@@ -0,0 +1,18 @@
title: MultiValuedVariables/Operands
description: Multi-valued operands
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\function myfunc() [all[tiddlers]sort[]]
<$let varname=<<myfunc>>>
<$text text={{{ [(varname)] +[join[-]] }}}/>
</$let>
+
title: ExpectedResult
<p>
$:/core-ExpectedResult-Output
</p>

View File

@@ -0,0 +1,21 @@
title: MultiValuedVariables/Parameters
description: Multi-valued function parameters
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\function myfunc(input) [(input)sort[]]
<$let
input={{{ [all[tiddlers]] }}}
output={{{ [function[myfunc],(input)] }}}
>
<$text text={{{ [(output)] +[join[-]] }}}/>
</$let>
+
title: ExpectedResult
<p>
$:/core-ExpectedResult-Output
</p>

View File

@@ -0,0 +1,21 @@
title: MultiValuedVariables/ParametersShortcut
description: Multi-valued function parameters using shortcut syntax
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\function my.func(input) [(input)sort[]]
<$let
input={{{ [all[tiddlers]] }}}
output={{{ [my.func(input)] }}}
>
<$text text={{{ [(output)] +[join[-]] }}}/>
</$let>
+
title: ExpectedResult
<p>
$:/core-ExpectedResult-Output
</p>

View File

@@ -0,0 +1,17 @@
title: MultiValuedVariables/Simple
description: Simple usage of multivalued assignments with the "let" widget
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
<$let varname={{{ [all[tiddlers]sort[]] }}}
varname2=<<varname>>>
<$text text={{{ [(varname2)] +[join[-]] }}}/>
</$let>
+
title: ExpectedResult
<p>
$:/core-ExpectedResult-Output
</p>

View File

@@ -60,10 +60,10 @@ describe("Widget module", function() {
childNode = childNode.children[0];
}
expect(childNode.getVariableInfo("macro",{allowSelfAssigned:true}).params).toEqual([{name:"a",value:"aa"}]);
expect(childNode.getVariableInfo("macro",{allowSelfAssigned:true}).params).toEqual([{name:"a",value:"aa",multiValue:["aa"]}]);
// function params
expect(childNode.getVariableInfo("fn", {allowSelfAssigned:true}).params).toEqual([{name:"f",value:"ff"}]);
expect(childNode.getVariableInfo("fn", {allowSelfAssigned:true}).params).toEqual([{name:"f",value:"ff",multiValue:["ff"]}]);
// functions have a text and a value
expect(childNode.getVariableInfo("x", {allowSelfAssigned:true}).text).toBe("fff");
expect(childNode.getVariableInfo("x", {allowSelfAssigned:true}).srcVariable.value).toBe("[<fn>]");
@@ -73,7 +73,7 @@ describe("Widget module", function() {
expect(childNode.getVariableInfo("$my.widget", {allowSelfAssigned:true}).params).toEqual([{name:"w",default:"ww"}]);
// no params expected
expect(childNode.getVariableInfo("abc", {allowSelfAssigned:true})).toEqual({text:"def"});
expect(childNode.getVariableInfo("abc", {allowSelfAssigned:true})).toEqual({text:"def", resultList: [ "def" ]});
// debugger; Find code in browser

View File

@@ -14,6 +14,7 @@ In technical / logical terms:
|`-[run]` |`:except[run]` |difference of sets |... AND NOT run |
|`~[run]` |`:else[run]` |else |... ELSE run |
|`=[run]` |`:all[run]` |union of sets without de-duplication |... OR run |
|`=>[run]` |`:let[run]` |<<.from-version "5.4.0">> assign results to a variable |... LET run |
The input of a run is normally a list of all the non-[[shadow|ShadowTiddlers]] tiddler titles in the wiki (in no particular order).<br>But the `+` prefix can change this:

View File

@@ -0,0 +1,28 @@
created: 20250307212252946
from-version: 5.4.0
modified: 20250307212252946
rp-input: all titles from previous filter runs
rp-output: an empty title list is always returned from the "let" filter run prefix
rp-purpose: assign the title list resulting from previous filter runs to a multi-valued variable
tags: [[Named Filter Run Prefix]]
title: Let Filter Run Prefix
type: text/vnd.tiddlywiki
<$railroad text="""
\start none
\end none
( ":let" )
[[run|"Filter Run"]]
"""/>
The `:let` filter run prefix assigns the title list resulting from previous filter runs to a [[multi-valued variable|Multi-Valued Variable]]. The variable is named with the first result returned by the filter run.
The variable is made available to the remaining [[filter runs|Filter Run]] in the [[filter expression|Filter Expression]]. Only the first item in the result list is returned when the variable is accessed in the usual way (or an empty string if the result list is empty). Using round brackets instead of angle brackets around a variable name as an operand retrieves the complete list of items in the result list.
This prefix has an optional [[shortcut syntax|Shortcut Filter Run Prefix]] symbol `=>run`. For example:
```
=[<myfun1>] =[<myfun2>] =>myvar
```
The `:let` filter run prefix always clears the current result list.

View File

@@ -23,6 +23,7 @@ A named filter run prefix can precede any [[run|Filter Run]] of a [[filter expre
[[<":or"> |"Or Filter Run Prefix"]] |
[[<":reduce"> |"Reduce Filter Run Prefix"]] |
[[<":sort"> /"v5.2.0"/ |"Sort Filter Run Prefix"]] |
[[<":let"> /"v5.4.0"/ |"Let Filter Run Prefix"]] |
[[<":then"> /"v5.3.0"/ |"Then Filter Run Prefix"]]) [[run|"Filter Run"]]
"""/>

View File

@@ -9,7 +9,7 @@ Shortcut prefixes are commonly used by advanced users because they are fast to t
<$railroad text="""
\start none
\end none
(-|:"+"|"-"|"~"|"=")
(-|:"+"|"-"|"~"|"="|"=>")
[[run|"Filter Run"]]
"""/>
@@ -23,7 +23,8 @@ If a run has:
* the prefix `~`, if the filter output so far is an empty list then the output titles of the run are [[dominantly appended|Dominant Append]] to the filter's output. If the filter output so far is not an empty list then the run is ignored. <<.from-version "5.1.18">>
* the prefix `=`, output titles are appended to the filter's output without de-duplication. <<.from-version "5.1.20">>
* the prefix `=`, output titles are appended to the filter's output without de-duplication. <<.from-version "5.1.20">>
* the prefix `=>`, the input is assigned to the variable named with the output title. <<.from-version "5.4.0">>
{{Interchangeable Filter Run Prefixes}}

View File

@@ -16,4 +16,6 @@ op-neg-output: the input, but with tiddler <<.place T>> filtered out if it exist
<<.op title>> is a [[constructor|Selection Constructors]] (except in the form `!title`), but <<.olink2 "field:title" field>> is a [[modifier|Selection Constructors]].
<<.from-version "5.4.0">> If the operand is quoted with round brackets then the <<.op title>> operator returns the complete list of titles assigned to the multi-valued variable. When negated, the title operator with multi-valued operands returns all the titles that are not present in the operand list.
<<.operator-examples "title">>

View File

@@ -0,0 +1,16 @@
title: $:/changenotes/5.4.0/#8972
description: Multi-valued variables and let filter run prefix
release: 5.4.0
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: hackability
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972
github-contributors: Jermolene
This PR introduces a new filter run prefix `:let` that assigns the result of the filter run to a variable that is made available for the remaining filter runs of the filter expression. It solves the problem that previously it was impossible to compute values for filter operator parameters; parameters could only be a literal string, text reference or variable reference.
This PR also introduces multi-valued variables, the ability to store a list of results in a variable, not just a single string. They can be assigned with the new `:let` filter run prefix, or the existing `<$let>` widget. The full list of values can be retrieved using round brackets instead of the usual angle brackets. In all other contexts only the first item in the list is used as the variable value.
* [[Multi-Valued Variables]]
* [[Let Filter Run Prefix]]
* [[LetWidget]]

View File

@@ -3,11 +3,5 @@ modified: 20140912135951542
title: $:/DefaultTiddlers
type: text/vnd.tiddlywiki
[[Hire the founder of TiddlyWiki]]
HelloThere
[[Quick Start]]
[[Find Out More]]
[[TiddlyWiki on the Web]]
[[Testimonials and Reviews]]
GettingStarted
Community
[[Multi-Valued Variables]]
[[Let Filter Run Prefix]]

View File

@@ -0,0 +1,48 @@
title: Multi-Valued Variables
created: 20250307212252946
modified: 20250307212252946
tags: Concepts Variables
<<.from-version "5.4.0">> In ordinary usage, [[variables|Variables]] contain a single snippet of text. With the introduction of multi-valued variables. it is now possible to store a list of multiple values in a single variable. When accessed in the usual way, only the first value is returned, but using round brackets instead of angle brackets around the variable name allows access to the complete list of the values. This makes multi-valued variables largely invisible unless you specifically need to use them.
! Setting Multi-Valued Variables
!! LetWidget
The <<.wid let>> widget allows multi-valued variables to be set in one operation, each to the complete list of results obtained from evaluating an attribute that is defined via a filtered transclusion. For example:
```
<$let
varname={{{ [all[tiddlers]sort[]] }}}
>
```
The <<.wid let>> widget also allows the complete list of return values from a function to be assigned to a multi-valued variable. For example:
```
<$let
varname=<<my.func>>
>
```
!! [[Let Filter Run Prefix]]
The `:let` filter run prefix (or its shortcut syntax `=>`) assigns the complete list of results of a filter run to a multi-valued variable.
! Retrieving Multi-Valued Variables
!! [[title Operator]]
!! Multi-valued Parameters for Filter Operators
! Examples
For example:
```
<$let varname={{{ [all[tiddlers]sort[]] }}}>
<$text text={{{ [(varname)] +[join[-]] }}}/>
</$let>
```

View File

@@ -1,13 +1,15 @@
created: 20141002133113496
modified: 20240422084347375
modified: 20250307212252946
tags: Concepts WikiText
title: Variables
type: text/vnd.tiddlywiki
!! Introduction
* A <<.def variable>> is a ''snippet of text'' that can be accessed by name.
* The text is referred to as the variable's <<.def value>>.
* A <<.def variable>> is a ''snippet of text'' that can be accessed by name
* The text is referred to as the variable's <<.def value>>
<<.from-version "5.4.0">> In ordinary usage, variables contain a single snippet of text. With the introduction of [[Multi-Valued Variables]] it is possible to store a list of multiple values in a single variable. When accessed in the usual way, only the first value is returned, but using round brackets instead of the usual angle brackets retrieves the complete list of values. This makes the existence of multi-valued variables invisible unless you specifically need to use them.
Variables are defined by [[widgets|Widgets]]. Several core widgets define variables, the most common being the <<.wlink LetWidget>>, <<.wlink SetWidget>> and <<.wlink ListWidget>> widgets.

View File

@@ -25,6 +25,7 @@ In addition there are optional attributes that can be used:
|$$message |A message to display as the title of the information logged. Useful when several `action-log` widgets are used in sequence. |
|$$all |Set to "yes" to log all variables in a collapsed table. Note that if there is nothing specified to log, all variables are always logged instead.|
<<.from-version "5.4.0">> Any [[multi-valued variables|Multi-Valued Variables]] or attributes are logged as a list of values.
<<.tip """A handy tip if an action widget is not behaving as expected is to temporarily change it to an `<$action-log>` widget so that the attributes can be observed.""">>

View File

@@ -1,12 +1,12 @@
title: LetWidget
created: 20211028115900000
modified: 20221001094658854
modified: 20250307212252946
tags: Widgets
caption: let
! Introduction
<<.from-version "5.2.1">> The <<.wid let>> widget allows multiple variables to be set in one operation. In some situations it can result in simpler code than using the more flexible <<.wlink SetWidget>> widget. It differs from the <<.wlink VarsWidget>> widget in that variables you're defining may depend on earlier variables defined within the same <<.wid let>>.
<<.from-version "5.2.1">> The <<.wid let>> widget allows more than one variable to be set in one operation. In some situations it can result in simpler code than using the more flexible <<.wlink SetWidget>> widget. It differs from the <<.wlink VarsWidget>> widget in that variables you're defining may depend on earlier variables defined within the same <<.wid let>>.
! Content and Attributes
@@ -19,6 +19,18 @@ Attributes are evaluated in the order they are written. Attributes with the same
<<.note """<<.from-version "5.2.4">> There is no longer any restriction on using variable names that start with the $ character.""">>
! Multi-Valued Variables
<<.from-version "5.4.0">> The <<.wid let>> widget also allows [[multi-valued variables|Multi-Valued Variables]] to be set in one operation to the complete list of results obtained from evaluating an attribute.
Almost all operations that work with variables only consider the first item in the list. Using round brackets instead of angle brackets around the variable name gives access to the complete list of results. For example:
```
<$let varname={{{ [all[tiddlers]sort[]] }}}>
<$text text={{{ [(varname)] +[join[-]] }}}/>
</$let>
```
! Examples
Consider a case where you need to set multiple variables, where some depend on the evaluation of others.