1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-06-26 07:13:15 +00:00

First commit

This commit is contained in:
jeremy@jermolene.com 2022-08-30 18:01:46 +01:00
parent 127f660c91
commit 3f68b13699
9 changed files with 468 additions and 13 deletions

View File

@ -195,12 +195,20 @@ exports.parseFilter = function(filterString) {
exports.getFilterOperators = function() {
if(!this.filterOperators) {
$tw.Wiki.prototype.filterOperators = {};
$tw.modules.applyMethods("filteroperator",this.filterOperators);
$tw.Wiki.prototype.oldFilterOperators = {};
$tw.modules.applyMethods("filteroperator",this.oldFilterOperators);
$tw.Wiki.prototype.newFilterOperators = {};
$tw.modules.applyMethods("newfilteroperator",this.newFilterOperators);
$tw.Wiki.prototype.filterOperators = $tw.utils.extend({},$tw.Wiki.prototype.oldFilterOperators,$tw.Wiki.prototype.newFilterOperators);
}
return this.filterOperators;
};
exports.isFilterOperatorNew = function(name) {
this.getFilterOperators();
return name in $tw.Wiki.prototype.newFilterOperators;
};
exports.getFilterRunPrefixes = function() {
if(!this.filterRunPrefixes) {
$tw.Wiki.prototype.filterRunPrefixes = {};
@ -209,11 +217,21 @@ exports.getFilterRunPrefixes = function() {
return this.filterRunPrefixes;
}
exports.filterTiddlers = function(filterString,widget,source) {
exports.filterTiddlersPolymorphic = function(filterString,widget,source) {
var fn = this.compileFilter(filterString);
return fn.call(this,source,widget);
};
exports.filterTiddlers = function(filterString,widget,source) {
var results = this.filterTiddlersPolymorphic(filterString,widget,source);
for(var t=0; t<results.length; t++) {
if(typeof results[t] !== "string") {
results[t] = $tw.utils.filterItemToString(results[t]);
}
}
return results;
};
/*
Compile a filter into a function with the signature fn(source,widget) where:
source: an iterator function for the source tiddlers, called source(iterator), where iterator is called as iterator(tiddler,title)
@ -250,15 +268,18 @@ exports.compileFilter = function(filterString) {
currTiddlerTitle = widget && widget.getVariable("currentTiddler");
$tw.utils.each(operation.operators,function(operator) {
var operands = [],
operatorFunction;
operatorFunction,
operatorName;
if(!operator.operator) {
operatorFunction = filterOperators.title;
operatorName = "title";
} else if(!filterOperators[operator.operator]) {
operatorFunction = filterOperators.field;
operatorName = "field";
} else {
operatorFunction = filterOperators[operator.operator];
operatorName = operator.operator;
}
$tw.utils.each(operator.operands,function(operand) {
if(operand.indirect) {
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
@ -270,7 +291,17 @@ exports.compileFilter = function(filterString) {
}
operands.push(operand.value);
});
// If this is a legacy monomorphic operator then wrap the accumulator source in a wrapper that converts each item to a string
if(!self.isFilterOperatorNew(operatorName)) {
var innerAccumulator = accumulator;
accumulator = function(iterator) {
innerAccumulator(function(ignored,item) {
var title = $tw.utils.filterItemToString(item),
tiddler = self.getTiddler(title);
iterator(tiddler,title);
});
};
}
// Invoke the appropriate filteroperator module
results = operatorFunction(accumulator,{
operator: operator.operator,
@ -337,7 +368,7 @@ exports.compileFilter = function(filterString) {
if(!widget) {
widget = $tw.rootWidget;
}
var results = new $tw.utils.LinkedList();
var results = new $tw.utils.SimpleList();
$tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,widget);
});

View File

@ -1,7 +1,7 @@
/*\
title: $:/core/modules/filters/count.js
type: application/javascript
module-type: filteroperator
module-type: newfilteroperator
Filter operator returning the number of entries in the current list.
@ -20,7 +20,7 @@ exports.count = function(source,operator,options) {
source(function(tiddler,title) {
count++;
});
return [count + ""];
return [count];
};
})();

View File

@ -0,0 +1,35 @@
/*\
title: $:/core/modules/filters/format/json.js
type: application/javascript
module-type: formatfilteroperator
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.json = function(source,operand,options) {
var results = [],
spaces = null;
if(operand) {
spaces = /^\d+$/.test(operand) ? parseInt(operand,10) : operand;
}
source(function(tiddler,title) {
var data = $tw.utils.parseJSONSafe(title);
try {
data = JSON.parse(title);
} catch(e) {
data = undefined;
}
if(data !== undefined) {
results.push(JSON.stringify(data,null,spaces));
}
});
return results;
};
})();

View File

@ -0,0 +1,142 @@
/*\
title: $:/core/modules/filters/json-ops.js
type: application/javascript
module-type: newfilteroperator
Filter operators for JSON operations
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["jsonget"] = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var data = $tw.utils.filterItemToObject(title,{defaultValue: title,parseStringsAsJson: true});
if(data !== undefined) {
var item = getDataItem(data,operator.operands);
if(item !== undefined) {
results.push(item);
}
}
});
return results;
};
exports["jsonindexes"] = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var data = $tw.utils.filterItemToObject(title,{defaultValue: title,parseStringsAsJson: true});
if(data !== undefined) {
var item = getDataItemKeysAsStrings(data,operator.operands);
if(item !== undefined) {
results.push.apply(results,item);
}
}
});
return results;
};
exports["jsontype"] = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var data = $tw.utils.filterItemToObject(title,{defaultValue: title,parseStringsAsJson: true});
if(data !== undefined) {
var item = getFilterItemType(data,operator.operands);
if(item !== undefined) {
results.push(item);
}
}
});
return results;
};
exports["jsontypecheck"] = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var data = $tw.utils.filterItemToObject(title,{defaultValue: title,parseStringsAsJson: false});
if(data !== undefined) {
var item = getFilterItemType(data,[]);
if(item === operator.operand) {
results.push(title);
}
}
});
return results;
};
/*
Given a JSON data structure and an array of index strings, return an array of the string representation of the keys of the item at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
function getDataItemKeysAsStrings(data,indexes) {
// Get the item
var item = getDataItem(data,indexes);
// Return the item keys as a string
return convertDataItemKeysToStrings(item);
}
/*
Return an array of the string representation of the keys of a data item, or "undefined" if the item is undefined
*/
function convertDataItemKeysToStrings(item) {
// Return the item as a string
if(item === undefined) {
return item;
} else if(typeof item === "object") {
if(item === null) {
return [];
}
var results = [];
if($tw.utils.isArray(item)) {
for(var i=0; i<item.length; i++) {
results.push(i.toString());
}
return results;
} else {
$tw.utils.each(Object.keys(item).sort(),function(key) {
results.push(key);
});
return results;
}
}
return [];
}
function getFilterItemType(data,indexes) {
// Get the item
var item = getDataItem(data,indexes);
// Return the item type
if(item === undefined) {
return item;
} else if(item === null) {
return "null";
} else if($tw.utils.isArray(item)) {
return "array";
} else if(typeof item === "object") {
return "object";
} else {
return typeof item;
}
}
/*
Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid
*/
function getDataItem(data,indexes) {
if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) {
return data;
}
// Get the item
var item = data;
for(var i=0; i<indexes.length; i++) {
if(item !== undefined) {
item = item[indexes[i]];
}
}
return item;
}
})();

View File

@ -1,7 +1,7 @@
/*\
title: $:/core/modules/filters/math.js
type: application/javascript
module-type: filteroperator
module-type: newfilteroperator
Filter operators for math. Unary/binary operators work on each item in turn, and return a new item list.
@ -207,7 +207,10 @@ function makeNumericBinaryOperator(fnCalc) {
var result = [],
numOperand = $tw.utils.parseNumber(operator.operand);
source(function(tiddler,title) {
result.push($tw.utils.stringifyNumber(fnCalc($tw.utils.parseNumber(title),numOperand)));
if(typeof title !== "string") {
console.log("YES!")
}
result.push(fnCalc($tw.utils.parseNumber(title),numOperand));
});
return result;
};
@ -226,7 +229,7 @@ function makeNumericReducingOperator(fnCalc,initialValue,fnFinal) {
if(fnFinal) {
value = fnFinal(value,result.length,result);
}
return [$tw.utils.stringifyNumber(value)];
return [value];
};
};
@ -238,7 +241,7 @@ function makeNumericArrayOperator(fnCalc) {
});
results = fnCalc(results);
$tw.utils.each(results,function(value,index) {
results[index] = $tw.utils.stringifyNumber(value);
results[index] = value;
});
return results;
};

View File

@ -0,0 +1,86 @@
/*\
module-type: utils
title: $:/core/modules/utils/simple-list.js
type: application/javascript
Switched from a linked list to simplify things
\*/
(function(){
function SimpleList() {
this.clear();
};
Object.defineProperty(SimpleList.prototype,"length", {
get: function() {
return this.list.length;
}
});
SimpleList.prototype.clear = function() {
this.list = [];
};
SimpleList.prototype.remove = function(value) {
if($tw.utils.isArray(value)) {
for(var t=0; t<value.length; t++) {
this._remove(value[t]);
}
} else {
this._remove(value);
}
};
/*
Push behaves like array.push and accepts multiple string arguments. But it also
accepts a single array argument too, to be consistent with its other methods.
*/
SimpleList.prototype.push = function(/* values */) {
var values = arguments;
if(arguments.length === 1 && $tw.utils.isArray(values[0])) {
values = values[0];
}
for(var i = 0; i < values.length; i++) {
this._push(values[i]);
}
return this.list.length;
};
SimpleList.prototype.pushTop = function(value) {
// this.push(value);
// -or-
$tw.utils.pushTop(this.list,value);
};
SimpleList.prototype.each = function(callback) {
$tw.utils.each(this.list,callback);
};
SimpleList.prototype.toArray = function() {
return this.list.slice(0);
};
SimpleList.prototype.makeTiddlerIterator = function(wiki) {
var self = this;
return function(callback) {
self.each(function(title) {
callback(wiki.getTiddler(title),title);
});
};
};
SimpleList.prototype._remove = function(value) {
var p = this.list.indexOf(value);
if(p !== -1) {
this.list.splice(p,1);
}
};
SimpleList.prototype._push = function(value) {
this.list.push(value);
};
exports.SimpleList = SimpleList;
})();

View File

@ -992,4 +992,53 @@ exports.makeCompareFunction = function(type,options) {
return (types[type] || types[options.defaultType] || types.number);
};
exports.filterItemToString = function(value) {
switch(typeof value) {
case "undefined":
return "undefined"
case "object":
return JSON.stringify(value);
case "boolean":
return value ? "true" : "false";
case "number":
return value.toString();
case "bigint":
return value.toString();
case "string":
return value;
case "symbol":
throw "Filter operators cannot return Symbols";
case "function":
throw "Filter operators cannot return Functions";
}
};
exports.filterItemToObject = function(value,options) {
options = options || {};
var defaultValue = options.defaultValue || {};
switch(typeof value) {
case "undefined":
return defaultValue;
case "object":
console.log("Got an object!")
return value;
case "boolean":
return value;
case "number":
return value;
case "bigint":
return value;
case "string":
if(options.parseStringsAsJson) {
return $tw.utils.parseJSONSafe(value,defaultValue);
} else {
return value;
}
case "symbol":
throw "Filter operators cannot return Symbols";
case "function":
throw "Filter operators cannot return Functions";
}
};
})();

View File

@ -707,6 +707,10 @@ Tests the filtering mechanism.
expect(wiki.filterTiddlers("1 2 3 4 +[min[2]]").join(",")).toBe("1,2,2,2");
});
it("should handle type conversions", function() {
expect(wiki.filterTiddlers("[[2]add[2]addprefix[donkey]]").join(",")).toBe("donkey4");
});
/* listops filters */
it("should handle the allafter operator", function() {

View File

@ -0,0 +1,105 @@
/*\
title: test-json-filters.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Tests the JSON filters and the format:json operator
\*/
(function(){
/* jslint node: true, browser: true */
/* eslint-env node, browser, jasmine */
/* eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/
/* global $tw, require */
"use strict";
describe("json filter tests", function() {
var wiki = new $tw.Wiki();
var tiddlers = [{
title: "First",
text: '{"a":"one","b":"","c":1.618,"d": {"e": "four","f": ["five","six",true,false,null]}}',
type: "application/json"
},{
title: "Second",
text: '["une","deux","trois"]',
type: "application/json"
}];
wiki.addTiddlers(tiddlers);
it("should support the getindex operator", function() {
expect(wiki.filterTiddlers("[{First}getindex[b]]")).toEqual([]);
});
it("should support the jsonget operator", function() {
expect(wiki.filterTiddlers("[{First}jsonget[]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']);
expect(wiki.filterTiddlers("[{First}jsonget[a]]")).toEqual(["one"]);
expect(wiki.filterTiddlers("[{First}jsonget[b]]")).toEqual([""]);
expect(wiki.filterTiddlers("[{First}jsonget[missing-property]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonget[d]]")).toEqual(['{"e":"four","f":["five","six",true,false,null]}']);
expect(wiki.filterTiddlers("[{First}jsonget[d]jsonget[f]]")).toEqual(['["five","six",true,false,null]']);
expect(wiki.filterTiddlers("[{First}jsonget[d],[e]]")).toEqual(["four"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f]]")).toEqual(['["five","six",true,false,null]']);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[0]]")).toEqual(["five"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[1]]")).toEqual(["six"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[2]]")).toEqual(["true"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[3]]")).toEqual(["false"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[4]]")).toEqual(["null"]);
});
it("should support the jsonindexes operator", function() {
expect(wiki.filterTiddlers("[{Second}jsonindexes[]]")).toEqual(["0","1","2"]);
expect(wiki.filterTiddlers("[{First}jsonindexes[]]")).toEqual(["a","b","c","d"]);
expect(wiki.filterTiddlers("[{First}jsonindexes[a]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[b]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d]]")).toEqual(["e","f"]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[e]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f]]")).toEqual(["0","1","2","3","4"]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[0]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[1]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[2]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[3]]")).toEqual([]);
expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[4]]")).toEqual([]);
});
it("should support the jsontype operator", function() {
expect(wiki.filterTiddlers("[{First}jsontype[]]")).toEqual(["object"]);
expect(wiki.filterTiddlers("[{First}jsontype[a]]")).toEqual(["string"]);
expect(wiki.filterTiddlers("[{First}jsontype[b]]")).toEqual(["string"]);
expect(wiki.filterTiddlers("[{First}jsontype[c]]")).toEqual(["number"]);
expect(wiki.filterTiddlers("[{First}jsontype[d]]")).toEqual(["object"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[e]]")).toEqual(["string"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f]]")).toEqual(["array"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[0]]")).toEqual(["string"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[1]]")).toEqual(["string"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[2]]")).toEqual(["boolean"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[3]]")).toEqual(["boolean"]);
expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[4]]")).toEqual(["null"]);
});
it("should support the jsontypecheck operator", function() {
expect(wiki.filterTiddlers("[{First}jsontypecheck[string]]")).toEqual(['{"a":"one","b":"","c":1.618,"d": {"e": "four","f": ["five","six",true,false,null]}}']);
expect(wiki.filterTiddlers("[{First}jsonget[]jsontypecheck[object]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']);
expect(wiki.filterTiddlers("[{First}jsonget[a]jsontypecheck[string]]")).toEqual(["one"]);
expect(wiki.filterTiddlers("[{First}jsonget[b]jsontypecheck[string]]")).toEqual([""]);
expect(wiki.filterTiddlers("[{First}jsonget[c]jsontypecheck[number]]")).toEqual(["1.618"]);
expect(wiki.filterTiddlers("[{First}jsonget[d]jsontypecheck[object]]")).toEqual(['{"e":"four","f":["five","six",true,false,null]}']);
expect(wiki.filterTiddlers("[{First}jsonget[d],[e]jsontypecheck[string]]")).toEqual(["four"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f]jsontypecheck[array]]")).toEqual(['["five","six",true,false,null]']);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[0]jsontypecheck[string]]")).toEqual(["five"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[1]jsontypecheck[string]]")).toEqual(["six"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[2]jsontypecheck[boolean]]")).toEqual(["true"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[3]jsontypecheck[boolean]]")).toEqual(["false"]);
expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[4]jsontypecheck[null]]")).toEqual(["null"]);
});
it("should support the jsontype operator", function() {
expect(wiki.filterTiddlers("[{First}format:json[]]")).toEqual(["{\"a\":\"one\",\"b\":\"\",\"c\":1.618,\"d\":{\"e\":\"four\",\"f\":[\"five\",\"six\",true,false,null]}}"]);
expect(wiki.filterTiddlers("[{First}format:json[4]]")).toEqual(["{\n \"a\": \"one\",\n \"b\": \"\",\n \"c\": 1.618,\n \"d\": {\n \"e\": \"four\",\n \"f\": [\n \"five\",\n \"six\",\n true,\n false,\n null\n ]\n }\n}"]);
expect(wiki.filterTiddlers("[{First}format:json[ ]]")).toEqual(["{\n \"a\": \"one\",\n \"b\": \"\",\n \"c\": 1.618,\n \"d\": {\n \"e\": \"four\",\n \"f\": [\n \"five\",\n \"six\",\n true,\n false,\n null\n ]\n }\n}"]);
});
});
})();