From 2d8cb3392fd930bc337a63249ee30810c06bfe38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Str=C3=A1nsk=C3=BD?= Date: Tue, 17 Mar 2015 09:26:52 +0100 Subject: [PATCH] Add support for custom functions. --- artifacts/jmespath.min.js | 4 ++-- jmespath.js | 12 ++++++++---- test/jmespath.js | 9 +++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/artifacts/jmespath.min.js b/artifacts/jmespath.min.js index 647757a..70985ec 100644 --- a/artifacts/jmespath.min.js +++ b/artifacts/jmespath.min.js @@ -1,2 +1,2 @@ -/*! jmespath 2015-02-25 */ -!function(a){"use strict";function b(a){return null!==a?"[object Array]"===toString.call(a):!1}function c(a){return null!==a?"[object Object]"===toString.call(a):!1}function d(a,e){if(a===e)return!0;var f=toString.call(a);if(f!==toString.call(e))return!1;if(b(a)===!0){if(a.length!==e.length)return!1;for(var g=0;g":!0,"=":!0,"!":!0},r={0:!0,1:!0,2:!0,3:!0,4:!0,5:!0,6:!0,7:!0,8:!0,9:!0,"-":!0},s=g(p,r),t={" ":!0," ":!0,"\n":!0};h.prototype={tokenize:function(a){var b=[];this.current=0;for(var c,d,e;this.current"===c)return"="===a[this.current]?(this.current++,{type:"GTE",value:">=",start:b}):{type:"GT",value:">",start:b};if("="===c&&"="===a[this.current])return this.current++,{type:"EQ",value:"==",start:b}}},consumeLiteral:function(a){this.current++;for(var b,c=this.current,d=a.length;"`"!==a[this.current]&&this.current=0)return!0;if(c.indexOf(a)>=0)return!0;if(!(d.indexOf(a[0])>=0))return!1;try{return JSON.parse(a),!0}catch(e){return!1}}},i.prototype={parse:function(a){this.loadTokens(a),this.index=0;var b=this.expression(0);if("EOF"!==this.lookahead(0)){var c=this.lookaheadToken(0),d=new Error("Unexpected token type: "+c.type+", value: "+c.value);throw d.name="ParserError",d}return b},loadTokens:function(a){var b=new h,c=b.tokenize(a);c.push({type:"EOF",value:"",start:a.length}),this.tokens=c},expression:function(a){var b=this.lookaheadToken(0);this.advance();for(var c="nud"+b.type,d=this[c]||this.errorToken,e=d.call(this,b),f=this.lookahead(0);ab;){if("Colon"===c)b++,this.advance();else{if("Number"!==c){var d=this.lookahead(0),e=new Error("Syntax error, unexpected token: "+d.value+"("+d.type+")");throw e.name="Parsererror",e}a[b]=this.lookaheadToken(0).value,this.advance()}c=this.lookahead(0)}return this.match("Rbracket"),{type:"Slice",children:a}},nudLbrace:function(){return this.parseMultiselectHash()},ledDot:function(a){var b,c=this.bindingPower.Dot;return"Star"!==this.lookahead(0)?(b=this.parseDotRHS(c),{type:"Subexpression",children:[a,b]}):(this.advance(),b=this.parseProjectionRHS(c),{type:"ValueProjection",children:[a,b]})},nudFilter:function(){return this.ledFilter({type:"Identity"})},ledFilter:function(a){var b,c=this.expression(0);return this.match("Rbracket"),b="Flatten"===this.lookahead(0)?{type:"Identity"}:this.parseProjectionRHS(this.bindingPower.Filter),{type:"FilterProjection",children:[a,b,c]}},ledEQ:function(a){return this.parseComparator(a,"EQ")},ledNE:function(a){return this.parseComparator(a,"NE")},ledGT:function(a){return this.parseComparator(a,"GT")},ledGTE:function(a){return this.parseComparator(a,"GTE")},ledLT:function(a){return this.parseComparator(a,"LT")},ledLTE:function(a){return this.parseComparator(a,"LTE")},parseComparator:function(a,b){var c=this.expression(this.bindingPower[b]);return{type:"Comparator",name:b,children:[a,c]}},ledLbracket:function(a){var b,c=this.lookaheadToken(0);return"Number"===c.type||"Colon"===c.type?(b=this.parseIndexExpression(),this.projectIfSlice(a,b)):(this.match("Star"),this.match("Rbracket"),b=this.parseProjectionRHS(this.bindingPower.Star),{type:"Projection",children:[a,b]})},nudFlatten:function(){var a={type:"Flatten",children:[{type:"Identity"}]},b=this.parseProjectionRHS(this.bindingPower.Flatten);return{type:"Projection",children:[a,b]}},ledFlatten:function(a){var b={type:"Flatten",children:[a]},c=this.parseProjectionRHS(this.bindingPower.Flatten);return{type:"Projection",children:[b,c]}},ledLparen:function(a){for(var b,c,d=a.name,e=[];"Rparen"!==this.lookahead(0);)"Current"===this.lookahead(0)?(b={type:"Current"},this.advance()):b=this.expression(0),"Comma"===this.lookahead(0)&&this.match("Comma"),e.push(b);return this.match("Rparen"),c={type:"Function",name:d,children:e}},parseDotRHS:function(a){var b=this.lookahead(0),c=["UnquotedIdentifier","QuotedIdentifier","Star"];return c.indexOf(b)>=0?this.expression(a):"Lbracket"===b?(this.match("Lbracket"),this.parseMultiselectList()):"Lbrace"===b?(this.match("Lbrace"),this.parseMultiselectHash()):void 0},parseProjectionRHS:function(a){var b;if(this.bindingPower[this.lookahead(0)]<10)b={type:"Identity"};else if("Lbracket"===this.lookahead(0))b=this.expression(a);else if("Filter"===this.lookahead(0))b=this.expression(a);else{if("Dot"!==this.lookahead(0)){var c=this.lookaheadToken(0),d=new Error("Sytanx error, unexpected token: "+c.value+"("+c.type+")");throw d.name="ParserError",d}this.match("Dot"),b=this.parseDotRHS(a)}return b},parseMultiselectList:function(){for(var a=[];"Rbracket"!==this.lookahead(0);){var b=this.expression(0);if(a.push(b),"Comma"===this.lookahead(0)&&(this.match("Comma"),"Rbracket"===this.lookahead(0)))throw new Error("Unexpected token Rbracket")}return this.match("Rbracket"),{type:"MultiSelectList",children:a}},parseMultiselectHash:function(){for(var a,b,c,d,e=[],f=["UnquotedIdentifier","QuotedIdentifier"];;){if(a=this.lookaheadToken(0),f.indexOf(a.type)<0)throw new Error("Expecting an identifier token, got: "+a.type);if(b=a.value,this.advance(),this.match("Colon"),c=this.expression(0),d={type:"KeyValuePair",name:b,value:c},e.push(d),"Comma"===this.lookahead(0))this.match("Comma");else if("Rbrace"===this.lookahead(0)){this.match("Rbrace");break}}return{type:"MultiSelectHash",children:e}}},j.prototype={search:function(a,b){return this.visit(a,b)},visit:function(a,b){var c=this["visit"+a.type];if(void 0===c)throw new Error("Unknown node type: "+a.type);return c.call(this,a,b)},visitField:function(a,b){if(null===b)return null;if(c(b)){var d=b[a.name];return void 0===d?null:d}return null},visitSubexpression:function(a,b){for(var c=this.visit(a.children[0],b),d=1;dd&&(d=c.length+d);var e=c[d];return void 0===e&&(e=null),e},visitSlice:function(a,c){if(!b(c))return null;var d,e=a.children.slice(0),f=this.computeSliceParams(c.length,e),g=f[0],h=f[1],i=f[2],j=[];if(i>0)for(d=g;h>d;d+=i)j.push(c[d]);else for(d=g;d>h;d+=i)j.push(c[d]);return j},computeSliceParams:function(a,b){var c=b[0],d=b[1],e=b[2],f=[null,null,null];if(null===e)e=1;else if(0===e){var g=new Error("Invalid slice, step cannot be 0");throw g.name="RuntimeError",g}var h=0>e?!0:!1;return c=null===c?h?a-1:0:this.capSliceRange(a,c,e),d=null===d?h?-1:a:this.capSliceRange(a,d,e),f[0]=c,f[1]=d,f[2]=e,f},capSliceRange:function(a,b,c){return 0>b?(b+=a,0>b&&(b=0>c?-1:0)):b>=a&&(b=0>c?a-1:a),b},visitProjection:function(a,c){var d=this.visit(a.children[0],c);if(!b(d))return null;for(var e=[],f=0;ff;break;case"GTE":c=e>=f;break;case"LT":c=f>e;break;case"LTE":c=f>=e;break;default:throw new Error("Unknown comparator: "+a.name)}return c},visitFlatten:function(a,c){var d=this.visit(a.children[0],c);if(!b(d))return null;for(var e=[],f=0;f=0;e--)d+=c[e];return d}var f=a[0].slice(0);return f.reverse(),f},functionAbs:function(a){return Math.abs(a[0])},functionCeil:function(a){return Math.ceil(a[0])},functionAvg:function(a){for(var b=0,c=a[0],d=0;d=0},functionFloor:function(a){return Math.floor(a[0])},functionLength:function(a){return c(a[0])?Object.keys(a[0]).length:a[0].length},functionMax:function(a){if(a[0].length>0){var b=this.getTypeName(a[0][0]);if("number"===b)return Math.max.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;e0){var b=this.getTypeName(a[0][0]);if("number"===b)return Math.min.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;eh?1:h>g?-1:a[0]-b[0]});for(var i=0;ig&&(g=c,b=e[h]);return b},functionMinBy:function(a){for(var b,c,d=a[1],e=a[0],f=this.createKeyFunction(d,["number","string"]),g=1/0,h=0;hc&&(g=c,b=e[h]);return b},createKeyFunction:function(a,b){var c=this,d=this.interpreter,e=function(e){var f=d.visit(a,e);if(b.indexOf(c.getTypeName(f))<0){var g="TypeError: expected one of "+b+", received "+c.getTypeName(f);throw new Error(g)}return f};return e}},a.tokenize=m,a.compile=l,a.search=n,a.Parser=i,a.strictDeepEqual=d}("undefined"==typeof exports?this.jmespath={}:exports); \ No newline at end of file +/*! jmespath 2015-03-17 */ +!function(a){"use strict";function b(a){return null!==a?"[object Array]"===toString.call(a):!1}function c(a){return null!==a?"[object Object]"===toString.call(a):!1}function d(a,e){if(a===e)return!0;var f=toString.call(a);if(f!==toString.call(e))return!1;if(b(a)===!0){if(a.length!==e.length)return!1;for(var g=0;g":!0,"=":!0,"!":!0},r={0:!0,1:!0,2:!0,3:!0,4:!0,5:!0,6:!0,7:!0,8:!0,9:!0,"-":!0},s=g(p,r),t={" ":!0," ":!0,"\n":!0};h.prototype={tokenize:function(a){var b=[];this.current=0;for(var c,d,e;this.current"===c)return"="===a[this.current]?(this.current++,{type:"GTE",value:">=",start:b}):{type:"GT",value:">",start:b};if("="===c&&"="===a[this.current])return this.current++,{type:"EQ",value:"==",start:b}}},consumeLiteral:function(a){this.current++;for(var b,c=this.current,d=a.length;"`"!==a[this.current]&&this.current=0)return!0;if(c.indexOf(a)>=0)return!0;if(!(d.indexOf(a[0])>=0))return!1;try{return JSON.parse(a),!0}catch(e){return!1}}},i.prototype={parse:function(a){this.loadTokens(a),this.index=0;var b=this.expression(0);if("EOF"!==this.lookahead(0)){var c=this.lookaheadToken(0),d=new Error("Unexpected token type: "+c.type+", value: "+c.value);throw d.name="ParserError",d}return b},loadTokens:function(a){var b=new h,c=b.tokenize(a);c.push({type:"EOF",value:"",start:a.length}),this.tokens=c},expression:function(a){var b=this.lookaheadToken(0);this.advance();for(var c="nud"+b.type,d=this[c]||this.errorToken,e=d.call(this,b),f=this.lookahead(0);ab;){if("Colon"===c)b++,this.advance();else{if("Number"!==c){var d=this.lookahead(0),e=new Error("Syntax error, unexpected token: "+d.value+"("+d.type+")");throw e.name="Parsererror",e}a[b]=this.lookaheadToken(0).value,this.advance()}c=this.lookahead(0)}return this.match("Rbracket"),{type:"Slice",children:a}},nudLbrace:function(){return this.parseMultiselectHash()},ledDot:function(a){var b,c=this.bindingPower.Dot;return"Star"!==this.lookahead(0)?(b=this.parseDotRHS(c),{type:"Subexpression",children:[a,b]}):(this.advance(),b=this.parseProjectionRHS(c),{type:"ValueProjection",children:[a,b]})},nudFilter:function(){return this.ledFilter({type:"Identity"})},ledFilter:function(a){var b,c=this.expression(0);return this.match("Rbracket"),b="Flatten"===this.lookahead(0)?{type:"Identity"}:this.parseProjectionRHS(this.bindingPower.Filter),{type:"FilterProjection",children:[a,b,c]}},ledEQ:function(a){return this.parseComparator(a,"EQ")},ledNE:function(a){return this.parseComparator(a,"NE")},ledGT:function(a){return this.parseComparator(a,"GT")},ledGTE:function(a){return this.parseComparator(a,"GTE")},ledLT:function(a){return this.parseComparator(a,"LT")},ledLTE:function(a){return this.parseComparator(a,"LTE")},parseComparator:function(a,b){var c=this.expression(this.bindingPower[b]);return{type:"Comparator",name:b,children:[a,c]}},ledLbracket:function(a){var b,c=this.lookaheadToken(0);return"Number"===c.type||"Colon"===c.type?(b=this.parseIndexExpression(),this.projectIfSlice(a,b)):(this.match("Star"),this.match("Rbracket"),b=this.parseProjectionRHS(this.bindingPower.Star),{type:"Projection",children:[a,b]})},nudFlatten:function(){var a={type:"Flatten",children:[{type:"Identity"}]},b=this.parseProjectionRHS(this.bindingPower.Flatten);return{type:"Projection",children:[a,b]}},ledFlatten:function(a){var b={type:"Flatten",children:[a]},c=this.parseProjectionRHS(this.bindingPower.Flatten);return{type:"Projection",children:[b,c]}},ledLparen:function(a){for(var b,c,d=a.name,e=[];"Rparen"!==this.lookahead(0);)"Current"===this.lookahead(0)?(b={type:"Current"},this.advance()):b=this.expression(0),"Comma"===this.lookahead(0)&&this.match("Comma"),e.push(b);return this.match("Rparen"),c={type:"Function",name:d,children:e}},parseDotRHS:function(a){var b=this.lookahead(0),c=["UnquotedIdentifier","QuotedIdentifier","Star"];return c.indexOf(b)>=0?this.expression(a):"Lbracket"===b?(this.match("Lbracket"),this.parseMultiselectList()):"Lbrace"===b?(this.match("Lbrace"),this.parseMultiselectHash()):void 0},parseProjectionRHS:function(a){var b;if(this.bindingPower[this.lookahead(0)]<10)b={type:"Identity"};else if("Lbracket"===this.lookahead(0))b=this.expression(a);else if("Filter"===this.lookahead(0))b=this.expression(a);else{if("Dot"!==this.lookahead(0)){var c=this.lookaheadToken(0),d=new Error("Sytanx error, unexpected token: "+c.value+"("+c.type+")");throw d.name="ParserError",d}this.match("Dot"),b=this.parseDotRHS(a)}return b},parseMultiselectList:function(){for(var a=[];"Rbracket"!==this.lookahead(0);){var b=this.expression(0);if(a.push(b),"Comma"===this.lookahead(0)&&(this.match("Comma"),"Rbracket"===this.lookahead(0)))throw new Error("Unexpected token Rbracket")}return this.match("Rbracket"),{type:"MultiSelectList",children:a}},parseMultiselectHash:function(){for(var a,b,c,d,e=[],f=["UnquotedIdentifier","QuotedIdentifier"];;){if(a=this.lookaheadToken(0),f.indexOf(a.type)<0)throw new Error("Expecting an identifier token, got: "+a.type);if(b=a.value,this.advance(),this.match("Colon"),c=this.expression(0),d={type:"KeyValuePair",name:b,value:c},e.push(d),"Comma"===this.lookahead(0))this.match("Comma");else if("Rbrace"===this.lookahead(0)){this.match("Rbrace");break}}return{type:"MultiSelectHash",children:e}}},j.prototype={search:function(a,b){return this.visit(a,b)},visit:function(a,b){var c=this["visit"+a.type];if(void 0===c)throw new Error("Unknown node type: "+a.type);return c.call(this,a,b)},visitField:function(a,b){if(null===b)return null;if(c(b)){var d=b[a.name];return void 0===d?null:d}return null},visitSubexpression:function(a,b){for(var c=this.visit(a.children[0],b),d=1;dd&&(d=c.length+d);var e=c[d];return void 0===e&&(e=null),e},visitSlice:function(a,c){if(!b(c))return null;var d,e=a.children.slice(0),f=this.computeSliceParams(c.length,e),g=f[0],h=f[1],i=f[2],j=[];if(i>0)for(d=g;h>d;d+=i)j.push(c[d]);else for(d=g;d>h;d+=i)j.push(c[d]);return j},computeSliceParams:function(a,b){var c=b[0],d=b[1],e=b[2],f=[null,null,null];if(null===e)e=1;else if(0===e){var g=new Error("Invalid slice, step cannot be 0");throw g.name="RuntimeError",g}var h=0>e?!0:!1;return c=null===c?h?a-1:0:this.capSliceRange(a,c,e),d=null===d?h?-1:a:this.capSliceRange(a,d,e),f[0]=c,f[1]=d,f[2]=e,f},capSliceRange:function(a,b,c){return 0>b?(b+=a,0>b&&(b=0>c?-1:0)):b>=a&&(b=0>c?a-1:a),b},visitProjection:function(a,c){var d=this.visit(a.children[0],c);if(!b(d))return null;for(var e=[],f=0;ff;break;case"GTE":c=e>=f;break;case"LT":c=f>e;break;case"LTE":c=f>=e;break;default:throw new Error("Unknown comparator: "+a.name)}return c},visitFlatten:function(a,c){var d=this.visit(a.children[0],c);if(!b(d))return null;for(var e=[],f=0;f=0;e--)d+=c[e];return d}var f=a[0].slice(0);return f.reverse(),f},functionAbs:function(a){return Math.abs(a[0])},functionCeil:function(a){return Math.ceil(a[0])},functionAvg:function(a){for(var b=0,c=a[0],d=0;d=0},functionFloor:function(a){return Math.floor(a[0])},functionLength:function(a){return c(a[0])?Object.keys(a[0]).length:a[0].length},functionMax:function(a){if(a[0].length>0){var b=this.getTypeName(a[0][0]);if("number"===b)return Math.max.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;e0){var b=this.getTypeName(a[0][0]);if("number"===b)return Math.min.apply(Math,a[0]);for(var c=a[0],d=c[0],e=1;eh?1:h>g?-1:a[0]-b[0]});for(var i=0;ig&&(g=c,b=e[h]);return b},functionMinBy:function(a){for(var b,c,d=a[1],e=a[0],f=this.createKeyFunction(d,["number","string"]),g=1/0,h=0;hc&&(g=c,b=e[h]);return b},createKeyFunction:function(a,b){var c=this,d=this.interpreter,e=function(e){var f=d.visit(a,e);if(b.indexOf(c.getTypeName(f))<0){var g="TypeError: expected one of "+b+", received "+c.getTypeName(f);throw new Error(g)}return f};return e}},a.tokenize=m,a.compile=l,a.search=n,a.Parser=i,a.strictDeepEqual=d}("undefined"==typeof exports?this.jmespath={}:exports); \ No newline at end of file diff --git a/jmespath.js b/jmespath.js index df03488..0851657 100644 --- a/jmespath.js +++ b/jmespath.js @@ -1090,7 +1090,8 @@ } }; - function Runtime(interpreter) { + function Runtime(interpreter, options) { + this.options = options || {}; this.interpreter = interpreter; this.functionTable = { // name: [function, ] @@ -1168,7 +1169,10 @@ callFunction: function(name, resolvedArgs) { var functionEntry = this.functionTable[name]; if (functionEntry === undefined) { - throw new Error("Unknown function: " + name + "()"); + if(this.options.resolveUnknownFunction) { + return this.options.resolveUnknownFunction(name, resolvedArgs, this); + } + throw new Error("Unknown function: " + name + "()"); } this.validateArgs(name, resolvedArgs, functionEntry.signature); return functionEntry.func.call(this, resolvedArgs); @@ -1555,12 +1559,12 @@ return lexer.tokenize(stream); } - function search(data, expression) { + function search(data, expression, options) { var parser = new Parser(); // This needs to be improved. Both the interpreter and runtime depend on // each other. The runtime needs the interpreter to support exprefs. // There's likely a clean way to avoid the cyclic dependency. - var runtime = new Runtime(); + var runtime = new Runtime(undefined, options); var interpreter = new TreeInterpreter(runtime); runtime.interpreter = interpreter; var node = parser.parse(expression); diff --git a/test/jmespath.js b/test/jmespath.js index 15f61c1..38c7bec 100644 --- a/test/jmespath.js +++ b/test/jmespath.js @@ -215,3 +215,12 @@ describe('strictDeepEqual', function() { {a: {b: [1, 4]}}), false); }); }); + +describe('options', function() { + it('should accept custom functions', function() { + assert.deepEqual( + jmespath.search({ x: [5,6,7] }, "example(x[1])", { + resolveUnknownFunction: function(fn, args) { return { fn: fn, output: args[0]*2 };}}), + { fn: "example", output: 12 }); + }); +});