Add new compare filter operator

Fixes #4554
Jermolene-patch-1
Jeremy Ruston 2020-04-13 10:03:01 +01:00
rodzic 3733f8b4ae
commit 7b53f5724c
8 zmienionych plików z 291 dodań i 29 usunięć

Wyświetl plik

@ -409,10 +409,10 @@ $tw.utils.resolvePath = function(sourcepath,rootpath) {
};
/*
Parse a semantic version string into its constituent parts
Parse a semantic version string into its constituent parts -- see https://semver.org
*/
$tw.utils.parseVersion = function(version) {
var match = /^((\d+)\.(\d+)\.(\d+))(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?(?:\+([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?$/.exec(version);
var match = /^v?((\d+)\.(\d+)\.(\d+))(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?(?:\+([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?$/.exec(version);
if(match) {
return {
version: match[1],
@ -427,25 +427,37 @@ $tw.utils.parseVersion = function(version) {
}
};
/*
Returns +1 if the version string A is greater than the version string B, 0 if they are the same, and +1 if B is greater than A.
Missing or malformed version strings are parsed as 0.0.0
*/
$tw.utils.compareVersions = function(versionStringA,versionStringB) {
var defaultVersion = {
major: 0,
minor: 0,
patch: 0
},
versionA = $tw.utils.parseVersion(versionStringA) || defaultVersion,
versionB = $tw.utils.parseVersion(versionStringB) || defaultVersion,
diff = [
versionA.major - versionB.major,
versionA.minor - versionB.minor,
versionA.patch - versionB.patch
];
if((diff[0] > 0) || (diff[0] === 0 && diff[1] > 0) || (diff[0] === 0 & diff[1] === 0 & diff[2] > 0)) {
return +1;
} else if((diff[0] < 0) || (diff[0] === 0 && diff[1] < 0) || (diff[0] === 0 & diff[1] === 0 & diff[2] < 0)) {
return -1;
} else {
return 0;
}
};
/*
Returns true if the version string A is greater than the version string B. Returns true if the versions are the same
*/
$tw.utils.checkVersions = function(versionStringA,versionStringB) {
var defaultVersion = {
major: 0,
minor: 0,
patch: 0
},
versionA = $tw.utils.parseVersion(versionStringA) || defaultVersion,
versionB = $tw.utils.parseVersion(versionStringB) || defaultVersion,
diff = [
versionA.major - versionB.major,
versionA.minor - versionB.minor,
versionA.patch - versionB.patch
];
return (diff[0] > 0) ||
(diff[0] === 0 && diff[1] > 0) ||
(diff[0] === 0 && diff[1] === 0 && diff[2] >= 0);
return $tw.utils.compareVersions(versionStringA,versionStringB) !== -1;
};
/*

Wyświetl plik

@ -0,0 +1,76 @@
/*\
title: $:/core/modules/filters/compare.js
type: application/javascript
module-type: filteroperator
General purpose comparison operator
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.compare = function(source,operator,options) {
var suffixes = operator.suffixes || [],
type = (suffixes[0] || [])[0],
mode = (suffixes[1] || [])[0],
typeFn = types[type] || types.number,
modeFn = modes[mode] || modes.eq,
invert = operator.prefix === "!",
results = [];
source(function(tiddler,title) {
if(modeFn(typeFn(title,operator.operand)) !== invert) {
results.push(title);
}
});
return results;
};
var types = {
"number": function(a,b) {
return compare($tw.utils.parseNumber(a),$tw.utils.parseNumber(b));
},
"integer": function(a,b) {
return compare($tw.utils.parseInt(a),$tw.utils.parseInt(b));
},
"string": function(a,b) {
return compare("" + a,"" +b);
},
"date": function(a,b) {
var dateA = $tw.utils.parseDate(a),
dateB = $tw.utils.parseDate(b);
if(!isFinite(dateA)) {
dateA = new Date(0);
}
if(!isFinite(dateB)) {
dateB = new Date(0);
}
return compare(dateA,dateB);
},
"version": function(a,b) {
return $tw.utils.compareVersions(a,b);
}
};
function compare(a,b) {
if(a > b) {
return +1;
} else if(a < b) {
return -1;
} else {
return 0;
}
};
var modes = {
"eq": function(value) {return value === 0;},
"ne": function(value) {return value !== 0;},
"gteq": function(value) {return value >= 0;},
"gt": function(value) {return value > 0;},
"lteq": function(value) {return value <= 0;},
"lt": function(value) {return value < 0;}
}
})();

Wyświetl plik

@ -114,9 +114,9 @@ exports.minall = makeNumericReducingOperator(
function makeNumericBinaryOperator(fnCalc) {
return function(source,operator,options) {
var result = [],
numOperand = parseNumber(operator.operand);
numOperand = $tw.utils.parseNumber(operator.operand);
source(function(tiddler,title) {
result.push(stringifyNumber(fnCalc(parseNumber(title),numOperand)));
result.push($tw.utils.stringifyNumber(fnCalc($tw.utils.parseNumber(title),numOperand)));
});
return result;
};
@ -129,18 +129,10 @@ function makeNumericReducingOperator(fnCalc,initialValue) {
source(function(tiddler,title) {
result.push(title);
});
return [stringifyNumber(result.reduce(function(accumulator,currentValue) {
return fnCalc(accumulator,parseNumber(currentValue));
return [$tw.utils.stringifyNumber(result.reduce(function(accumulator,currentValue) {
return fnCalc(accumulator,$tw.utils.parseNumber(currentValue));
},initialValue))];
};
}
function parseNumber(str) {
return parseFloat(str) || 0;
}
function stringifyNumber(num) {
return num + "";
}
})();

Wyświetl plik

@ -801,4 +801,16 @@ exports.getSystemInfo = function(str,ending,position) {
return results.join("\n");
};
exports.parseNumber = function(str) {
return parseFloat(str) || 0;
};
exports.parseInt = function(str) {
return parseInt(str) || 0;
};
exports.stringifyNumber = function(num) {
return num + "";
};
})();

Wyświetl plik

@ -0,0 +1,84 @@
/*\
title: test-compare-filters.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Tests the compare filter.
\*/
(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("'compare' filter tests", function() {
var wiki = new $tw.Wiki();
it("should compare numerical equality", function() {
expect(wiki.filterTiddlers("[[2]compare:number:eq[0003]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]compare:number:ne[000003]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]compare:number:eq[3]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]compare:number:ne[3]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]compare:number:eq[2]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]compare:number:ne[2]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]compare:number:eq[x]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]compare:number:ne[x]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]!compare:number:eq[3]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]!compare:number:ne[3]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]!compare:number:eq[2]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]!compare:number:ne[2]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]!compare:number:eq[x]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]!compare:number:ne[x]]").join(",")).toBe("");
});
it("should compare numerical magnitude", function() {
expect(wiki.filterTiddlers("[[2]compare:number:gt[3]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]compare:number:lt[3]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]compare:number:gt[2]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]compare:number:lt[2]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]compare:number:gt[x]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]compare:number:lt[x]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]!compare:number:gt[3]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]!compare:number:lt[3]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]!compare:number:gt[2]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]!compare:number:lt[2]]").join(",")).toBe("2");
expect(wiki.filterTiddlers("[[2]!compare:number:gt[x]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[2]!compare:number:lt[x]]").join(",")).toBe("2");
});
it("should compare string", function() {
expect(wiki.filterTiddlers("[[Monday]compare:string:lt[M]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[Monday]compare:string:lt[W]]").join(",")).toBe("Monday");
expect(wiki.filterTiddlers("Monday Tuesday Wednesday Thursday Friday Saturday Sunday +[compare:string:gt[M]sort[]]").join(",")).toBe("Monday,Saturday,Sunday,Thursday,Tuesday,Wednesday");
expect(wiki.filterTiddlers("Monday Tuesday Wednesday Thursday Friday Saturday Sunday +[compare:string:gt[M]compare:string:lt[W]sort[]]").join(",")).toBe("Monday,Saturday,Sunday,Thursday,Tuesday");
});
it("should compare dates", function() {
expect(wiki.filterTiddlers("[[20200101]compare:date:gt[201912311852]]").join(",")).toBe("20200101");
});
it("should compare version numbers", function() {
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:eq[v1.1.0]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:eq[v1.2.2]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:eq[v1.2.3]]").join(",")).toBe("v1.2.3");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:eq[v1.2.4]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:eq[v2.0.0]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:gt[v1.1.0]]").join(",")).toBe("v1.2.3");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:gt[v1.2.2]]").join(",")).toBe("v1.2.3");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:gt[v1.2.3]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:gt[v1.2.4]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:gt[v2.0.0]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:lt[v1.1.0]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:lt[v1.2.2]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:lt[v1.2.3]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:lt[v1.2.4]]").join(",")).toBe("v1.2.3");
expect(wiki.filterTiddlers("[[v1.2.3]compare:version:lt[v2.0.0]]").join(",")).toBe("v1.2.3");
});
});
})();

Wyświetl plik

@ -107,6 +107,30 @@ describe("Utility tests", function() {
});
it("should compare versions", function() {
var cv = $tw.utils.compareVersions;
expect(cv("v0.0.0","v0.0.0")).toEqual(0);
expect(cv("0.0.0","v0.0.0")).toEqual(0);
expect(cv("v0.0.0","0.0.0")).toEqual(0);
expect(cv("v0.0.0","not a version")).toEqual(0);
expect(cv("v0.0.0",undefined)).toEqual(0);
expect(cv("not a version","v0.0.0")).toEqual(0);
expect(cv(undefined,"v0.0.0")).toEqual(0);
expect(cv("v1.0.0","v1.0.0")).toEqual(0);
expect(cv("v1.0.0","1.0.0")).toEqual(0);
expect(cv("v1.0.1",undefined)).toEqual(+1);
expect(cv("v1.0.1","v1.0.0")).toEqual(+1);
expect(cv("v1.1.1","v1.1.0")).toEqual(+1);
expect(cv("v1.1.2","v1.1.1")).toEqual(+1);
expect(cv("1.1.2","v1.1.1")).toEqual(+1);
expect(cv("v1.0.0","v1.0.1")).toEqual(-1);
expect(cv("v1.1.0","v1.1.1")).toEqual(-1);
expect(cv("v1.1.1","v1.1.2")).toEqual(-1);
expect(cv("1.1.1","1.1.2")).toEqual(-1);
});
});
})();

Wyświetl plik

@ -0,0 +1,51 @@
created: 20200412181551706
modified: 20200412181551706
tags: [[Filter Operators]] [[Mathematics Operators]] [[String Operators]] [[Negatable Operators]]
title: compare Operator
type: text/vnd.tiddlywiki
caption: compare
op-purpose: filter the input by comparing each item against the operand
op-input: a [[selection of titles|Title Selection]]
op-suffix: the <<.op compare>> operator uses a rich suffix, see below for details
op-parameter: the value to compare
op-output: those input titles matching the specified comparison
op-neg-output: those input titles <<.em not>> matching the specified comparison
<<.from-version "5.1.22">>The <<.op compare>> filter allows numerical, string and date comparisons to be performed.
The <<.op compare>> operator uses an extended syntax to specify all the options:
```
[compare:<type>:<mode>[<operand>]]
```
The ''type'' can be:
* "number" - invalid numbers are interpreted as zero
* "integer" - invalid integers are interpreted as zero
* "string"
* "date" - invalid dates are interpreted as 1st January 1970
* "version" - invalid versions are interpreted as "v0.0.0"
The ''mode'' can be:
* "eq" - equal to
* "ne" - not equal ot
* "gteq" - greater than or equal to
* "gt" - greater than
* "lteq" - less than or equal to
* "lt" - less than
The operator compares each item in the selection against the value of the parameter, retaining only those items that pass the specified condition.
For example:
```
[[2]compare:number:eq[3]] returns nothing
[[2]compare:number:lt[3]] returns "2"
[[2]compare:number:eq[2]] returns "2"
```
Note that several of the variants of the <<.op compare>> operator are synonyms for existing operators, and are provided in the interests of consistency. For example, `compare:string:eq[x]` is a synonym for `match[x]`.
<<.operator-examples "compare">>

Wyświetl plik

@ -0,0 +1,11 @@
created: 20200412212935849
modified: 20200412212935849
tags: [[compare Operator]] [[Operator Examples]]
title: compare Operator (Examples)
type: text/vnd.tiddlywiki
<<.operator-example 1 "[[20200101]compare:date:gt[201912311852]]" "compares two partial dates">>
<<.operator-example 2 "[[202001011852]compare:integer:gt[20191231]]" "compares the same two strings as integers">>
<<.operator-example 3 "[list[Days of the Week]compare:string:gt[M]compare:string:lt[W]]">>
<<.operator-example 4 "[[v5.1.23-prerelease]compare:version:gt[v5.1.22]]">>
<<.operator-example 5 "[[1]compare:number:gt[2]then[yes]else[no]]">>