This repository has been archived on 2018-10-12. You can view files and clone it, but cannot push or open issues or pull requests.
node-task/node_modules/dustjs-helpers/dist/dust-helpers.js

517 lines
20 KiB
JavaScript
Raw Permalink Normal View History

2014-09-18 16:17:29 -04:00
/*! dustjs-helpers - v1.3.0
* https://github.com/linkedin/dustjs-helpers
* Copyright (c) 2014 Aleksander Williams; Released under the MIT License */
(function(dust){
//using the built in logging method of dust when accessible
var _log = dust.log ? function(mssg) { dust.log(mssg, "INFO"); } : function() {};
function isSelect(context) {
var value = context.current();
return typeof value === "object" && value.isSelect === true;
}
// Utility method : toString() equivalent for functions
function jsonFilter(key, value) {
if (typeof value === "function") {
//to make sure all environments format functions the same way
return value.toString()
//remove all leading and trailing whitespace
.replace(/(^\s+|\s+$)/mg, '')
//remove new line characters
.replace(/\n/mg, '')
//replace , and 0 or more spaces with ", "
.replace(/,\s*/mg, ', ')
//insert space between ){
.replace(/\)\{/mg, ') {')
;
}
return value;
}
// Utility method: to invoke the given filter operation such as eq/gt etc
function filter(chunk, context, bodies, params, filterOp) {
params = params || {};
var body = bodies.block,
actualKey,
expectedValue,
filterOpType = params.filterOpType || '';
// when @eq, @lt etc are used as standalone helpers, key is required and hence check for defined
if ( typeof params.key !== "undefined") {
actualKey = dust.helpers.tap(params.key, chunk, context);
}
else if (isSelect(context)) {
actualKey = context.current().selectKey;
// supports only one of the blocks in the select to be selected
if (context.current().isResolved) {
filterOp = function() { return false; };
}
}
else {
_log("No key specified for filter in:" + filterOpType + " helper ");
return chunk;
}
expectedValue = dust.helpers.tap(params.value, chunk, context);
// coerce both the actualKey and expectedValue to the same type for equality and non-equality compares
if (filterOp(coerce(expectedValue, params.type, context), coerce(actualKey, params.type, context))) {
if (isSelect(context)) {
context.current().isResolved = true;
}
// we want helpers without bodies to fail gracefully so check it first
if(body) {
return chunk.render(body, context);
}
else {
_log("No key specified for filter in:" + filterOpType + " helper ");
return chunk;
}
}
else if (bodies['else']) {
return chunk.render(bodies['else'], context);
}
return chunk;
}
function coerce (value, type, context) {
if (value) {
switch (type || typeof(value)) {
case 'number': return +value;
case 'string': return String(value);
case 'boolean': {
value = (value === 'false' ? false : value);
return Boolean(value);
}
case 'date': return new Date(value);
case 'context': return context.get(value);
}
}
return value;
}
var helpers = {
// Utility helping to resolve dust references in the given chunk
// uses the Chunk.render method to resolve value
/*
Reference resolution rules:
if value exists in JSON:
"" or '' will evaluate to false, boolean false, null, or undefined will evaluate to false,
numeric 0 evaluates to true, so does, string "0", string "null", string "undefined" and string "false".
Also note that empty array -> [] is evaluated to false and empty object -> {} and non-empty object are evaluated to true
The type of the return value is string ( since we concatenate to support interpolated references
if value does not exist in JSON and the input is a single reference: {x}
dust render emits empty string, and we then return false
if values does not exist in JSON and the input is interpolated references : {x} < {y}
dust render emits < and we return the partial output
*/
"tap": function(input, chunk, context) {
// return given input if there is no dust reference to resolve
// dust compiles a string/reference such as {foo} to a function
if (typeof input !== "function") {
return input;
}
var dustBodyOutput = '',
returnValue;
//use chunk render to evaluate output. For simple functions result will be returned from render call,
//for dust body functions result will be output via callback function
returnValue = chunk.tap(function(data) {
dustBodyOutput += data;
return '';
}).render(input, context);
chunk.untap();
//assume it's a simple function call if return result is not a chunk
if (returnValue.constructor !== chunk.constructor) {
//use returnValue as a result of tap
return returnValue;
} else if (dustBodyOutput === '') {
return false;
} else {
return dustBodyOutput;
}
},
"sep": function(chunk, context, bodies) {
var body = bodies.block;
if (context.stack.index === context.stack.of - 1) {
return chunk;
}
if(body) {
return bodies.block(chunk, context);
}
else {
return chunk;
}
},
"idx": function(chunk, context, bodies) {
var body = bodies.block;
if(body) {
return bodies.block(chunk, context.push(context.stack.index));
}
else {
return chunk;
}
},
/**
* contextDump helper
* @param key specifies how much to dump.
* "current" dumps current context. "full" dumps the full context stack.
* @param to specifies where to write dump output.
* Values can be "console" or "output". Default is output.
*/
"contextDump": function(chunk, context, bodies, params) {
var p = params || {},
to = p.to || 'output',
key = p.key || 'current',
dump;
to = dust.helpers.tap(to, chunk, context);
key = dust.helpers.tap(key, chunk, context);
if (key === 'full') {
dump = JSON.stringify(context.stack, jsonFilter, 2);
}
else {
dump = JSON.stringify(context.stack.head, jsonFilter, 2);
}
if (to === 'console') {
_log(dump);
return chunk;
}
else {
return chunk.write(dump);
}
},
/**
if helper for complex evaluation complex logic expressions.
Note : #1 if helper fails gracefully when there is no body block nor else block
#2 Undefined values and false values in the JSON need to be handled specially with .length check
for e.g @if cond=" '{a}'.length && '{b}'.length" is advised when there are chances of the a and b been
undefined or false in the context
#3 Use only when the default ? and ^ dust operators and the select fall short in addressing the given logic,
since eval executes in the global scope
#4 All dust references are default escaped as they are resolved, hence eval will block malicious scripts in the context
Be mindful of evaluating a expression that is passed through the unescape filter -> |s
@param cond, either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. cond="2>3"
a dust reference is also enclosed in double quotes, e.g. cond="'{val}'' > 3"
cond argument should evaluate to a valid javascript expression
**/
"if": function( chunk, context, bodies, params ){
var body = bodies.block,
skip = bodies['else'];
if( params && params.cond){
var cond = params.cond;
cond = dust.helpers.tap(cond, chunk, context);
// eval expressions with given dust references
if(eval(cond)){
if(body) {
return chunk.render( bodies.block, context );
}
else {
_log("Missing body block in the if helper!");
return chunk;
}
}
if(skip){
return chunk.render( bodies['else'], context );
}
}
// no condition
else {
_log("No condition given in the if helper!");
}
return chunk;
},
/**
* math helper
* @param key is the value to perform math against
* @param method is the math method, is a valid string supported by math helper like mod, add, subtract
* @param operand is the second value needed for operations like mod, add, subtract, etc.
* @param round is a flag to assure that an integer is returned
*/
"math": function ( chunk, context, bodies, params ) {
//key and method are required for further processing
if( params && typeof params.key !== "undefined" && params.method ){
var key = params.key,
method = params.method,
// operand can be null for "abs", ceil and floor
operand = params.operand,
round = params.round,
mathOut = null,
operError = function(){
_log("operand is required for this math method");
return null;
};
key = dust.helpers.tap(key, chunk, context);
operand = dust.helpers.tap(operand, chunk, context);
// TODO: handle and tests for negatives and floats in all math operations
switch(method) {
case "mod":
if(operand === 0 || operand === -0) {
_log("operand for divide operation is 0/-0: expect Nan!");
}
mathOut = parseFloat(key) % parseFloat(operand);
break;
case "add":
mathOut = parseFloat(key) + parseFloat(operand);
break;
case "subtract":
mathOut = parseFloat(key) - parseFloat(operand);
break;
case "multiply":
mathOut = parseFloat(key) * parseFloat(operand);
break;
case "divide":
if(operand === 0 || operand === -0) {
_log("operand for divide operation is 0/-0: expect Nan/Infinity!");
}
mathOut = parseFloat(key) / parseFloat(operand);
break;
case "ceil":
mathOut = Math.ceil(parseFloat(key));
break;
case "floor":
mathOut = Math.floor(parseFloat(key));
break;
case "round":
mathOut = Math.round(parseFloat(key));
break;
case "abs":
mathOut = Math.abs(parseFloat(key));
break;
default:
_log("method passed is not supported");
}
if (mathOut !== null){
if (round) {
mathOut = Math.round(mathOut);
}
if (bodies && bodies.block) {
// with bodies act like the select helper with mathOut as the key
// like the select helper bodies['else'] is meaningless and is ignored
return chunk.render(bodies.block, context.push({ isSelect: true, isResolved: false, selectKey: mathOut }));
} else {
// self closing math helper will return the calculated output
return chunk.write(mathOut);
}
} else {
return chunk;
}
}
// no key parameter and no method
else {
_log("Key is a required parameter for math helper along with method/operand!");
}
return chunk;
},
/**
select helper works with one of the eq/ne/gt/gte/lt/lte/default providing the functionality
of branching conditions
@param key, ( required ) either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. key="foo"
a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
@param type (optional), supported types are number, boolean, string, date, context, defaults to string
**/
"select": function(chunk, context, bodies, params) {
var body = bodies.block;
// key is required for processing, hence check for defined
if( params && typeof params.key !== "undefined"){
// returns given input as output, if the input is not a dust reference, else does a context lookup
var key = dust.helpers.tap(params.key, chunk, context);
// bodies['else'] is meaningless and is ignored
if( body ) {
return chunk.render(bodies.block, context.push({ isSelect: true, isResolved: false, selectKey: key }));
}
else {
_log("Missing body block in the select helper ");
return chunk;
}
}
// no key
else {
_log("No key given in the select helper!");
}
return chunk;
},
/**
eq helper compares the given key is same as the expected value
It can be used standalone or in conjunction with select for multiple branching
@param key, The actual key to be compared ( optional when helper used in conjunction with select)
either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. key="foo"
a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
@param value, The expected value to compare to, when helper is used standalone or in conjunction with select
@param type (optional), supported types are number, boolean, string, date, context, defaults to string
Note : use type="number" when comparing numeric
**/
"eq": function(chunk, context, bodies, params) {
if(params) {
params.filterOpType = "eq";
}
return filter(chunk, context, bodies, params, function(expected, actual) { return actual === expected; });
},
/**
ne helper compares the given key is not the same as the expected value
It can be used standalone or in conjunction with select for multiple branching
@param key, The actual key to be compared ( optional when helper used in conjunction with select)
either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. key="foo"
a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
@param value, The expected value to compare to, when helper is used standalone or in conjunction with select
@param type (optional), supported types are number, boolean, string, date, context, defaults to string
Note : use type="number" when comparing numeric
**/
"ne": function(chunk, context, bodies, params) {
if(params) {
params.filterOpType = "ne";
return filter(chunk, context, bodies, params, function(expected, actual) { return actual !== expected; });
}
return chunk;
},
/**
lt helper compares the given key is less than the expected value
It can be used standalone or in conjunction with select for multiple branching
@param key, The actual key to be compared ( optional when helper used in conjunction with select)
either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. key="foo"
a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
@param value, The expected value to compare to, when helper is used standalone or in conjunction with select
@param type (optional), supported types are number, boolean, string, date, context, defaults to string
Note : use type="number" when comparing numeric
**/
"lt": function(chunk, context, bodies, params) {
if(params) {
params.filterOpType = "lt";
return filter(chunk, context, bodies, params, function(expected, actual) { return actual < expected; });
}
},
/**
lte helper compares the given key is less or equal to the expected value
It can be used standalone or in conjunction with select for multiple branching
@param key, The actual key to be compared ( optional when helper used in conjunction with select)
either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. key="foo"
a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
@param value, The expected value to compare to, when helper is used standalone or in conjunction with select
@param type (optional), supported types are number, boolean, string, date, context, defaults to string
Note : use type="number" when comparing numeric
**/
"lte": function(chunk, context, bodies, params) {
if(params) {
params.filterOpType = "lte";
return filter(chunk, context, bodies, params, function(expected, actual) { return actual <= expected; });
}
return chunk;
},
/**
gt helper compares the given key is greater than the expected value
It can be used standalone or in conjunction with select for multiple branching
@param key, The actual key to be compared ( optional when helper used in conjunction with select)
either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. key="foo"
a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
@param value, The expected value to compare to, when helper is used standalone or in conjunction with select
@param type (optional), supported types are number, boolean, string, date, context, defaults to string
Note : use type="number" when comparing numeric
**/
"gt": function(chunk, context, bodies, params) {
// if no params do no go further
if(params) {
params.filterOpType = "gt";
return filter(chunk, context, bodies, params, function(expected, actual) { return actual > expected; });
}
return chunk;
},
/**
gte helper, compares the given key is greater than or equal to the expected value
It can be used standalone or in conjunction with select for multiple branching
@param key, The actual key to be compared ( optional when helper used in conjunction with select)
either a string literal value or a dust reference
a string literal value, is enclosed in double quotes, e.g. key="foo"
a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
@param value, The expected value to compare to, when helper is used standalone or in conjunction with select
@param type (optional), supported types are number, boolean, string, date, context, defaults to string
Note : use type="number" when comparing numeric
**/
"gte": function(chunk, context, bodies, params) {
if(params) {
params.filterOpType = "gte";
return filter(chunk, context, bodies, params, function(expected, actual) { return actual >= expected; });
}
return chunk;
},
// to be used in conjunction with the select helper
// TODO: fix the helper to do nothing when used standalone
"default": function(chunk, context, bodies, params) {
// does not require any params
if(params) {
params.filterOpType = "default";
}
return filter(chunk, context, bodies, params, function(expected, actual) { return true; });
},
/**
* size helper prints the size of the given key
* Note : size helper is self closing and does not support bodies
* @param key, the element whose size is returned
*/
"size": function( chunk, context, bodies, params ) {
var key, value=0, nr, k;
params = params || {};
key = params.key;
if (!key || key === true) { //undefined, null, "", 0
value = 0;
}
else if(dust.isArray(key)) { //array
value = key.length;
}
else if (!isNaN(parseFloat(key)) && isFinite(key)) { //numeric values
value = key;
}
else if (typeof key === "object") { //object test
//objects, null and array all have typeof ojbect...
//null and array are already tested so typeof is sufficient http://jsperf.com/isobject-tests
nr = 0;
for(k in key){
if(Object.hasOwnProperty.call(key,k)){
nr++;
}
}
value = nr;
} else {
value = (key + '').length; //any other value (strings etc.)
}
return chunk.write(value);
}
};
for (var key in helpers) {
dust.helpers[key] = helpers[key];
}
if(typeof exports !== 'undefined') {
module.exports = dust;
}
})(typeof exports !== 'undefined' ? require('dustjs-linkedin') : dust);