From b8bdb0aeeb0875f3b3f7b6ad85f6fb6e5d208cba Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 7 Mar 2025 21:48:36 +0000 Subject: [PATCH 01/30] Introduce let filter run prefix for assigning filter run result to a variable --- core/modules/filterrunprefixes/let.js | 37 +++++++++++++++++++ core/modules/filters.js | 8 +++- .../tests/data/let-filter-prefix/All.tid | 12 ++++++ .../tests/data/let-filter-prefix/Simple.tid | 12 ++++++ .../filters/syntax/Let Filter Run Prefix.tid | 23 ++++++++++++ .../syntax/Named Filter Run Prefix.tid | 1 + 6 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 core/modules/filterrunprefixes/let.js create mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/All.tid create mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid create mode 100644 editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js new file mode 100644 index 000000000..e8218bab4 --- /dev/null +++ b/core/modules/filterrunprefixes/let.js @@ -0,0 +1,37 @@ +/*\ +title: $:/core/modules/filterrunprefixes/let.js +type: application/javascript +module-type: filterrunprefix + +Assign a value to a variable + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.let = function(operationSubFunction,options) { + // Save the variable name + var name = options.suffixes[0][0]; + // Return the filter run prefix function + return function(results,source,widget) { + // Set the input source to the incoming results + var inputSource = widget.wiki.makeTiddlerIterator(results.toArray()); + // Assign the result of the subfunction to the variable + var variables = {}; + variables[name] = operationSubFunction(inputSource,widget)[0] || ""; + // Clear the results + results.clear(); + // Return the variables + return { + variables: variables + }; + }; +}; + +})(); diff --git a/core/modules/filters.js b/core/modules/filters.js index aa82a352a..62f6e7213 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -348,7 +348,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 --**/"); diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/All.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/All.tid new file mode 100644 index 000000000..9167ec712 --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/All.tid @@ -0,0 +1,12 @@ +title: LetFilterRunPrefix/All +description: Usage of "all[]" operator within "let" filter run prefix +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$text text={{{ [[colossus]] :let:another[all[]] [] +[join[-]] }}}/> ++ +title: ExpectedResult + +

colossus

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid new file mode 100644 index 000000000..6385c83e2 --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid @@ -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={{{ :let:varname[[magpie]] [] +[join[-]] }}}/> ++ +title: ExpectedResult + +

magpie

\ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid new file mode 100644 index 000000000..88ec3f6ac --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -0,0 +1,23 @@ +created: 20250307212252946 +from-version: 5.3.7 +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 result of a filter run to a variable +tags: [[Named Filter Run Prefix]] +title: Let Filter Run Prefix +type: text/vnd.tiddlywiki + +<$railroad text=""" +\start none +\end none +( ":let" ) +( ":" ) +( : "variable" ) +( ":" ) +[[run|"Filter Run"]] +"""/> + +The `:let` filter run prefix assigns the first result of a filter run to a variable that is made available to the remaining [[filter runs|Filter Run]] in the [[filter expression|Filter Expression]]. If the filter run does not return any results then the variable is set to an empty string. + +Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. diff --git a/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid index 31534479e..df7315a67 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid @@ -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.3.7"/ |"Let Filter Run Prefix"]] | [[<":then"> /"v5.3.0"/ |"Then Filter Run Prefix"]]) [[run|"Filter Run"]] """/> From 1acef48a10ea4097a2c901ee379050be97e99b7d Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 8 Mar 2025 08:56:12 +0000 Subject: [PATCH 02/30] Get rid of the special behaviour for all[] Not needed because the input to the filter run is available --- core/modules/filterrunprefixes/let.js | 4 +--- .../tiddlers/tests/data/let-filter-prefix/All.tid | 12 ------------ .../tests/data/let-filter-prefix/UseInput.tid | 12 ++++++++++++ .../filters/syntax/Let Filter Run Prefix.tid | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) delete mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/All.tid create mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js index e8218bab4..0cb9a7ae9 100644 --- a/core/modules/filterrunprefixes/let.js +++ b/core/modules/filterrunprefixes/let.js @@ -20,11 +20,9 @@ exports.let = function(operationSubFunction,options) { var name = options.suffixes[0][0]; // Return the filter run prefix function return function(results,source,widget) { - // Set the input source to the incoming results - var inputSource = widget.wiki.makeTiddlerIterator(results.toArray()); // Assign the result of the subfunction to the variable var variables = {}; - variables[name] = operationSubFunction(inputSource,widget)[0] || ""; + variables[name] = operationSubFunction(source,widget)[0] || ""; // Clear the results results.clear(); // Return the variables diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/All.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/All.tid deleted file mode 100644 index 9167ec712..000000000 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/All.tid +++ /dev/null @@ -1,12 +0,0 @@ -title: LetFilterRunPrefix/All -description: Usage of "all[]" operator within "let" filter run prefix -type: text/vnd.tiddlywiki-multiple -tags: [[$:/tags/wiki-test-spec]] - -title: Output - -<$text text={{{ [[colossus]] :let:another[all[]] [] +[join[-]] }}}/> -+ -title: ExpectedResult - -

colossus

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid new file mode 100644 index 000000000..24fd566fd --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid @@ -0,0 +1,12 @@ +title: LetFilterRunPrefix/UseInput +description: Using the input of "let" filter run prefix +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$text text={{{ [[colossus]] :let:another[addprefix[something]] [] +[join[-]] }}}/> ++ +title: ExpectedResult + +

colossus

\ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index 88ec3f6ac..fc8480190 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -20,4 +20,4 @@ type: text/vnd.tiddlywiki The `:let` filter run prefix assigns the first result of a filter run to a variable that is made available to the remaining [[filter runs|Filter Run]] in the [[filter expression|Filter Expression]]. If the filter run does not return any results then the variable is set to an empty string. -Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. +The output of the `:let` filter run prefix is an empty title list. From db1ed0e66b019b4fbfc04268aa899718ed1312f0 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 8 Mar 2025 09:00:32 +0000 Subject: [PATCH 03/30] Fix tests --- core/modules/filterrunprefixes/let.js | 4 +++- .../tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js index 0cb9a7ae9..e8218bab4 100644 --- a/core/modules/filterrunprefixes/let.js +++ b/core/modules/filterrunprefixes/let.js @@ -20,9 +20,11 @@ exports.let = function(operationSubFunction,options) { var name = options.suffixes[0][0]; // Return the filter run prefix function return function(results,source,widget) { + // Set the input source to the incoming results + var inputSource = widget.wiki.makeTiddlerIterator(results.toArray()); // Assign the result of the subfunction to the variable var variables = {}; - variables[name] = operationSubFunction(source,widget)[0] || ""; + variables[name] = operationSubFunction(inputSource,widget)[0] || ""; // Clear the results results.clear(); // Return the variables diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index fc8480190..ed8b6bffc 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -20,4 +20,4 @@ type: text/vnd.tiddlywiki The `:let` filter run prefix assigns the first result of a filter run to a variable that is made available to the remaining [[filter runs|Filter Run]] in the [[filter expression|Filter Expression]]. If the filter run does not return any results then the variable is set to an empty string. -The output of the `:let` filter run prefix is an empty title list. +Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. \ No newline at end of file From 8e9b30fec888a8f0c6fb35c147242328fad7ff1d Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 8 Mar 2025 09:05:59 +0000 Subject: [PATCH 04/30] Fix tests --- core/modules/filterrunprefixes/let.js | 37 +++++++++++-------- .../tests/data/let-filter-prefix/UseInput.tid | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js index e8218bab4..c590c6ec2 100644 --- a/core/modules/filterrunprefixes/let.js +++ b/core/modules/filterrunprefixes/let.js @@ -16,22 +16,29 @@ Assign a value to a variable Export our filter prefix function */ exports.let = function(operationSubFunction,options) { - // Save the variable name - var name = options.suffixes[0][0]; - // Return the filter run prefix function - return function(results,source,widget) { - // Set the input source to the incoming results - var inputSource = widget.wiki.makeTiddlerIterator(results.toArray()); - // Assign the result of the subfunction to the variable - var variables = {}; - variables[name] = operationSubFunction(inputSource,widget)[0] || ""; - // Clear the results - results.clear(); - // Return the variables - return { - variables: variables + if(options.suffixes[0] && options.suffixes[0][0]) { + // Save the variable name + var name = options.suffixes[0][0]; + // Return the filter run prefix function + return function(results,source,widget) { + // Set the input source to the incoming results + var inputSource = widget.wiki.makeTiddlerIterator(results.toArray()); + // Assign the result of the subfunction to the variable + var variables = {}; + variables[name] = operationSubFunction(inputSource,widget)[0] || ""; + // Clear the results + results.clear(); + // Return the variables + return { + variables: variables + }; }; - }; + } else { + // Return nothing if there is no variable name + return function(results,source,widget) { + }; + + } }; })(); diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid index 24fd566fd..c20e16442 100644 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid @@ -5,7 +5,7 @@ tags: [[$:/tags/wiki-test-spec]] title: Output -<$text text={{{ [[colossus]] :let:another[addprefix[something]] [] +[join[-]] }}}/> +<$text text={{{ [[colossus]] :let:another[all[]] [] +[join[-]] }}}/> + title: ExpectedResult From e7b713c27772d9455fffdb153072e3b5a582c699 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 9 Mar 2025 14:56:15 +0000 Subject: [PATCH 05/30] Cleanup --- core/modules/filterrunprefixes/let.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js index c590c6ec2..64c2a411f 100644 --- a/core/modules/filterrunprefixes/let.js +++ b/core/modules/filterrunprefixes/let.js @@ -35,9 +35,7 @@ exports.let = function(operationSubFunction,options) { }; } else { // Return nothing if there is no variable name - return function(results,source,widget) { - }; - + return function(results,source,widget) {}; } }; From 7397f4fa3a89ed7a9c31e109dac40728ee5afeb1 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 9 Mar 2025 16:50:48 +0000 Subject: [PATCH 06/30] 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 --- core/modules/filterrunprefixes/let.js | 2 +- core/modules/filters/varlist.js | 30 +++++++++++++++++ core/modules/widgets/widget.js | 33 +++++++++++++++---- .../data/let-filter-prefix/ResultList.tid | 12 +++++++ 4 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 core/modules/filters/varlist.js create mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js index 64c2a411f..8f877b18c 100644 --- a/core/modules/filterrunprefixes/let.js +++ b/core/modules/filterrunprefixes/let.js @@ -25,7 +25,7 @@ exports.let = function(operationSubFunction,options) { var inputSource = widget.wiki.makeTiddlerIterator(results.toArray()); // Assign the result of the subfunction to the variable var variables = {}; - variables[name] = operationSubFunction(inputSource,widget)[0] || ""; + variables[name] = operationSubFunction(inputSource,widget); // Clear the results results.clear(); // Return the variables diff --git a/core/modules/filters/varlist.js b/core/modules/filters/varlist.js new file mode 100644 index 000000000..e8677dbf5 --- /dev/null +++ b/core/modules/filters/varlist.js @@ -0,0 +1,30 @@ +/*\ +title: $:/core/modules/filters/varlist.js +type: application/javascript +module-type: filteroperator + +Retrieve the value of a variable as a list + +[all[shadows+tiddlers]] + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter function +*/ +exports.varlist = function(source,operator,options) { + // Check for common optimisations + var variableInfo = options.widget.getVariableInfo(operator.operand); + if(variableInfo) { + return options.wiki.makeTiddlerIterator(variableInfo.resultList); + } else { + return []; + } +}; + +})(); diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 14e90ba2d..844f1a35d 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -83,7 +83,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: @@ -93,8 +93,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, @@ -164,6 +166,9 @@ Widget.prototype.getVariableInfo = function(name,options) { resultList = this.wiki.filterTiddlers(value,this.makeFakeWidgetWithVariables(variables),options.source); value = resultList[0] || ""; } else { + if(variable.resultList) { + resultList = variable.resultList; + } params = variable.params; } return { @@ -313,7 +318,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, @@ -321,7 +326,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; @@ -330,9 +340,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); diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid new file mode 100644 index 000000000..ee9e8e800 --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid @@ -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={{{ :let:varname[all[tiddlers]] [varlist[varname]sort[]join[,]] }}}/> ++ +title: ExpectedResult + +

$:/core,ExpectedResult,Output

\ No newline at end of file From 2a1542c4e5d6d7b835daafe7cd29d62a349275e8 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 9 Mar 2025 17:01:31 +0000 Subject: [PATCH 07/30] Docs update --- .../filters/syntax/Let Filter Run Prefix.tid | 7 ++++--- .../tiddlers/filters/syntax/varlist Operator.tid | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index ed8b6bffc..25e091e23 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -3,7 +3,7 @@ from-version: 5.3.7 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 result of a filter run to a variable +rp-purpose: assign the results of a filter run to a variable tags: [[Named Filter Run Prefix]] title: Let Filter Run Prefix type: text/vnd.tiddlywiki @@ -18,6 +18,7 @@ type: text/vnd.tiddlywiki [[run|"Filter Run"]] """/> -The `:let` filter run prefix assigns the first result of a filter run to a variable that is made available to the remaining [[filter runs|Filter Run]] in the [[filter expression|Filter Expression]]. If the filter run does not return any results then the variable is set to an empty string. +The `:let` filter run prefix assigns the result list of a filter run to a variable which 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). The special [varlist Operator] is used to access all the items in the result list. + +Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. -Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid b/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid new file mode 100644 index 000000000..fa303860d --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid @@ -0,0 +1,14 @@ +caption: varlist +created: 20250307212252946 +modified: 20250307212252946 +op-input: ignored +op-output: the list items stored in the variable +op-parameter: the name of a variable +op-purpose: retrieve the value of a variable as a list +tags: [[Filter Operators]] [[Selection Constructors]] +title: varlist Operator +type: text/vnd.tiddlywiki + +The parameter specifies the name of a variable that has been assigned a list of items by a [[let filter run prefix|Let Filter Run Prefix]]. The operator retrieves all the list items. + +<<.operator-examples "varlist">> From 78a7aedd4baa9715ea4e87b09aa07119035d8f4b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 9 Mar 2025 17:50:42 +0000 Subject: [PATCH 08/30] Introduce letlist widget for assigning lists to variables Terrible name. Annoyingly, we can't overload the existing let or set widgets. --- core/modules/widgets/letlist.js | 91 +++++++++++++++++++ .../data/letlist-widget/SelfReference.tid | 19 ++++ .../tests/data/letlist-widget/Simple.tid | 16 ++++ .../filters/syntax/varlist Operator.tid | 2 +- 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 core/modules/widgets/letlist.js create mode 100644 editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid create mode 100644 editions/test/tiddlers/tests/data/letlist-widget/Simple.tid diff --git a/core/modules/widgets/letlist.js b/core/modules/widgets/letlist.js new file mode 100644 index 000000000..38d4b6be2 --- /dev/null +++ b/core/modules/widgets/letlist.js @@ -0,0 +1,91 @@ +/*\ +title: $:/core/modules/widgets/letlist.js +type: application/javascript +module-type: widget + +This widget allows defining multiple variables as lists, allowing +the later variables to depend upon the earlier ones + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var LetListWidget = function(parseTreeNode,options) { + // Initialise + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +LetListWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +LetListWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +LetListWidget.prototype.computeAttributes = function() { + // Before computing attributes, we must make clear that none of the + // existing attributes are staged for lookup, even on a refresh + var changedAttributes = {}, + self = this; + this.currentValueFor = Object.create(null); + $tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(this.parseTreeNode),function(attribute) { + var value = self.computeAttribute(attribute), + name = attribute.name; + // Now that it's prepped, we're allowed to look this variable up + // when defining later variables + if(value !== undefined) { + self.currentValueFor[name] = self.wiki.filterTiddlers(value,self); + } + }); + // Run through again, setting variables and looking for differences + $tw.utils.each(this.currentValueFor,function(value,name) { + if (!$tw.utils.isArrayEqual(self.attributes[name],value)) { + self.attributes[name] = value; + self.setVariable(name,value); + changedAttributes[name] = true; + } + }); + return changedAttributes; +}; + +LetListWidget.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: value[0] || "", + resultList: value + }; + } + return Widget.prototype.getVariableInfo.call(this,name,options); +}; + +/* +Refresh the widget by ensuring our attributes are up to date +*/ +LetListWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if($tw.utils.count(changedAttributes) > 0) { + this.refreshSelf(); + return true; + } + return this.refreshChildren(changedTiddlers); +}; + +exports["letlist"] = LetListWidget; + +})(); diff --git a/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid b/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid new file mode 100644 index 000000000..275e1cbbf --- /dev/null +++ b/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid @@ -0,0 +1,19 @@ +title: LetListWidget/SelfReference +description: Using self references with the "letlist" widget +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$letlist + original="[all[tiddlers]sort[]]" + varname="[varlist[original]]" +> +<$text text={{{ [varlist[varname]] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid b/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid new file mode 100644 index 000000000..d4cf11cd4 --- /dev/null +++ b/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid @@ -0,0 +1,16 @@ +title: LetListWidget/Simple +description: Simple usage of the "letlist" widget +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$letlist varname="[all[tiddlers]sort[]]"> +<$text text={{{ [varlist[varname]] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid b/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid index fa303860d..1e81898c0 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid @@ -9,6 +9,6 @@ tags: [[Filter Operators]] [[Selection Constructors]] title: varlist Operator type: text/vnd.tiddlywiki -The parameter specifies the name of a variable that has been assigned a list of items by a [[let filter run prefix|Let Filter Run Prefix]]. The operator retrieves all the list items. +The parameter specifies the name of a variable that has been assigned a list of items by a [[let filter run prefix|Let Filter Run Prefix]]. The operator retrieves all the list items stored in the variable. <<.operator-examples "varlist">> From 76e1c2124b2fea311af0632c0fccf51fa8488bc5 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 9 Mar 2025 18:21:36 +0000 Subject: [PATCH 09/30] Docs update --- .../filters/syntax/Let Filter Run Prefix.tid | 2 +- .../filters/{syntax => }/varlist Operator.tid | 2 +- .../tw5.com/tiddlers/widgets/LetListWidget.tid | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) rename editions/tw5.com/tiddlers/filters/{syntax => }/varlist Operator.tid (61%) create mode 100644 editions/tw5.com/tiddlers/widgets/LetListWidget.tid diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index 25e091e23..28762875f 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -18,7 +18,7 @@ type: text/vnd.tiddlywiki [[run|"Filter Run"]] """/> -The `:let` filter run prefix assigns the result list of a filter run to a variable which 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). The special [varlist Operator] is used to access all the items in the result list. +<<.from-version "5.3.7">> The `:let` filter run prefix assigns the result list of a filter run to a variable which 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). The special [varlist Operator] is used to access all the items in the result list. Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. diff --git a/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid b/editions/tw5.com/tiddlers/filters/varlist Operator.tid similarity index 61% rename from editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid rename to editions/tw5.com/tiddlers/filters/varlist Operator.tid index 1e81898c0..60176b36c 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/varlist Operator.tid +++ b/editions/tw5.com/tiddlers/filters/varlist Operator.tid @@ -9,6 +9,6 @@ tags: [[Filter Operators]] [[Selection Constructors]] title: varlist Operator type: text/vnd.tiddlywiki -The parameter specifies the name of a variable that has been assigned a list of items by a [[let filter run prefix|Let Filter Run Prefix]]. The operator retrieves all the list items stored in the variable. +<<.from-version "5.3.7">> The parameter specifies the name of a variable that has been assigned a list of items by a [[let filter run prefix|Let Filter Run Prefix]]. The operator retrieves all the list items stored in the variable. <<.operator-examples "varlist">> diff --git a/editions/tw5.com/tiddlers/widgets/LetListWidget.tid b/editions/tw5.com/tiddlers/widgets/LetListWidget.tid new file mode 100644 index 000000000..ef69f3216 --- /dev/null +++ b/editions/tw5.com/tiddlers/widgets/LetListWidget.tid @@ -0,0 +1,18 @@ +title: LetListWidget +created: 20250307212252946 +modified: 20250307212252946 +tags: Widgets +caption: letlist + +! Introduction + +<<.from-version "5.3.7">> The <<.wid letlist>> widget allows multiple variables to be set in one operation, each to the list of results from evaluating a filter. It is similar to the <<.wlink LetWidget>> widget but assigns the complete list of results to the variable instead of just the first result. + +! Content and Attributes + +The content of the <<.wid letlist>> widget is the scope for the value assigned to the variable. + +|!Attribute |!Description | +|//{attributes}// |Each attribute name specifies a variable name. The attribute value is a filter whose results are is assigned to the variable | + +Attributes are evaluated in the order they are written. Attributes with the same name are allowed. Each time a duplicate attribute is encountered, it will replace the existing value set by the earlier duplicate. From c5ad4294935ce7ff75bce9709e05c891126e3c7e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 10 Mar 2025 12:45:10 +0000 Subject: [PATCH 10/30] Update DefaultTiddlers to highlight the new docs --- .../prerelease/tiddlers/system/DefaultTiddlers.tid | 11 +++-------- editions/tw5.com/tiddlers/system/DefaultTiddlers.tid | 10 +++------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/editions/prerelease/tiddlers/system/DefaultTiddlers.tid b/editions/prerelease/tiddlers/system/DefaultTiddlers.tid index 66d1c9bab..0bd696977 100644 --- a/editions/prerelease/tiddlers/system/DefaultTiddlers.tid +++ b/editions/prerelease/tiddlers/system/DefaultTiddlers.tid @@ -2,11 +2,6 @@ created: 20131127215321439 modified: 20140912135951542 title: $:/DefaultTiddlers -[[TiddlyWiki Pre-release]] -HelloThere -[[Quick Start]] -[[Find Out More]] -[[TiddlyWiki on the Web]] -[[Testimonials and Reviews]] -GettingStarted -Community +[[Let Filter Run Prefix]] +[[varlist Operator]] +[[LetListWidget]] diff --git a/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid b/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid index 7b9e94cbc..33be88abd 100644 --- a/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid +++ b/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid @@ -3,10 +3,6 @@ modified: 20140912135951542 title: $:/DefaultTiddlers type: text/vnd.tiddlywiki -HelloThere -[[Quick Start]] -[[Find Out More]] -[[TiddlyWiki on the Web]] -[[Testimonials and Reviews]] -GettingStarted -Community +[[Let Filter Run Prefix]] +[[varlist Operator]] +[[LetListWidget]] From 4cfa758d511655d3021b76bd10212241c893b964 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 10 Mar 2025 15:31:28 +0000 Subject: [PATCH 11/30] Fixed varlist crash with empty parameter --- core/modules/filters/varlist.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/filters/varlist.js b/core/modules/filters/varlist.js index e8677dbf5..5c6db3616 100644 --- a/core/modules/filters/varlist.js +++ b/core/modules/filters/varlist.js @@ -19,7 +19,7 @@ Export our filter function */ exports.varlist = function(source,operator,options) { // Check for common optimisations - var variableInfo = options.widget.getVariableInfo(operator.operand); + var variableInfo = operator.operand && options.widget.getVariableInfo(operator.operand); if(variableInfo) { return options.wiki.makeTiddlerIterator(variableInfo.resultList); } else { From a9a9a745d4e587121043ab8ca35a2d885c091484 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 11 Mar 2025 12:47:01 +0000 Subject: [PATCH 12/30] Switch to triple brace syntax for assigning filtered lists --- core/modules/widgets/letlist.js | 4 +-- core/modules/widgets/widget.js | 33 ++++++++++++++++--- .../data/letlist-widget/SelfReference.tid | 4 +-- .../tests/data/letlist-widget/Simple.tid | 2 +- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/core/modules/widgets/letlist.js b/core/modules/widgets/letlist.js index 38d4b6be2..bcc2b6dbf 100644 --- a/core/modules/widgets/letlist.js +++ b/core/modules/widgets/letlist.js @@ -42,12 +42,12 @@ LetListWidget.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 if(value !== undefined) { - self.currentValueFor[name] = self.wiki.filterTiddlers(value,self); + self.currentValueFor[name] = value; } }); // Run through again, setting variables and looking for differences diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 844f1a35d..ad2ab4c2c 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -388,20 +388,43 @@ 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(variableInfo) { + 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; + value = attribute.value || ""; + if(options.asList) { + value = [value]; + } } return value; }; diff --git a/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid b/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid index 275e1cbbf..3ad4026b6 100644 --- a/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid +++ b/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid @@ -6,8 +6,8 @@ tags: [[$:/tags/wiki-test-spec]] title: Output <$letlist - original="[all[tiddlers]sort[]]" - varname="[varlist[original]]" + original={{{ [all[tiddlers]sort[]] }}} + varname={{{ [varlist[original]] }}} > <$text text={{{ [varlist[varname]] +[join[-]] }}}/> diff --git a/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid b/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid index d4cf11cd4..6a31d2189 100644 --- a/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid +++ b/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid @@ -5,7 +5,7 @@ tags: [[$:/tags/wiki-test-spec]] title: Output -<$letlist varname="[all[tiddlers]sort[]]"> +<$letlist varname={{{ [all[tiddlers]sort[]] }}}> <$text text={{{ [varlist[varname]] +[join[-]] }}}/> + From efce6731e1d9c9270f2cac7609e4ac0ef02fd442 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 11 Mar 2025 12:49:40 +0000 Subject: [PATCH 13/30] Docs update --- editions/tw5.com/tiddlers/widgets/LetListWidget.tid | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/editions/tw5.com/tiddlers/widgets/LetListWidget.tid b/editions/tw5.com/tiddlers/widgets/LetListWidget.tid index ef69f3216..6a265d34a 100644 --- a/editions/tw5.com/tiddlers/widgets/LetListWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/LetListWidget.tid @@ -6,13 +6,21 @@ caption: letlist ! Introduction -<<.from-version "5.3.7">> The <<.wid letlist>> widget allows multiple variables to be set in one operation, each to the list of results from evaluating a filter. It is similar to the <<.wlink LetWidget>> widget but assigns the complete list of results to the variable instead of just the first result. +<<.from-version "5.3.7">> The <<.wid letlist>> widget allows multiple variables to be set in one operation, each to the list of results from evaluating an attribute. It is similar to the <<.wlink LetWidget>> widget but assigns the complete list of results to a multi-valued variable instead of just the first result. ! Content and Attributes The content of the <<.wid letlist>> widget is the scope for the value assigned to the variable. |!Attribute |!Description | -|//{attributes}// |Each attribute name specifies a variable name. The attribute value is a filter whose results are is assigned to the variable | +|//{attributes}// |Each attribute name specifies a variable name. The attribute value is a list that is assigned to the variable | + +The <<.wid letlist>> is primarily intended to be used with the filtered attribute syntax. This enables the result list of a filter to be assigned to a multi-valued variable: + +``` +<$letlist varname={{{ [all[tiddlers]sort[]] }}}> +<$text text={{{ [varlist[varname]] +[join[-]] }}}/> + +``` Attributes are evaluated in the order they are written. Attributes with the same name are allowed. Each time a duplicate attribute is encountered, it will replace the existing value set by the earlier duplicate. From d69ecf46395281ad5e55a51ce487bc48d8517dad Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 11 Mar 2025 13:30:17 +0000 Subject: [PATCH 14/30] Test for multivalued functions --- .../letlist-widget/MultiValuedFunction.tid | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 editions/test/tiddlers/tests/data/letlist-widget/MultiValuedFunction.tid diff --git a/editions/test/tiddlers/tests/data/letlist-widget/MultiValuedFunction.tid b/editions/test/tiddlers/tests/data/letlist-widget/MultiValuedFunction.tid new file mode 100644 index 000000000..57879407f --- /dev/null +++ b/editions/test/tiddlers/tests/data/letlist-widget/MultiValuedFunction.tid @@ -0,0 +1,18 @@ +title: LetListWidget/Simple +description: Simple usage of the "letlist" widget +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\function myfunc() [all[tiddlers]sort[]] + +<$letlist varname=<>> +<$text text={{{ [varlist[varname]] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file From a31df85894236c2f4de193848213b23f6b1d19c7 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 11 Mar 2025 13:59:19 +0000 Subject: [PATCH 15/30] varlist operator: fixed crash accessing non-existent variable See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2712068743 --- core/modules/filters/varlist.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/modules/filters/varlist.js b/core/modules/filters/varlist.js index 5c6db3616..0857da2fe 100644 --- a/core/modules/filters/varlist.js +++ b/core/modules/filters/varlist.js @@ -5,8 +5,6 @@ module-type: filteroperator Retrieve the value of a variable as a list -[all[shadows+tiddlers]] - \*/ (function(){ @@ -18,9 +16,8 @@ Retrieve the value of a variable as a list Export our filter function */ exports.varlist = function(source,operator,options) { - // Check for common optimisations var variableInfo = operator.operand && options.widget.getVariableInfo(operator.operand); - if(variableInfo) { + if(variableInfo && variableInfo.text !== undefined) { return options.wiki.makeTiddlerIterator(variableInfo.resultList); } else { return []; From 0db188f365103ac7039231d8b93c55bb4539454e Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 18 Mar 2025 12:46:31 +0000 Subject: [PATCH 16/30] 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. --- core/modules/widgets/let.js | 8 +- core/modules/widgets/letlist.js | 91 ------------------- .../data/letlist-widget/SelfReference.tid | 19 ---- .../tests/data/letlist-widget/Simple.tid | 16 ---- .../MultiValuedFunction.tid | 8 +- .../data/multi-valued-variables/Simple.tid | 17 ++++ .../tests/test-widget-getVariableInfo.js | 2 +- .../variables/Multi-Valued Variables.tid | 16 ++++ .../tw5.com/tiddlers/variables/Variables.tid | 8 +- .../tiddlers/widgets/LetListWidget.tid | 26 ------ .../tw5.com/tiddlers/widgets/LetWidget.tid | 16 +++- 11 files changed, 62 insertions(+), 165 deletions(-) delete mode 100644 core/modules/widgets/letlist.js delete mode 100644 editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid delete mode 100644 editions/test/tiddlers/tests/data/letlist-widget/Simple.tid rename editions/test/tiddlers/tests/data/{letlist-widget => multi-valued-variables}/MultiValuedFunction.tid (66%) create mode 100644 editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid create mode 100644 editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid delete mode 100644 editions/tw5.com/tiddlers/widgets/LetListWidget.tid diff --git a/core/modules/widgets/let.js b/core/modules/widgets/let.js index 2b2886530..2cde66fde 100644 --- a/core/modules/widgets/let.js +++ b/core/modules/widgets/let.js @@ -49,7 +49,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 @@ -59,7 +59,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; @@ -72,8 +72,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); diff --git a/core/modules/widgets/letlist.js b/core/modules/widgets/letlist.js deleted file mode 100644 index bcc2b6dbf..000000000 --- a/core/modules/widgets/letlist.js +++ /dev/null @@ -1,91 +0,0 @@ -/*\ -title: $:/core/modules/widgets/letlist.js -type: application/javascript -module-type: widget - -This widget allows defining multiple variables as lists, allowing -the later variables to depend upon the earlier ones - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -var Widget = require("$:/core/modules/widgets/widget.js").widget; - -var LetListWidget = function(parseTreeNode,options) { - // Initialise - this.initialise(parseTreeNode,options); -}; - -/* -Inherit from the base widget class -*/ -LetListWidget.prototype = new Widget(); - -/* -Render this widget into the DOM -*/ -LetListWidget.prototype.render = function(parent,nextSibling) { - this.parentDomNode = parent; - this.computeAttributes(); - this.execute(); - this.renderChildren(parent,nextSibling); -}; - -LetListWidget.prototype.computeAttributes = function() { - // Before computing attributes, we must make clear that none of the - // existing attributes are staged for lookup, even on a refresh - var changedAttributes = {}, - self = this; - this.currentValueFor = Object.create(null); - $tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(this.parseTreeNode),function(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 - if(value !== undefined) { - self.currentValueFor[name] = value; - } - }); - // Run through again, setting variables and looking for differences - $tw.utils.each(this.currentValueFor,function(value,name) { - if (!$tw.utils.isArrayEqual(self.attributes[name],value)) { - self.attributes[name] = value; - self.setVariable(name,value); - changedAttributes[name] = true; - } - }); - return changedAttributes; -}; - -LetListWidget.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: value[0] || "", - resultList: value - }; - } - return Widget.prototype.getVariableInfo.call(this,name,options); -}; - -/* -Refresh the widget by ensuring our attributes are up to date -*/ -LetListWidget.prototype.refresh = function(changedTiddlers) { - var changedAttributes = this.computeAttributes(); - if($tw.utils.count(changedAttributes) > 0) { - this.refreshSelf(); - return true; - } - return this.refreshChildren(changedTiddlers); -}; - -exports["letlist"] = LetListWidget; - -})(); diff --git a/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid b/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid deleted file mode 100644 index 3ad4026b6..000000000 --- a/editions/test/tiddlers/tests/data/letlist-widget/SelfReference.tid +++ /dev/null @@ -1,19 +0,0 @@ -title: LetListWidget/SelfReference -description: Using self references with the "letlist" widget -type: text/vnd.tiddlywiki-multiple -tags: [[$:/tags/wiki-test-spec]] - -title: Output - -<$letlist - original={{{ [all[tiddlers]sort[]] }}} - varname={{{ [varlist[original]] }}} -> -<$text text={{{ [varlist[varname]] +[join[-]] }}}/> - -+ -title: ExpectedResult - -

-$:/core-ExpectedResult-Output -

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid b/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid deleted file mode 100644 index 6a31d2189..000000000 --- a/editions/test/tiddlers/tests/data/letlist-widget/Simple.tid +++ /dev/null @@ -1,16 +0,0 @@ -title: LetListWidget/Simple -description: Simple usage of the "letlist" widget -type: text/vnd.tiddlywiki-multiple -tags: [[$:/tags/wiki-test-spec]] - -title: Output - -<$letlist varname={{{ [all[tiddlers]sort[]] }}}> -<$text text={{{ [varlist[varname]] +[join[-]] }}}/> - -+ -title: ExpectedResult - -

-$:/core-ExpectedResult-Output -

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/letlist-widget/MultiValuedFunction.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedFunction.tid similarity index 66% rename from editions/test/tiddlers/tests/data/letlist-widget/MultiValuedFunction.tid rename to editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedFunction.tid index 57879407f..228c42bf4 100644 --- a/editions/test/tiddlers/tests/data/letlist-widget/MultiValuedFunction.tid +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedFunction.tid @@ -1,5 +1,5 @@ -title: LetListWidget/Simple -description: Simple usage of the "letlist" widget +title: MultiValuedVariables/Functions +description: Multi-valued functions type: text/vnd.tiddlywiki-multiple tags: [[$:/tags/wiki-test-spec]] @@ -7,9 +7,9 @@ title: Output \function myfunc() [all[tiddlers]sort[]] -<$letlist varname=<>> +<$let varname=<>> <$text text={{{ [varlist[varname]] +[join[-]] }}}/> - + + title: ExpectedResult diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid new file mode 100644 index 000000000..ebaf11901 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid @@ -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=<>> +<$text text={{{ [varlist[varname2]] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-widget-getVariableInfo.js b/editions/test/tiddlers/tests/test-widget-getVariableInfo.js index 7273a6dce..9d2084b58 100644 --- a/editions/test/tiddlers/tests/test-widget-getVariableInfo.js +++ b/editions/test/tiddlers/tests/test-widget-getVariableInfo.js @@ -77,7 +77,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 diff --git a/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid b/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid new file mode 100644 index 000000000..302f6f084 --- /dev/null +++ b/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid @@ -0,0 +1,16 @@ +title: Multi-Valued Variables +created: 20250307212252946 +modified: 20250307212252946 +tags: Concepts Variables + +<<.from-version "5.3.7">> In ordinary usage, [[variables|Variables]] contain a single spinnet 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 the [[varlist Operator]] can be used to access all the values. This makes the existence of multi-valued variables largely invisible unless you specifically need to use them. + +The <<.wid let>> widget also allows [[multi-valued variables|Multi-Valued Variables]] to be set in one operation, each to the complete list of results obtained from evaluating an attribute. + +For example: + +``` +<$letlist varname={{{ [all[tiddlers]sort[]] }}}> +<$text text={{{ [varlist[varname]] +[join[-]] }}}/> + +``` diff --git a/editions/tw5.com/tiddlers/variables/Variables.tid b/editions/tw5.com/tiddlers/variables/Variables.tid index 43387eb4f..62fd661e0 100644 --- a/editions/tw5.com/tiddlers/variables/Variables.tid +++ b/editions/tw5.com/tiddlers/variables/Variables.tid @@ -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.3.7">> In ordinary usage, variables contain a single spinnet 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 the [[varlist Operator]] can be used to access all the values. This makes the existence of multi-valued variables largely 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. diff --git a/editions/tw5.com/tiddlers/widgets/LetListWidget.tid b/editions/tw5.com/tiddlers/widgets/LetListWidget.tid deleted file mode 100644 index 6a265d34a..000000000 --- a/editions/tw5.com/tiddlers/widgets/LetListWidget.tid +++ /dev/null @@ -1,26 +0,0 @@ -title: LetListWidget -created: 20250307212252946 -modified: 20250307212252946 -tags: Widgets -caption: letlist - -! Introduction - -<<.from-version "5.3.7">> The <<.wid letlist>> widget allows multiple variables to be set in one operation, each to the list of results from evaluating an attribute. It is similar to the <<.wlink LetWidget>> widget but assigns the complete list of results to a multi-valued variable instead of just the first result. - -! Content and Attributes - -The content of the <<.wid letlist>> widget is the scope for the value assigned to the variable. - -|!Attribute |!Description | -|//{attributes}// |Each attribute name specifies a variable name. The attribute value is a list that is assigned to the variable | - -The <<.wid letlist>> is primarily intended to be used with the filtered attribute syntax. This enables the result list of a filter to be assigned to a multi-valued variable: - -``` -<$letlist varname={{{ [all[tiddlers]sort[]] }}}> -<$text text={{{ [varlist[varname]] +[join[-]] }}}/> - -``` - -Attributes are evaluated in the order they are written. Attributes with the same name are allowed. Each time a duplicate attribute is encountered, it will replace the existing value set by the earlier duplicate. diff --git a/editions/tw5.com/tiddlers/widgets/LetWidget.tid b/editions/tw5.com/tiddlers/widgets/LetWidget.tid index b27306cfb..5a6e42434 100644 --- a/editions/tw5.com/tiddlers/widgets/LetWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/LetWidget.tid @@ -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.3.7">> The <<.wid let>> widget also allows [[multi-valued variables|Multi-Valued Variables]] to be set in one operation, each 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. The [[varlist Operator]] gives access to the complete list of results. For example: + +``` +<$letlist varname={{{ [all[tiddlers]sort[]] }}}> +<$text text={{{ [varlist[varname]] +[join[-]] }}}/> + +``` + ! Examples Consider a case where you need to set multiple variables, where some depend on the evaluation of others. From 211b13526501b7e61e42a6ffc6695688281258b2 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 18 Mar 2025 15:53:28 +0000 Subject: [PATCH 17/30] 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. --- core/modules/filterrunprefixes/let.js | 40 +++++++++---------- .../data/let-filter-prefix/ResultList.tid | 2 +- .../tests/data/let-filter-prefix/Simple.tid | 2 +- .../tests/data/let-filter-prefix/UseInput.tid | 2 +- .../filters/syntax/Let Filter Run Prefix.tid | 11 +++-- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js index 8f877b18c..d8afdd1a1 100644 --- a/core/modules/filterrunprefixes/let.js +++ b/core/modules/filterrunprefixes/let.js @@ -16,27 +16,27 @@ Assign a value to a variable Export our filter prefix function */ exports.let = function(operationSubFunction,options) { - if(options.suffixes[0] && options.suffixes[0][0]) { - // Save the variable name - var name = options.suffixes[0][0]; - // Return the filter run prefix function - return function(results,source,widget) { - // Set the input source to the incoming results - var inputSource = widget.wiki.makeTiddlerIterator(results.toArray()); - // Assign the result of the subfunction to the variable - var variables = {}; - variables[name] = operationSubFunction(inputSource,widget); - // Clear the results - results.clear(); - // Return the variables - return { - variables: variables - }; + // Return the filter run prefix function + return function(results,source,widget) { + // 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] = results.toArray() + // Clear the results + results.clear(); + // Return the variables + return { + variables: variables }; - } else { - // Return nothing if there is no variable name - return function(results,source,widget) {}; - } + }; }; })(); diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid index ee9e8e800..4263e565b 100644 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid @@ -5,7 +5,7 @@ tags: [[$:/tags/wiki-test-spec]] title: Output -<$text text={{{ :let:varname[all[tiddlers]] [varlist[varname]sort[]join[,]] }}}/> +<$text text={{{ [all[tiddlers]] :let[[varname]] [varlist[varname]sort[]join[,]] }}}/> + title: ExpectedResult diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid index 6385c83e2..f16ea107f 100644 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid @@ -5,7 +5,7 @@ tags: [[$:/tags/wiki-test-spec]] title: Output -<$text text={{{ :let:varname[[magpie]] [] +[join[-]] }}}/> +<$text text={{{ [[magpie]] :let[[varname]] [] +[join[-]] }}}/> + title: ExpectedResult diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid index c20e16442..4d0aa8eba 100644 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid @@ -5,7 +5,7 @@ tags: [[$:/tags/wiki-test-spec]] title: Output -<$text text={{{ [[colossus]] :let:another[all[]] [] +[join[-]] }}}/> +<$text text={{{ [[colossus]] :let[[another]] [] +[join[-]] }}}/> + title: ExpectedResult diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index 28762875f..8d4ed15a4 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -3,7 +3,7 @@ from-version: 5.3.7 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 results of a filter run to a variable +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 @@ -11,14 +11,13 @@ type: text/vnd.tiddlywiki <$railroad text=""" \start none \end none -( ":let" ) -( ":" ) -( : "variable" ) -( ":" ) +( ":let" ) [[run|"Filter Run"]] """/> -<<.from-version "5.3.7">> The `:let` filter run prefix assigns the result list of a filter run to a variable which 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). The special [varlist Operator] is used to access all the items in the result list. +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). The special [varlist Operator] is used to access all the items in the result list. Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. From ac134ac5496423eac57d5df0720f3b7dcb814f0d Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 18 Mar 2025 15:54:51 +0000 Subject: [PATCH 18/30] Missed off 211b13526501b7e61e42a6ffc6695688281258b2 --- .../tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid | 3 --- 1 file changed, 3 deletions(-) diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index 8d4ed15a4..3fea6d36c 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -18,6 +18,3 @@ type: text/vnd.tiddlywiki 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). The special [varlist Operator] is used to access all the items in the result list. - -Within the filter run the [[all Operator]] with an empty parameter retrieves all the titles from the previous filter runs, instead of the usual behaviour of retieving all the titles that were passed to the filter expression. - From 7640135a7ef8ecbe5d56ea2b2dc829c2b8265dbd Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 18 Mar 2025 16:29:11 +0000 Subject: [PATCH 19/30] Docs update --- .../tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index 3fea6d36c..60daba149 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -18,3 +18,5 @@ type: text/vnd.tiddlywiki 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). The special [varlist Operator] is used to access all the items in the result list. + +The `:let` filter run prefix always clears the current result list. \ No newline at end of file From 938e72dc3460f593833ccaa2bf2a4d02b63173dd Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 19 Mar 2025 11:00:03 +0000 Subject: [PATCH 20/30] Bug fix --- core/modules/filterrunprefixes/let.js | 8 +++++--- .../let-filter-prefix/ResultListUnnamedVariable.tid | 12 ++++++++++++ .../data/multi-valued-variables/MissingVariable.tid | 12 ++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid create mode 100644 editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js index d8afdd1a1..3fa3569f5 100644 --- a/core/modules/filterrunprefixes/let.js +++ b/core/modules/filterrunprefixes/let.js @@ -18,6 +18,10 @@ 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) { @@ -29,9 +33,7 @@ exports.let = function(operationSubFunction,options) { } // Assign the result of the subfunction to the variable var variables = {}; - variables[name] = results.toArray() - // Clear the results - results.clear(); + variables[name] = resultList; // Return the variables return { variables: variables diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid new file mode 100644 index 000000000..7f8c8525b --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid @@ -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]] [varlist[varname]sort[]join[,]] }}}/> ++ +title: ExpectedResult + +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid new file mode 100644 index 000000000..30744d844 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid @@ -0,0 +1,12 @@ +title: MultiValuedVariables/MissingVariable +description: Using varlist with a missing variable +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$text text={{{ [varlist[varname]] }}}/> ++ +title: ExpectedResult + +

\ No newline at end of file From 0c24e2f5f1c0566074bf514de454e135bd92a9ad Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 19 Mar 2025 16:14:54 +0000 Subject: [PATCH 21/30] Introduce round brackets for multi-valued filter operands Allowing us to drop the varlist operator --- core/modules/filters.js | 26 +++++++++++++++++- core/modules/filters/title.js | 6 ++++- core/modules/filters/varlist.js | 27 ------------------- .../tiddlers/system/DefaultTiddlers.tid | 3 +-- .../data/let-filter-prefix/ResultList.tid | 2 +- .../ResultListUnnamedVariable.tid | 2 +- .../MissingVariable.tid | 4 +-- .../MultiValuedFunction.tid | 2 +- .../MultiValuedOperands.tid | 18 +++++++++++++ .../data/multi-valued-variables/Simple.tid | 2 +- .../filters/syntax/Let Filter Run Prefix.tid | 2 +- .../tiddlers/filters/varlist Operator.tid | 14 ---------- .../tiddlers/system/DefaultTiddlers.tid | 3 +-- .../variables/Multi-Valued Variables.tid | 8 +++--- .../tw5.com/tiddlers/variables/Variables.tid | 2 +- .../tw5.com/tiddlers/widgets/LetWidget.tid | 8 +++--- 16 files changed, 66 insertions(+), 63 deletions(-) delete mode 100644 core/modules/filters/varlist.js create mode 100644 editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedOperands.tid delete mode 100644 editions/tw5.com/tiddlers/filters/varlist Operator.tid diff --git a/core/modules/filters.js b/core/modules/filters.js index 62f6e7213..7fb2f4834 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -38,7 +38,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"; } @@ -82,6 +82,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)); @@ -255,6 +259,8 @@ exports.compileFilter = function(filterString) { currTiddlerTitle = widget && widget.getVariable("currentTiddler"); $tw.utils.each(operation.operators,function(operator) { var operands = [], + multiValueOperands = [], + isMultiValueOperand = [], operatorFunction; if(!operator.operator) { // Use the "title" operator if no operator is specified @@ -269,13 +275,29 @@ exports.compileFilter = function(filterString) { $tw.utils.each(operator.operands,function(operand) { if(operand.indirect) { 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 @@ -283,6 +305,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, diff --git a/core/modules/filters/title.js b/core/modules/filters/title.js index 88ac6f132..a3eeb83c8 100644 --- a/core/modules/filters/title.js +++ b/core/modules/filters/title.js @@ -24,7 +24,11 @@ exports.title = function(source,operator,options) { } }); } else { - results.push(operator.operand); + if(operator.isMultiValueOperand[0]) { + Array.prototype.push.apply(results,operator.multiValueOperands[0]); + } else { + results.push(operator.operand); + } } return results; }; diff --git a/core/modules/filters/varlist.js b/core/modules/filters/varlist.js deleted file mode 100644 index 0857da2fe..000000000 --- a/core/modules/filters/varlist.js +++ /dev/null @@ -1,27 +0,0 @@ -/*\ -title: $:/core/modules/filters/varlist.js -type: application/javascript -module-type: filteroperator - -Retrieve the value of a variable as a list - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -/* -Export our filter function -*/ -exports.varlist = function(source,operator,options) { - var variableInfo = operator.operand && options.widget.getVariableInfo(operator.operand); - if(variableInfo && variableInfo.text !== undefined) { - return options.wiki.makeTiddlerIterator(variableInfo.resultList); - } else { - return []; - } -}; - -})(); diff --git a/editions/prerelease/tiddlers/system/DefaultTiddlers.tid b/editions/prerelease/tiddlers/system/DefaultTiddlers.tid index 0bd696977..38cb8526e 100644 --- a/editions/prerelease/tiddlers/system/DefaultTiddlers.tid +++ b/editions/prerelease/tiddlers/system/DefaultTiddlers.tid @@ -2,6 +2,5 @@ created: 20131127215321439 modified: 20140912135951542 title: $:/DefaultTiddlers +[[Multi-Valued Variables]] [[Let Filter Run Prefix]] -[[varlist Operator]] -[[LetListWidget]] diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid index 4263e565b..594ad4b4b 100644 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultList.tid @@ -5,7 +5,7 @@ tags: [[$:/tags/wiki-test-spec]] title: Output -<$text text={{{ [all[tiddlers]] :let[[varname]] [varlist[varname]sort[]join[,]] }}}/> +<$text text={{{ [all[tiddlers]] :let[[varname]] [(varname)sort[]join[,]] }}}/> + title: ExpectedResult diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid index 7f8c8525b..58493f2f4 100644 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid @@ -5,7 +5,7 @@ tags: [[$:/tags/wiki-test-spec]] title: Output -<$text text={{{ [all[tiddlers]] :let[all[]tag[nothing]] [varlist[varname]sort[]join[,]] }}}/> +<$text text={{{ [all[tiddlers]] :let[all[]tag[nothing]] [(varname)sort[]join[,]] }}}/> + title: ExpectedResult diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid index 30744d844..8481dcb44 100644 --- a/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid @@ -1,11 +1,11 @@ title: MultiValuedVariables/MissingVariable -description: Using varlist with a missing variable +description: Using multivalued operands with a missing variable type: text/vnd.tiddlywiki-multiple tags: [[$:/tags/wiki-test-spec]] title: Output -<$text text={{{ [varlist[varname]] }}}/> +<$text text={{{ [(varname)] }}}/> + title: ExpectedResult diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedFunction.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedFunction.tid index 228c42bf4..15d20731a 100644 --- a/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedFunction.tid +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedFunction.tid @@ -8,7 +8,7 @@ title: Output \function myfunc() [all[tiddlers]sort[]] <$let varname=<>> -<$text text={{{ [varlist[varname]] +[join[-]] }}}/> +<$text text={{{ [(varname)] +[join[-]] }}}/> + title: ExpectedResult diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedOperands.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedOperands.tid new file mode 100644 index 000000000..902dc5699 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedOperands.tid @@ -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=<>> +<$text text={{{ [(varname)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid index ebaf11901..f52e1e6a1 100644 --- a/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid @@ -7,7 +7,7 @@ title: Output <$let varname={{{ [all[tiddlers]sort[]] }}} varname2=<>> -<$text text={{{ [varlist[varname2]] +[join[-]] }}}/> +<$text text={{{ [(varname2)] +[join[-]] }}}/> + title: ExpectedResult diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index 60daba149..d29a0ad90 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -17,6 +17,6 @@ type: text/vnd.tiddlywiki 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). The special [varlist Operator] is used to access all the items in the result list. +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. The `:let` filter run prefix always clears the current result list. \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/varlist Operator.tid b/editions/tw5.com/tiddlers/filters/varlist Operator.tid deleted file mode 100644 index 60176b36c..000000000 --- a/editions/tw5.com/tiddlers/filters/varlist Operator.tid +++ /dev/null @@ -1,14 +0,0 @@ -caption: varlist -created: 20250307212252946 -modified: 20250307212252946 -op-input: ignored -op-output: the list items stored in the variable -op-parameter: the name of a variable -op-purpose: retrieve the value of a variable as a list -tags: [[Filter Operators]] [[Selection Constructors]] -title: varlist Operator -type: text/vnd.tiddlywiki - -<<.from-version "5.3.7">> The parameter specifies the name of a variable that has been assigned a list of items by a [[let filter run prefix|Let Filter Run Prefix]]. The operator retrieves all the list items stored in the variable. - -<<.operator-examples "varlist">> diff --git a/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid b/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid index 33be88abd..ab7933bc2 100644 --- a/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid +++ b/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid @@ -3,6 +3,5 @@ modified: 20140912135951542 title: $:/DefaultTiddlers type: text/vnd.tiddlywiki +[[Multi-Valued Variables]] [[Let Filter Run Prefix]] -[[varlist Operator]] -[[LetListWidget]] diff --git a/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid b/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid index 302f6f084..cd642ef23 100644 --- a/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid +++ b/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid @@ -3,14 +3,14 @@ created: 20250307212252946 modified: 20250307212252946 tags: Concepts Variables -<<.from-version "5.3.7">> In ordinary usage, [[variables|Variables]] contain a single spinnet 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 the [[varlist Operator]] can be used to access all the values. This makes the existence of multi-valued variables largely invisible unless you specifically need to use them. +<<.from-version "5.3.7">> In ordinary usage, [[variables|Variables]] contain a single spinnet 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 the existence of multi-valued variables largely invisible unless you specifically need to use them. The <<.wid let>> widget also allows [[multi-valued variables|Multi-Valued Variables]] to be set in one operation, each to the complete list of results obtained from evaluating an attribute. For example: ``` -<$letlist varname={{{ [all[tiddlers]sort[]] }}}> -<$text text={{{ [varlist[varname]] +[join[-]] }}}/> - +<$let varname={{{ [all[tiddlers]sort[]] }}}> +<$text text={{{ [(varname)] +[join[-]] }}}/> + ``` diff --git a/editions/tw5.com/tiddlers/variables/Variables.tid b/editions/tw5.com/tiddlers/variables/Variables.tid index 62fd661e0..53868a40a 100644 --- a/editions/tw5.com/tiddlers/variables/Variables.tid +++ b/editions/tw5.com/tiddlers/variables/Variables.tid @@ -9,7 +9,7 @@ type: text/vnd.tiddlywiki * 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.3.7">> In ordinary usage, variables contain a single spinnet 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 the [[varlist Operator]] can be used to access all the values. This makes the existence of multi-valued variables largely invisible unless you specifically need to use them +<<.from-version "5.3.7">> In ordinary usage, variables contain a single spinnet 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 the usual angle brackets retrieves the complete list of values. This makes the existence of multi-valued variables largely 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. diff --git a/editions/tw5.com/tiddlers/widgets/LetWidget.tid b/editions/tw5.com/tiddlers/widgets/LetWidget.tid index 5a6e42434..68d6299d8 100644 --- a/editions/tw5.com/tiddlers/widgets/LetWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/LetWidget.tid @@ -23,12 +23,12 @@ Attributes are evaluated in the order they are written. Attributes with the same <<.from-version "5.3.7">> The <<.wid let>> widget also allows [[multi-valued variables|Multi-Valued Variables]] to be set in one operation, each 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. The [[varlist Operator]] gives access to the complete list of results. For example: +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: ``` -<$letlist varname={{{ [all[tiddlers]sort[]] }}}> -<$text text={{{ [varlist[varname]] +[join[-]] }}}/> - +<$let varname={{{ [all[tiddlers]sort[]] }}}> +<$text text={{{ [(varname)] +[join[-]] }}}/> + ``` ! Examples From bb5061c6ff5716332542049817f0cd89a7d23657 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 20 Mar 2025 09:29:15 +0000 Subject: [PATCH 22/30] 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 --- core/modules/filters.js | 53 ++++++++++--------- .../data/let-filter-prefix/ShortcutSyntax.tid | 12 +++++ .../Interchangeable Filter Run Prefixes.tid | 1 + .../filters/syntax/Let Filter Run Prefix.tid | 6 +++ .../syntax/Shortcut Filter Run Prefixes.tid | 5 +- 5 files changed, 50 insertions(+), 27 deletions(-) create mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/ShortcutSyntax.tid diff --git a/core/modules/filters.js b/core/modules/filters.js index 7fb2f4834..2ca232ab2 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -148,7 +148,7 @@ exports.parseFilter = function(filterString) { p = 0, // Current position in the filter string match; var whitespaceRegExp = /(\s+)/mg, - operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; + operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; while(p < filterString.length) { // Skip any whitespace whitespaceRegExp.lastIndex = p; @@ -159,37 +159,38 @@ 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(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); + } + }); }); - }); + } + } + if(match[4]) { // Opening square bracket + 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; + p = parseFilterOperation(operation.operators,filterString,p); } if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title operation.operators.push( @@ -346,6 +347,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); diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ShortcutSyntax.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ShortcutSyntax.tid new file mode 100644 index 000000000..a7430994c --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ShortcutSyntax.tid @@ -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]] =>varname [] +[join[-]] }}}/> ++ +title: ExpectedResult + +

magpie

\ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid b/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid index 0313f543a..66acfbef7 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid @@ -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]` |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).
But the `+` prefix can change this: diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid index d29a0ad90..807880713 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -19,4 +19,10 @@ The `:let` filter run prefix assigns the title list resulting from previous filt 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: + +``` +=[] =[] =>myvar +``` + The `:let` filter run prefix always clears the current result list. \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid b/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid index fa8bc26e1..224f867bf 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid @@ -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.3.7">> {{Interchangeable Filter Run Prefixes}} \ No newline at end of file From f914aa6605ba11aa62ba7bd72bab126e3ff3a5b3 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 20 Mar 2025 17:46:12 +0000 Subject: [PATCH 23/30] Fix bug exposed in "Filter Operators" tiddler See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2740003414 --- core/modules/widgets/widget.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index ad2ab4c2c..06e023936 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -421,9 +421,13 @@ Widget.prototype.computeAttribute = function(attribute,options) { value = [value]; } } else { // String attribute - value = attribute.value || ""; + value = attribute.value; if(options.asList) { - value = [value]; + if(value === undefined) { + value = []; + } else { + value = [value]; + } } } return value; From b6a171cf5ecc4fd8e57e840f0047932c14c67bf9 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Wed, 26 Mar 2025 20:32:35 +0000 Subject: [PATCH 24/30] Fix bug with missing variable attributes See https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972#issuecomment-2752792329 --- core/modules/widgets/widget.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index aed331c80..0674df670 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -405,12 +405,10 @@ Widget.prototype.computeAttribute = function(attribute,options) { } } else if(attribute.type === "macro") { var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params}); - if(variableInfo) { - if(options.asList) { - value = variableInfo.resultList; - } else { - 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) || ""; From 22eea56b53ff8e1e5172d9fea6fb471869ea2b83 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 29 Mar 2025 12:50:43 +0000 Subject: [PATCH 25/30] Fix bug with round brackets for 2nd parameter onwards --- core/modules/filters.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/modules/filters.js b/core/modules/filters.js index b7cf6354b..d9e87e4a3 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -116,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)); @@ -145,6 +145,14 @@ exports.parseFilter = function(filterString) { p = 0, // Current position in the filter string match; var whitespaceRegExp = /(\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 @@ -162,12 +170,15 @@ exports.parseFilter = function(filterString) { }; 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) { @@ -181,14 +192,17 @@ exports.parseFilter = function(filterString) { }); } } - if(match[4]) { // Opening square bracket + // Opening square bracket + if(match[4]) { p = parseFilterOperation(operation.operators,filterString,p); } else { p = match.index + match[0].length; } } else { + // 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]}]} From d20b02e606c3d7ca4eee84cee429b52a3ae28ac9 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 29 Mar 2025 14:13:23 +0000 Subject: [PATCH 26/30] Allow functions to take multivalued parameters --- core/modules/filters/function.js | 4 ++-- core/modules/filters/unknown.js | 4 ++-- core/modules/widgets/widget.js | 24 +++++++++++++------ .../MultiValuedParameters.tid | 21 ++++++++++++++++ .../MultiValuedParametersShortcut.tid | 21 ++++++++++++++++ .../tests/test-widget-getVariableInfo.js | 4 ++-- 6 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedParameters.tid create mode 100644 editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedParametersShortcut.tid diff --git a/core/modules/filters/function.js b/core/modules/filters/function.js index b6f2fa636..cf7a6bb0d 100644 --- a/core/modules/filters/function.js +++ b/core/modules/filters/function.js @@ -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}); diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js index 8fe2a6889..33b229092 100644 --- a/core/modules/filters/unknown.js +++ b/core/modules/filters/unknown.js @@ -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) { diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 0674df670..67d32e976 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -116,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 @@ -142,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]; @@ -156,9 +158,13 @@ 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] || ""; @@ -197,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 +<$text text={{{ [(output)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedParametersShortcut.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedParametersShortcut.tid new file mode 100644 index 000000000..9dbb5a3f2 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedParametersShortcut.tid @@ -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[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-widget-getVariableInfo.js b/editions/test/tiddlers/tests/test-widget-getVariableInfo.js index dc6c14494..c25f45418 100644 --- a/editions/test/tiddlers/tests/test-widget-getVariableInfo.js +++ b/editions/test/tiddlers/tests/test-widget-getVariableInfo.js @@ -61,10 +61,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("[]"); From 9453e3e2e2d7c080a12d5db879e9508c1abfcaa9 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 29 Mar 2025 14:45:35 +0000 Subject: [PATCH 27/30] Simplify title operator --- core/modules/filters/title.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/modules/filters/title.js b/core/modules/filters/title.js index b995995e1..174c324cd 100644 --- a/core/modules/filters/title.js +++ b/core/modules/filters/title.js @@ -21,11 +21,7 @@ exports.title = function(source,operator,options) { } }); } else { - if(operator.isMultiValueOperand[0]) { - Array.prototype.push.apply(results,operator.multiValueOperands[0]); - } else { - results.push(operator.operand); - } + Array.prototype.push.apply(results,operator.multiValueOperands[0]); } return results; }; From 2c503456531cd228386903037e9829eca6cdf3e7 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 29 Mar 2025 15:35:31 +0000 Subject: [PATCH 28/30] Extend title operator to allow negated form to use multi-valued variables --- core/modules/filters/title.js | 3 ++- .../MultiValuedNegatedTitle.tid | 18 ++++++++++++++++++ editions/tw5.com/tiddlers/filters/title.tid | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedNegatedTitle.tid diff --git a/core/modules/filters/title.js b/core/modules/filters/title.js index 174c324cd..228676da4 100644 --- a/core/modules/filters/title.js +++ b/core/modules/filters/title.js @@ -16,7 +16,8 @@ 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); } }); diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedNegatedTitle.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedNegatedTitle.tid new file mode 100644 index 000000000..576efdade --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MultiValuedNegatedTitle.tid @@ -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[-]] }}}/> + ++ +title: ExpectedResult + +

+ExpectedResult +

\ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/title.tid b/editions/tw5.com/tiddlers/filters/title.tid index 3bb59d73f..45f4eb93d 100644 --- a/editions/tw5.com/tiddlers/filters/title.tid +++ b/editions/tw5.com/tiddlers/filters/title.tid @@ -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.3.7">> 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">> From f3f232e634d8df358c78cb3ec55f4de27cd48bb1 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 30 Mar 2025 19:42:39 +0100 Subject: [PATCH 29/30] Remove duplicate test --- .../tests/data/let-filter-prefix/UseInput.tid | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid deleted file mode 100644 index 4d0aa8eba..000000000 --- a/editions/test/tiddlers/tests/data/let-filter-prefix/UseInput.tid +++ /dev/null @@ -1,12 +0,0 @@ -title: LetFilterRunPrefix/UseInput -description: Using the input of "let" filter run prefix -type: text/vnd.tiddlywiki-multiple -tags: [[$:/tags/wiki-test-spec]] - -title: Output - -<$text text={{{ [[colossus]] :let[[another]] [] +[join[-]] }}}/> -+ -title: ExpectedResult - -

colossus

\ No newline at end of file From c6b499af98a569ae2fa36a6503f606b1d6810031 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 30 Mar 2025 21:44:43 +0100 Subject: [PATCH 30/30] Update action-log widget to log multi-valued attributes --- core/modules/utils/utils.js | 18 ++++++++++--- core/modules/widgets/action-log.js | 26 ++++++++++++------- .../tiddlers/widgets/ActionLogWidget.tid | 1 + 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index a228f91d4..da12bba2d 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -51,14 +51,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 1 ? variableInfo.resultList : variableInfo.text; } } if(this.filter) { diff --git a/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid b/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid index 2fa75cfad..1386c2e64 100644 --- a/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid @@ -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.3.7">> 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.""">>