HTML templating library for JS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 2.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. var regStr = "{{(.+)}}";
  2. var localRegex = new RegExp(regStr);
  3. var globalRegex = new RegExp(regStr, "g");
  4. function expectType(type, val) {
  5. if (typeof val !== type)
  6. throw new TypeError("Expected "+type+", got "+typeof val);
  7. }
  8. function expectInstance(obj, val) {
  9. if (!(val instanceof obj)) {
  10. var regex = /function (.+)\(/;
  11. var expectedName = obj.toString().match(regex)[1];
  12. try {
  13. var realName = val.constructor.toString().match(regex)[1];
  14. } catch (err) {
  15. var realName = "undefined";
  16. }
  17. throw new TypeError("Expected "+expectedName+", got "+realName);
  18. }
  19. }
  20. function hasMultipleStatements(str) {
  21. return (str.indexOf(";") !== -1)
  22. }
  23. function Func(args) {
  24. return Function.apply(this, args);
  25. }
  26. Func.prototype = Function.prototype;
  27. function Templish(str, vals) {
  28. expectType("string", str);
  29. expectInstance(Array, vals);
  30. str = str.replace(/\s+/g, " ").replace(/}}/g, "}}\n");
  31. this.string = str;
  32. this.vals = vals;
  33. this.subs = [];
  34. var matches = str.match(globalRegex);
  35. if (!matches)
  36. return;
  37. matches.forEach(function(m) {
  38. var str = m.match(localRegex)[1];
  39. //Add user defined arguments
  40. var args = [];
  41. vals.forEach(function(v) {
  42. args.push(v);
  43. });
  44. //We also need a callback function, for async things
  45. args.push("cb");
  46. //We want to automatically return if there's only one statement
  47. if (hasMultipleStatements(str))
  48. args.push(str);
  49. else
  50. args.push("return ("+str+")");
  51. this.subs.push({
  52. func: new Func(args),
  53. match: m+"\n"
  54. });
  55. }.bind(this));
  56. }
  57. Templish.prototype.exec = function(vals, cb) {
  58. expectInstance(Object, vals);
  59. expectType("function", cb);
  60. var str = this.string;
  61. //Prepare generic arguments
  62. var args = [];
  63. for (var i in vals) {
  64. var index = this.vals.indexOf(i);
  65. if (index === -1)
  66. throw new Error("Error: argument "+i+" doesn't exist");
  67. args[index] = vals[i];
  68. }
  69. var numArgs = args.length;
  70. //Keep track of how many callbacks we're waiting for
  71. var cbsRemaining = this.subs.length;
  72. //Replace
  73. this.subs.forEach(function(sub, i) {
  74. //Create a callback function for each substitute
  75. args[numArgs] = function(err, res) {
  76. if (err)
  77. throw err;
  78. str = str.replace(sub.match, res);
  79. //If this is the last callback to be executed,
  80. //call back with the resulting string
  81. if (cbsRemaining <= 0)
  82. cb(null, str);
  83. cbsRemaining -= 1;
  84. }
  85. var ret = sub.func.apply(null, args);
  86. //If the substitute's function returns anything, we use that directly,
  87. //but if it doesn't, we wait for a callback
  88. if (ret !== undefined) {
  89. str = str.replace(sub.match, ret);
  90. if (cbsRemaining <= 0)
  91. cb(str);
  92. cbsRemaining -= 1;
  93. }
  94. });
  95. //If there's no asynchronous functions to wait for,
  96. //just call back immediately
  97. if (cbsRemaining === 0)
  98. return cb(null, str);
  99. }
  100. module.exports = Templish;