Skip to content

Instantly share code, notes, and snippets.

@mrlannigan
Created August 21, 2015 16:23
Show Gist options
  • Select an option

  • Save mrlannigan/08ec753740509e9907c8 to your computer and use it in GitHub Desktop.

Select an option

Save mrlannigan/08ec753740509e9907c8 to your computer and use it in GitHub Desktop.
var BPromise = require('bluebird'),
Hoek = require('hoek');
(function (exports) {
exports.shortCodeRegex = /\{\{\s*(.+?)\}\}/g;
exports.hasWhitespaceRegex = /\s+/;
/**
* @param {string} input - Parsed string
* @param {object} [context] - context object defining shortcodes
* @param {object} [callContext] - call context for any functions called within any shortcode replacement
*
* Supported Inputs:
* {{Count_ForSale_Total}}
* {{Count_ForSale_Total test="argument"}}
* {{Test_Func test="argument" nice="dude" }}
*
* Not Supported Inputs:
* {{Count_ForSale_Total test=arguments}}
*/
exports.replace = function doShortCodeReplace(input, context, callContext) {
var typeofInput = typeof input,
queue = [];
if (!input) {
return input;
}
if (typeofInput !== 'string') {
input = input.toString();
}
context = context || {};
callContext = callContext || this;
input.replace(exports.shortCodeRegex, function (wholeMatch, shortCode, offset) {
queue.push({
wholeMatch: wholeMatch,
shortCode: shortCode,
offset: offset
});
return wholeMatch;
});
return BPromise.reduce(queue, function (reduceResult, item) {
var result,
typeofResult,
codeArguments,
codeKey,
tmpCodeArguments,
matchedCodeArguments,
argumentsRegex,
hasWhitespace,
wholeMatch = item.wholeMatch,
shortCode = item.shortCode,
offset = item.offset;
hasWhitespace = exports.hasWhitespaceRegex.test(shortCode);
// test for no whitespace before checking context hash
if (!hasWhitespace) {
// grab value from keyed hash
result = Hoek.reach(context, shortCode);
typeofResult = typeof result;
// check if valid
if (typeofResult === 'undefined') {
reduceResult.push('{{! Invalid Shortcode : "' + shortCode + '" }}');
return reduceResult;
}
// short-circuit processing, no need to continue if not a function
if (typeofResult !== 'function') {
reduceResult.push(result);
return reduceResult;
}
} else {
// if whitespace exists, start processing for arguments
result = shortCode;
}
// check if has arguments
if (hasWhitespace) {
argumentsRegex = /(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[$"']))+.)["']?/g;
result = result.split(' ');
codeKey = result.shift();
tmpCodeArguments = result.join(' ');
// grab value from keyed hash
result = context[codeKey];
typeofResult = typeof result;
if (typeofResult !== 'function') {
reduceResult.push(result);
return reduceResult;
}
codeArguments = {};
while ((matchedCodeArguments = argumentsRegex.exec(tmpCodeArguments)) != null) {
codeArguments[matchedCodeArguments[1]] = matchedCodeArguments[2];
}
if (Object.keys(codeArguments).length < 1) {
reduceResult.push('{{! Invalid Shortcode : "' + shortCode + '" : Invalid arguments format given (no matches) }}');
return reduceResult;
}
return safeFunctionCall(result, callContext, codeArguments, context).then(function (res) {
reduceResult.push(res);
return reduceResult;
});
}
reduceResult.push(result);
return reduceResult;
}, []).then(function (result) {
for (var i = queue.length - 1; i >= 0 ; i--) {
input = strSplice(queue[i].offset, queue[i].wholeMatch.length, input, result[i]);
}
return input;
});
};
/**
Pass through function to safely call function and not disqualifying from compilation
**/
function safeFunctionCall(func, callContext, options, context) {
try {
return func.call(callContext, options, context);
} catch (err) {
var _toString = err.toString;
err.toString = function () {
return '{{! ' + _toString.call(err) + ' }}';
};
return err;
}
}
function strSplice(start, length, string, replacement) {
return string.substr(0,start) + replacement + string.substr(start+length);
}
})(exports);
//TEST
var context = {
"count.for_sale.total": 123456,
Count_ForRent_Total: 9876,
Test_Func: function (options) {
return 'Test Function: ' + JSON.stringify(options);
}
};
// console.log(exports.replace('{{Count_ForSale_Total}} For Sale Homes and {{Test_Func test="dude" neat="oh" sweet="man" }} For Rent Homes', context));
// console.log(exports.replace('', context));
// console.log(exports.replace(false, context));
// console.log(exports.replace('{{count.for_sale.total}}', context));
str = '{{blog name="wpMain" feed="https://wordpress.org/feed/" cache=5}}{{blog name="secondaryFun" feed="https://wordpress.org/feed/" cache=5}}This is a multi-lined string on the {{day_of_month}} of August.\n';
str += 'That works to provide a cool {{wpMain.table_flip}}, another table_flip {{secondaryFun.table_flip}}';
context = {
blog: function (options, context) {
return BPromise.resolve('').delay(1000).then(function (returnStr) {
context.day_of_month = new Date().getDate();
context[options.name] = {};
context[options.name].table_flip = ' (╯°□°)╯︵ ┻━┻';
return returnStr;
});
}
};
exports.replace(str, context).then(function (output) {
console.log(output);
console.log('');
console.log('done');
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment