From ac99d0f7b93c3873466d9e85b0b21a77bf5425aa Mon Sep 17 00:00:00 2001 From: Seth Kinast Date: Wed, 4 Mar 2015 18:10:55 -0800 Subject: [PATCH] Release v1.6.0 --- CHANGELOG.md | 12 +- dist/dust-helpers.js | 278 +++++++++++++++++++++++---------------- dist/dust-helpers.min.js | 6 +- package.json | 6 +- 4 files changed, 182 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12fb34..42710cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ ## Change Log -### upcoming (2014/11/20 00:55 +00:00) +### v1.6.0 (2015/03/05 18:00 +00:00) +- [#110](https://github.com/linkedin/dustjs-helpers/pull/110) Remove {@if} and {@idx} (@jimmyhchan) +- [#111](https://github.com/linkedin/dustjs-helpers/pull/111) Add AMD support (@sethkinast) +- [#109](https://github.com/linkedin/dustjs-helpers/pull/107) {@select} with an undefined key still runs (@sethkinast) +- [#108](https://github.com/linkedin/dustjs-helpers/pull/108) Add {@none} helper (@sethkinast) +- [#103](https://github.com/linkedin/dustjs-helpers/pull/103) Add {@first} and {@last} helpers (@sethkinast) +- [#107](https://github.com/linkedin/dustjs-helpers/pull/107) Add {@any} helper (@sethkinast) + +### v1.5.0 (2014/11/20 00:55 +00:00) - [#102](https://github.com/linkedin/dustjs-helpers/pull/102) Deprecate {@if} and {@idx} helpers (@sethkinast) - [#105](https://github.com/linkedin/dustjs-helpers/pull/105) Comparison helpers should still execute when their key parameter is set, but it resolves to undefined. (@sethkinast) - [#104](https://github.com/linkedin/dustjs-helpers/pull/104) Coerce falsy values when a type is specified by a comparison helper. (@sethkinast) @@ -40,4 +48,4 @@ - [#12](https://github.com/linkedin/dustjs-helpers/pull/12) Math helper with Body issue #120 (@jimmyhchan) - [#6](https://github.com/linkedin/dustjs-helpers/pull/6) Minor README markdown styling fix (@zzen) - [#5](https://github.com/linkedin/dustjs-helpers/pull/5) Updated README to install node dependencies properly (@zzen) -- [#2](https://github.com/linkedin/dustjs-helpers/pull/2) Extend contextDump helper (@rragan) \ No newline at end of file +- [#2](https://github.com/linkedin/dustjs-helpers/pull/2) Extend contextDump helper (@rragan) diff --git a/dist/dust-helpers.js b/dist/dust-helpers.js index aef2cfe..211540c 100644 --- a/dist/dust-helpers.js +++ b/dist/dust-helpers.js @@ -1,7 +1,15 @@ -/*! dustjs-helpers - v1.5.0 +/*! dustjs-helpers - v1.6.0 * https://github.com/linkedin/dustjs-helpers -* Copyright (c) 2014 Aleksander Williams; Released under the MIT License */ -(function(dust){ +* Copyright (c) 2015 Aleksander Williams; Released under the MIT License */ +(function(root, factory) { + if (typeof define === 'function' && define.amd && define.amd.dust === true) { + define(['dust.core'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('dustjs-linkedin')); + } else { + factory(root.dust); + } +}(this, function(dust) { // Use dust's built-in logging when available var _log = dust.log ? function(msg, level) { @@ -18,8 +26,27 @@ function _deprecated(target) { } function isSelect(context) { - var value = context.current(); - return typeof value === "object" && value.isSelect === true; + return context.stack.tail && + typeof context.stack.tail.head.__select__ !== "undefined"; +} + +function getSelectState(context) { + return context.get('__select__'); +} + +function addSelectState(context, key) { + var head = context.stack.head; + return context + .rebase(context.stack.tail) + .push({ "__select__": { + isResolved: false, + isDefaulted: false, + isDeferredComplete: false, + deferreds: [], + key: key + } + }) + .push(head); } // Utility method : toString() equivalent for functions @@ -46,32 +73,39 @@ function filter(chunk, context, bodies, params, filterOp) { var body = bodies.block, actualKey, expectedValue, + selectState, filterOpType = params.filterOpType || ''; - // when @eq, @lt etc are used as standalone helpers, key is required and hence check for defined + // Currently we first check for a key on the helper itself, then fall back to + // looking for a key on the {@select} that contains it. This is undocumented + // behavior that we may or may not support in the future. (If we stop supporting + // it, just switch the order of the test below to check the {@select} first.) if (params.hasOwnProperty("key")) { 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) { + selectState = getSelectState(context); + actualKey = selectState.key; + // Once one truth test in a select passes, short-circuit the rest of the tests + if (selectState.isResolved) { filterOp = function() { return false; }; } } else { - _log("No key specified for filter in:" + filterOpType + " helper "); + _log("No key specified for filter in {@" + filterOpType + "}"); 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; + if(filterOpType === 'default') { + selectState.isDefaulted = true; + } + selectState.isResolved = true; } - // we want helpers without bodies to fail gracefully so check it first + // Helpers without bodies are valid due to the use of {@any} blocks if(body) { - return chunk.render(body, context); + return chunk.render(body, context); } else { - _log("No body specified for " + filterOpType + " helper "); return chunk; } } else if (bodies['else']) { @@ -157,16 +191,18 @@ var helpers = { } }, - "idx": function(chunk, context, bodies) { - var body = bodies.block; - // Will be removed in 1.6 - _deprecated("{@idx}"); - if(body) { - return body(chunk, context.push(context.stack.index)); + "first": function(chunk, context, bodies) { + if (context.stack.index === 0) { + return bodies.block(chunk, context); } - else { - return chunk; + return chunk; + }, + + "last": function(chunk, context, bodies) { + if (context.stack.index === context.stack.of - 1) { + return bodies.block(chunk, context); } + return chunk; }, /** @@ -216,37 +252,6 @@ var helpers = { cond argument should evaluate to a valid javascript expression **/ - "if": function( chunk, context, bodies, params ) { - var body = bodies.block, - skip = bodies['else'], - cond; - - if(params && params.cond) { - // Will be removed in 1.6 - _deprecated("{@if}"); - - cond = dust.helpers.tap(params.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 @@ -267,6 +272,7 @@ var helpers = { _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 @@ -315,7 +321,8 @@ var helpers = { 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 })); + context = addSelectState(context, mathOut); + return chunk.render(bodies.block, context); } else { // self closing math helper will return the calculated output return chunk.write(mathOut); @@ -339,23 +346,28 @@ var helpers = { @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); + var body = bodies.block, + state, key, len, x; + + if (params.hasOwnProperty("key")) { + 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; + if (body) { + context = addSelectState(context, key); + state = getSelectState(context); + chunk = chunk.render(body, context); + // Resolve any deferred blocks (currently just {@any} blocks) + if(state.deferreds.length) { + state.isDeferredComplete = true; + for(x=0, len=state.deferreds.length; x expected; }); - } - return chunk; + params.filterOpType = "gt"; + return filter(chunk, context, bodies, params, function(expected, actual) { return actual > expected; }); }, /** @@ -469,21 +464,82 @@ var helpers = { 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; }); - } + params.filterOpType = "gte"; + return filter(chunk, context, bodies, params, function(expected, actual) { return actual >= expected; }); + }, + + /** + * {@any} + * Outputs as long as at least one truth test inside a {@select} has passed. + * Must be contained inside a {@select} block. + * The passing truth test can be before or after the {@any} block. + */ + "any": function(chunk, context, bodies, params) { + var selectState; + + if(!isSelect(context)) { + _log("{@any} used outside of a {@select} block", "WARN"); + } else { + selectState = getSelectState(context); + if(selectState.isDeferredComplete) { + _log("{@any} nested inside {@any} or {@none} block. It needs its own {@select} block", "WARN"); + } else { + chunk = chunk.map(function(chunk) { + selectState.deferreds.push(function() { + if(selectState.isResolved && !selectState.isDefaulted) { + chunk = chunk.render(bodies.block, context); + } + chunk.end(); + }); + }); + } + } 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"; + /** + * {@none} + * Outputs if no truth tests inside a {@select} pass. + * Must be contained inside a {@select} block. + * The position of the helper does not matter. + */ + "none": function(chunk, context, bodies, params) { + var selectState; + + if(!isSelect(context)) { + _log("{@none} used outside of a {@select} block", "WARN"); + } else { + selectState = getSelectState(context); + if(selectState.isDeferredComplete) { + _log("{@none} nested inside {@any} or {@none} block. It needs its own {@select} block", "WARN"); + } else { + chunk = chunk.map(function(chunk) { + selectState.deferreds.push(function() { + if(!selectState.isResolved) { + chunk = chunk.render(bodies.block, context); + } + chunk.end(); + }); + }); } - return filter(chunk, context, bodies, params, function(expected, actual) { return true; }); + } + return chunk; + }, + + /** + * {@default} + * Outputs if no truth test inside a {@select} has passed. + * Must be contained inside a {@select} block. + */ + "default": function(chunk, context, bodies, params) { + params.filterOpType = "default"; + // Deprecated for removal in 1.7 + _deprecated("{@default}"); + if(!isSelect(context)) { + _log("{@default} used outside of a {@select} block", "WARN"); + return chunk; + } + return filter(chunk, context, bodies, params, function() { return true; }); }, /** @@ -523,12 +579,10 @@ var helpers = { }; - for (var key in helpers) { + for(var key in helpers) { dust.helpers[key] = helpers[key]; } - if(typeof exports !== 'undefined') { - module.exports = dust; - } + return dust; -})(typeof exports !== 'undefined' ? require('dustjs-linkedin') : dust); +})); diff --git a/dist/dust-helpers.min.js b/dist/dust-helpers.min.js index 8fd292a..75983fa 100644 --- a/dist/dust-helpers.min.js +++ b/dist/dust-helpers.min.js @@ -1,4 +1,4 @@ -/*! dustjs-helpers - v1.5.0 +/*! dustjs-helpers - v1.6.0 * https://github.com/linkedin/dustjs-helpers -* Copyright (c) 2014 Aleksander Williams; Released under the MIT License */ -!function(dust){function _deprecated(a){_deprecatedCache[a]||(_log("Deprecation warning: "+a+" is deprecated and will be removed in a future version of dustjs-helpers","WARN"),_log("For help and a deprecation timeline, see https://github.com/linkedin/dustjs-helpers/wiki/Deprecated-Features#"+a.replace(/\W+/g,""),"WARN"),_deprecatedCache[a]=!0)}function isSelect(a){var b=a.current();return"object"==typeof b&&b.isSelect===!0}function jsonFilter(a,b){return"function"==typeof b?b.toString().replace(/(^\s+|\s+$)/gm,"").replace(/\n/gm,"").replace(/,\s*/gm,", ").replace(/\)\{/gm,") {"):b}function filter(a,b,c,d,e){d=d||{};var f,g,h=c.block,i=d.filterOpType||"";if(d.hasOwnProperty("key"))f=dust.helpers.tap(d.key,a,b);else{if(!isSelect(b))return _log("No key specified for filter in:"+i+" helper "),a;f=b.current().selectKey,b.current().isResolved&&(e=function(){return!1})}return g=dust.helpers.tap(d.value,a,b),e(coerce(g,d.type,b),coerce(f,d.type,b))?(isSelect(b)&&(b.current().isResolved=!0),h?a.render(h,b):(_log("No body specified for "+i+" helper "),a)):c["else"]?a.render(c["else"],b):a}function coerce(a,b,c){if("undefined"!=typeof a)switch(b||typeof a){case"number":return+a;case"string":return String(a);case"boolean":return a="false"===a?!1:a,Boolean(a);case"date":return new Date(a);case"context":return c.get(a)}return a}var _log=dust.log?function(a,b){b=b||"INFO",dust.log(a,b)}:function(){},_deprecatedCache={},helpers={tap:function(a,b,c){if("function"!=typeof a)return a;var d,e="";return d=b.tap(function(a){return e+=a,""}).render(a,c),b.untap(),d.constructor!==b.constructor?d:""===e?!1:e},sep:function(a,b,c){var d=c.block;return b.stack.index===b.stack.of-1?a:d?d(a,b):a},idx:function(a,b,c){var d=c.block;return _deprecated("{@idx}"),d?d(a,b.push(b.stack.index)):a},contextDump:function(a,b,c,d){var e,f=d||{},g=f.to||"output",h=f.key||"current";return g=dust.helpers.tap(g,a,b),h=dust.helpers.tap(h,a,b),e="full"===h?JSON.stringify(b.stack,jsonFilter,2):JSON.stringify(b.stack.head,jsonFilter,2),"console"===g?(_log(e),a):(e=e.replace(/b})):a},lte:function(a,b,c,d){return d?(d.filterOpType="lte",filter(a,b,c,d,function(a,b){return a>=b})):a},gt:function(a,b,c,d){return d?(d.filterOpType="gt",filter(a,b,c,d,function(a,b){return b>a})):a},gte:function(a,b,c,d){return d?(d.filterOpType="gte",filter(a,b,c,d,function(a,b){return b>=a})):a},"default":function(a,b,c,d){return d&&(d.filterOpType="default"),filter(a,b,c,d,function(){return!0})},size:function(a,b,c,d){var e,f,g,h=0;if(d=d||{},e=d.key,e&&e!==!0)if(dust.isArray(e))h=e.length;else if(!isNaN(parseFloat(e))&&isFinite(e))h=e;else if("object"==typeof e){f=0;for(g in e)Object.hasOwnProperty.call(e,g)&&f++;h=f}else h=(e+"").length;else h=0;return a.write(h)}};for(var key in helpers)dust.helpers[key]=helpers[key];"undefined"!=typeof exports&&(module.exports=dust)}("undefined"!=typeof exports?require("dustjs-linkedin"):dust); \ No newline at end of file +* Copyright (c) 2015 Aleksander Williams; Released under the MIT License */ +!function(a,b){"function"==typeof define&&define.amd&&define.amd.dust===!0?define(["dust.core"],b):"object"==typeof exports?module.exports=b(require("dustjs-linkedin")):b(a.dust)}(this,function(dust){function a(a){i[a]||(h("Deprecation warning: "+a+" is deprecated and will be removed in a future version of dustjs-helpers","WARN"),h("For help and a deprecation timeline, see https://github.com/linkedin/dustjs-helpers/wiki/Deprecated-Features#"+a.replace(/\W+/g,""),"WARN"),i[a]=!0)}function b(a){return a.stack.tail&&"undefined"!=typeof a.stack.tail.head.__select__}function c(a){return a.get("__select__")}function d(a,b){var c=a.stack.head;return a.rebase(a.stack.tail).push({__select__:{isResolved:!1,isDefaulted:!1,isDeferredComplete:!1,deferreds:[],key:b}}).push(c)}function e(a,b){return"function"==typeof b?b.toString().replace(/(^\s+|\s+$)/gm,"").replace(/\n/gm,"").replace(/,\s*/gm,", ").replace(/\)\{/gm,") {"):b}function f(a,d,e,f,i){f=f||{};var j,k,l,m=e.block,n=f.filterOpType||"";if(f.hasOwnProperty("key"))j=dust.helpers.tap(f.key,a,d);else{if(!b(d))return h("No key specified for filter in {@"+n+"}"),a;l=c(d),j=l.key,l.isResolved&&(i=function(){return!1})}return k=dust.helpers.tap(f.value,a,d),i(g(k,f.type,d),g(j,f.type,d))?(b(d)&&("default"===n&&(l.isDefaulted=!0),l.isResolved=!0),m?a.render(m,d):a):e["else"]?a.render(e["else"],d):a}function g(a,b,c){if("undefined"!=typeof a)switch(b||typeof a){case"number":return+a;case"string":return String(a);case"boolean":return a="false"===a?!1:a,Boolean(a);case"date":return new Date(a);case"context":return c.get(a)}return a}var h=dust.log?function(a,b){b=b||"INFO",dust.log(a,b)}:function(){},i={},j={tap:function(a,b,c){if("function"!=typeof a)return a;var d,e="";return d=b.tap(function(a){return e+=a,""}).render(a,c),b.untap(),d.constructor!==b.constructor?d:""===e?!1:e},sep:function(a,b,c){var d=c.block;return b.stack.index===b.stack.of-1?a:d?d(a,b):a},first:function(a,b,c){return 0===b.stack.index?c.block(a,b):a},last:function(a,b,c){return b.stack.index===b.stack.of-1?c.block(a,b):a},contextDump:function(a,b,c,d){var f,g=d||{},i=g.to||"output",j=g.key||"current";return i=dust.helpers.tap(i,a,b),j=dust.helpers.tap(j,a,b),f="full"===j?JSON.stringify(b.stack,e,2):JSON.stringify(b.stack.head,e,2),"console"===i?(h(f),a):(f=f.replace(/k;k++)g.deferreds[k]()}else h("Missing body block in {@select}");else h("No key provided for {@select}");return a},eq:function(a,b,c,d){return d.filterOpType="eq",f(a,b,c,d,function(a,b){return b===a})},ne:function(a,b,c,d){return d.filterOpType="ne",f(a,b,c,d,function(a,b){return b!==a})},lt:function(a,b,c,d){return d.filterOpType="lt",f(a,b,c,d,function(a,b){return a>b})},lte:function(a,b,c,d){return d.filterOpType="lte",f(a,b,c,d,function(a,b){return a>=b})},gt:function(a,b,c,d){return d.filterOpType="gt",f(a,b,c,d,function(a,b){return b>a})},gte:function(a,b,c,d){return d.filterOpType="gte",f(a,b,c,d,function(a,b){return b>=a})},any:function(a,d,e){var f;return b(d)?(f=c(d),f.isDeferredComplete?h("{@any} nested inside {@any} or {@none} block. It needs its own {@select} block","WARN"):a=a.map(function(a){f.deferreds.push(function(){f.isResolved&&!f.isDefaulted&&(a=a.render(e.block,d)),a.end()})})):h("{@any} used outside of a {@select} block","WARN"),a},none:function(a,d,e){var f;return b(d)?(f=c(d),f.isDeferredComplete?h("{@none} nested inside {@any} or {@none} block. It needs its own {@select} block","WARN"):a=a.map(function(a){f.deferreds.push(function(){f.isResolved||(a=a.render(e.block,d)),a.end()})})):h("{@none} used outside of a {@select} block","WARN"),a},"default":function(c,d,e,g){return g.filterOpType="default",a("{@default}"),b(d)?f(c,d,e,g,function(){return!0}):(h("{@default} used outside of a {@select} block","WARN"),c)},size:function(a,b,c,d){var e,f,g,h=0;if(d=d||{},e=d.key,e&&e!==!0)if(dust.isArray(e))h=e.length;else if(!isNaN(parseFloat(e))&&isFinite(e))h=e;else if("object"==typeof e){f=0;for(g in e)Object.hasOwnProperty.call(e,g)&&f++;h=f}else h=(e+"").length;else h=0;return a.write(h)}};for(var k in j)dust.helpers[k]=j[k];return dust}); \ No newline at end of file diff --git a/package.json b/package.json index 76fbc49..fefb429 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dustjs-helpers", - "version": "1.5.0", + "version": "1.6.0", "author": { "name": "Aleksander Williams", "url": "http://akdubya.github.com/dustjs" @@ -59,10 +59,10 @@ "linkedin" ], "peerDependencies": { - "dustjs-linkedin": "2.5 - 2.6" + "dustjs-linkedin": "2.6 - 2.7" }, "devDependencies": { - "dustjs-linkedin": "2.5 - 2.6", + "dustjs-linkedin": "2.6 - 2.7", "jasmine-node": "~1.13.0", "grunt": "~0.4.2", "grunt-contrib-jshint": "~0.8.0",