From 021e9b8c4d68f549550ebc3a2a7ba89af0cf6050 Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Mon, 21 Jun 2021 21:59:58 +0200 Subject: [PATCH] :map filter run prefix with docs and tests (#5813) --- core/modules/filterrunprefixes/map.js | 39 +++++++++++++++++++ .../tiddlers/tests/test-prefixes-filter.js | 11 ++++++ .../filters/syntax/Filter Expression.tid | 9 +++-- .../Map Filter Run Prefix (Examples).tid | 16 ++++++++ .../filters/syntax/Map Filter Run Prefix.tid | 19 +++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 core/modules/filterrunprefixes/map.js create mode 100644 editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix (Examples).tid create mode 100644 editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix.tid diff --git a/core/modules/filterrunprefixes/map.js b/core/modules/filterrunprefixes/map.js new file mode 100644 index 000000000..a70f6b0f4 --- /dev/null +++ b/core/modules/filterrunprefixes/map.js @@ -0,0 +1,39 @@ +/*\ +title: $:/core/modules/filterrunprefixes/map.js +type: application/javascript +module-type: filterrunprefix +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.map = function(operationSubFunction,options) { + return function(results,source,widget) { + if(results.length > 0) { + var inputTitles = results.toArray(); + results.clear(); + $tw.utils.each(inputTitles,function(title) { + var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{ + getVariable: function(name) { + switch(name) { + case "currentTiddler": + return "" + title; + case "..currentTiddler": + return widget.getVariable("currentTiddler"); + default: + return widget.getVariable(name); + } + } + }); + results.push(filtered[0] || ""); + }); + } + } +}; + +})(); \ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-prefixes-filter.js b/editions/test/tiddlers/tests/test-prefixes-filter.js index 1c380e347..c43ee6976 100644 --- a/editions/test/tiddlers/tests/test-prefixes-filter.js +++ b/editions/test/tiddlers/tests/test-prefixes-filter.js @@ -220,6 +220,7 @@ describe("'reduce' and 'intersection' filter prefix tests", function() { wiki.addTiddler({ title: "Brownies", text: "//This is a sample shopping list item for the [[Shopping List Example]]//", + description: "A square of rich chocolate cake", tags: ["shopping","food"], price: "4.99", quantity: "1" @@ -228,6 +229,7 @@ describe("'reduce' and 'intersection' filter prefix tests", function() { title: "Chick Peas", text: "//This is a sample shopping list item for the [[Shopping List Example]]//", tags: ["shopping","food"], + description: "a round yellow seed", price: "1.32", quantity: "5" }); @@ -242,6 +244,7 @@ describe("'reduce' and 'intersection' filter prefix tests", function() { title: "Rice Pudding", price: "2.66", quantity: "4", + description: "", tags: ["shopping", "dairy"], text: "//This is a sample shopping list item for the [[Shopping List Example]]//" }); @@ -374,6 +377,14 @@ describe("'reduce' and 'intersection' filter prefix tests", function() { expect(wiki.filterTiddlers("[tag[cakes]] :sort:string:casesensitive[{!!title}]").join(",")).toBe("Cheesecake,Chocolate Cake,Persian love cake,Pound cake,cheesecake,chocolate cake"); expect(wiki.filterTiddlers("[tag[cakes]] :sort:string:casesensitive,reverse[{!!title}]").join(",")).toBe("chocolate cake,cheesecake,Pound cake,Persian love cake,Chocolate Cake,Cheesecake"); }); + + it("should handle the :map prefix", function() { + expect(wiki.filterTiddlers("[tag[shopping]] :map[get[title]]").join(",")).toBe("Brownies,Chick Peas,Milk,Rice Pudding"); + expect(wiki.filterTiddlers("[tag[shopping]] :map[get[description]]").join(",")).toBe("A square of rich chocolate cake,a round yellow seed,,"); + expect(wiki.filterTiddlers("[tag[shopping]] :map[get[description]else{!!title}]").join(",")).toBe("A square of rich chocolate cake,a round yellow seed,Milk,Rice Pudding"); + // Return the first title from :map if the filter returns more than one result + expect(wiki.filterTiddlers("[tag[shopping]] :map[tags[]]").join(",")).toBe("shopping,shopping,shopping,shopping"); + }); }); })(); \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/Filter Expression.tid b/editions/tw5.com/tiddlers/filters/syntax/Filter Expression.tid index 34c07faea..fb621743b 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Filter Expression.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Filter Expression.tid @@ -1,5 +1,5 @@ created: 20150124182421000 -modified: 20210522162642994 +modified: 20210618153333369 tags: [[Filter Syntax]] title: Filter Expression type: text/vnd.tiddlywiki @@ -26,14 +26,17 @@ If a run has: * named prefix `:intersection` replaces all filter output so far with titles that are present in the output of this run, as well as the output from previous runs. Forms the input for the next run. <<.from-version "5.1.23">> * named prefix `:reduce` replaces all filter output so far with a single item by repeatedly applying a formula to each input title. A typical use is to add up the values in a given field of each input title. <<.from-version "5.1.23">> ** [[Examples|Filter Run Prefix (Examples)]] -* named prefix `:sort` sorts all filter output so far by applying this run to each input title and sorting according to that output. <<.from-version "5.1.24">> +* named prefix `:sort` sorts all filter output so far by applying this run to each input title and sorting according to that output. <<.from-version "5.2.0">> ** See [[Sort Filter Run Prefix]]. +* named prefix `:map` transforms all filter output so far by applying this run to each input title and replacing the input title with the output of this run for that title. +** See [[Map Filter Run Prefix]]. <<.from-version "5.2.0">> + <<.tip "Compare named filter run prefix `:filter` with [[filter Operator]] which applies a subfilter to every input title, removing the titles that return an empty result from the subfilter">> <<.tip "Compare named filter run prefix `:reduce` with [[reduce Operator]] which is used to used to flatten a list of items down to a single item by repeatedly applying a subfilter.">> -<<.tip """Within the filter runs prefixed with `:reduce`, `:sort` and `:filter`, the "currentTiddler" variable is set to the title of the tiddler being processed. The value of currentTiddler outside the subfilter is available in the variable "..currentTiddler".<<.from-version "5.1.24">>""" >> +<<.tip """Within the filter runs prefixed with `:reduce`, `:sort`, `:map` and `:filter`, the "currentTiddler" variable is set to the title of the tiddler being processed. The value of currentTiddler outside the subfilter is available in the variable "..currentTiddler".<<.from-version "5.2.0">>""" >> In technical / logical terms: diff --git a/editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix (Examples).tid b/editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix (Examples).tid new file mode 100644 index 000000000..7c3ff1ec5 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix (Examples).tid @@ -0,0 +1,16 @@ +created: 20210618134753828 +modified: 20210618140945870 +tags: [[Filter Syntax]] [[Filter Run Prefix Examples]] [[Map Filter Run Prefix]] +title: Map Filter Run Prefix (Examples) +type: text/vnd.tiddlywiki + +Replace the input titles with the caption field if it exists, otherwise preserve the input title: + +<<.operator-example 1 "[tag[Widgets]] :map[get[caption]else{!!title}]">> + +<<.tip "The above example is equivalent to `[tag[Widgets]] :map[get[{!!caption}!is[blank]else{!!title}]`. Note that referencing a field as a text reference such as `{!!caption}` returns an empty string for a non-existent or empty caption field. Therefore a check for `is[blank]` is needed before the `else` operator">> + + +For each title in a shopping list, calculate the total cost of purchasing each item: + +<<.operator-example 2 "[tag[shopping]] :map[get[quantity]else[0]multiply{!!price}]">> diff --git a/editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix.tid new file mode 100644 index 000000000..a2f09bc3a --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/syntax/Map Filter Run Prefix.tid @@ -0,0 +1,19 @@ +created: 20210618133745003 +modified: 20210618134747652 +tags: [[Filter Syntax]] [[Filter Run Prefix]] +title: Map Filter Run Prefix +type: text/vnd.tiddlywiki + +<<.from-version "5.2.0">> + +|''purpose'' |modify input titles by the result of evaluating this filter run for each item | +|''input'' |all titles from previous filter runs | +|''output''|the input titles as modified by the result of this filter run | + +Each input title from previous runs is passed to this run in turn. The filter run transforms the input titles and the output of this run replaces the input title. For example, the filter run `[get[caption]else{!!title}]` replaces each input title with its caption field, unless the field does not exist in which case the title is preserved. + +Note that within the filter run, the "currentTiddler" variable is set to the title of the tiddler being processed. This permits filter runs like `:map[{!!price}multiply{!!cost}]` to be used for computation. The value of currentTiddler outside the run is available in the variable "..currentTiddler". + +Filter runs used with the `:map` prefix should return the same number of items that they are passed. Any missing entries will be treated as an empty string. In particular, when retrieving the value of a field with the [[get Operator]] it is helpful to guard against a missing field value using the [[else Operator]]. For example `[get[myfield]else[default-value]...`. + +[[Examples|Map Filter Run Prefix (Examples)]] \ No newline at end of file