diff --git a/core/modules/filters/interleave.js b/core/modules/filters/interleave.js new file mode 100644 index 000000000..956d7647a --- /dev/null +++ b/core/modules/filters/interleave.js @@ -0,0 +1,75 @@ +/*\ +title: $:/core/modules/filters/interleave.js +type: application/javascript +module-type: filteroperator + +Interleave two or more lists one item at a time + +\*/ +(function(){ + + /*jslint node: true, browser: true */ + /*global $tw: false */ + "use strict"; + + /* + Interleave two or more lists + */ + exports.interleave = function(source,operator,options) { + var allowDuplicates = false; + switch(operator.suffix) { + case "raw": + allowDuplicates = true; + break; + case "dedupe": + allowDuplicates = false; + break; + } + var results = new $tw.utils.LinkedList(); + var pushResult = allowDuplicates ? results.push.bind(results) : results.pushTop.bind(results); + + // Alternately, could use the following function definition instead: + // function pushResult(item) { + // if(allowDuplicates) { + // results.push(item); + // } else { + // results.pushTop(item); + // } + // } + + var input = []; + source(function(tiddler,title) { + input.push(title); + }); + var lists = [input]; + operator.operands.forEach(function(operand) { + var list = $tw.utils.parseStringArray(operand,true); + lists.push(list); + }); + var listCount = lists.length; + var indices = new Array(listCount); + var remaining = new Array(listCount); + lists.forEach(function(list,index) { + indices[index] = 0; + remaining[index] = (list.length > 0); + }); + + var current = 0; + while(remaining.some(Boolean)) { + var list = lists[current]; + var index = indices[current]; + if(remaining[current]) { + pushResult(list[index]); + if(index+1 < list.length) { + indices[current] = index+1; + } else { + remaining[current] = false; + } + } + current = (current+1) % listCount; + } + + return results.makeTiddlerIterator(options.wiki); + }; + +})(); diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js index 96100e2f7..7bfa6fb9f 100644 --- a/editions/test/tiddlers/tests/test-filters.js +++ b/editions/test/tiddlers/tests/test-filters.js @@ -821,6 +821,41 @@ Tests the filtering mechanism. expect(wiki.filterTiddlers("a b c d e +[insertbefore:end[b],[foo]]").join(",")).toBe("a,c,d,e,b"); expect(wiki.filterTiddlers("a b c d e +[insertbefore:end[b],]").join(",")).toBe("a,c,d,e,b"); }); + + it("should handle the interleave operator", function() { + expect(wiki.filterTiddlers("").join(",")).toBe(""); + // Interleaving two same-length lists + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3]]").join(",")).toBe("a,1,b,2,c,3"); + // Two lists of length 1 should also work + expect(wiki.filterTiddlers("a +[interleave[1]]").join(",")).toBe("a,1"); + // Two lists of length 0 should produce an empty list + // Note the + so we don't get [all[tiddlers]] as input + expect(wiki.filterTiddlers("+[interleave[]]").join(",")).toBe(""); + // Two lists of different lengths should interleave until one list is exhausted, then take rest of other list + expect(wiki.filterTiddlers("a b c d +[interleave[1 2 3]]").join(",")).toBe("a,1,b,2,c,3,d"); + expect(wiki.filterTiddlers("a b c d e +[interleave[1 2 3]]").join(",")).toBe("a,1,b,2,c,3,d,e"); + expect(wiki.filterTiddlers("a b c d e f +[interleave[1 2 3]]").join(",")).toBe("a,1,b,2,c,3,d,e,f"); + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3 4]]").join(",")).toBe("a,1,b,2,c,3,4"); + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3 4 5]]").join(",")).toBe("a,1,b,2,c,3,4,5"); + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3 4 5 6]]").join(",")).toBe("a,1,b,2,c,3,4,5,6"); + // Interleaving with an empty list produces the original list unchanged + expect(wiki.filterTiddlers("a b c +[interleave[]]").join(",")).toBe("a,b,c"); + expect(wiki.filterTiddlers("+[interleave[a b c]]").join(",")).toBe("a,b,c"); + // Three or more lists can be interleaved using multiple parameters + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3],[red green blue]]").join(",")).toBe("a,1,red,b,2,green,c,3,blue"); + // If any list runs out of items, rest continue interleaving + expect(wiki.filterTiddlers("a b c d e f g h +[interleave[red green blue],[1 2 3 4 5]]").join(",")).toBe("a,red,1,b,green,2,c,blue,3,d,4,e,5,f,g,h"); + // An empty list in the middle of the parameters won't cause issues + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3],[],[red green blue]]").join(",")).toBe("a,1,red,b,2,green,c,3,blue"); + // Can interleave as many lists we we want; we'll test four and five lists + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3],[red green blue],[10 20 30]]").join(",")).toBe("a,1,red,10,b,2,green,20,c,3,blue,30"); + expect(wiki.filterTiddlers("a b c +[interleave[1 2 3],[red green blue],[x y z],[10 20 30]]").join(",")).toBe("a,1,red,x,10,b,2,green,y,20,c,3,blue,z,30"); + // De-duplicates output by default + expect(wiki.filterTiddlers("a b c d +[interleave[1 2 3 2]]").join(",")).toBe("a,1,b,c,3,d,2"); + // Use suffix "raw" to skip de-duplicating + expect(wiki.filterTiddlers("a b c d +[interleave:raw[1 2 3 2]]").join(",")).toBe("a,1,b,2,c,3,d,2"); + + }); it("should handle the move operator", function() { expect(wiki.filterTiddlers("a b c d e +[move[c]]").join(",")).toBe("a,b,d,c,e");