mustache.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*
  2. * CommonJS-compatible mustache.js module
  3. *
  4. * See http://github.com/janl/mustache.js for more info.
  5. */
  6. /*
  7. mustache.js — Logic-less templates in JavaScript
  8. See http://mustache.github.com/ for more info.
  9. */
  10. var Mustache = function() {
  11. var Renderer = function() {};
  12. Renderer.prototype = {
  13. otag: "{{",
  14. ctag: "}}",
  15. pragmas: {},
  16. buffer: [],
  17. pragmas_implemented: {
  18. "IMPLICIT-ITERATOR": true
  19. },
  20. context: {},
  21. render: function(template, context, partials, in_recursion) {
  22. // reset buffer & set context
  23. if(!in_recursion) {
  24. this.context = context;
  25. this.buffer = []; // TODO: make this non-lazy
  26. }
  27. // fail fast
  28. if(!this.includes("", template)) {
  29. if(in_recursion) {
  30. return template;
  31. } else {
  32. this.send(template);
  33. return;
  34. }
  35. }
  36. template = this.render_pragmas(template);
  37. var html = this.render_section(template, context, partials);
  38. if(in_recursion) {
  39. return this.render_tags(html, context, partials, in_recursion);
  40. }
  41. this.render_tags(html, context, partials, in_recursion);
  42. },
  43. /*
  44. Sends parsed lines
  45. */
  46. send: function(line) {
  47. if(line != "") {
  48. this.buffer.push(line);
  49. }
  50. },
  51. /*
  52. Looks for %PRAGMAS
  53. */
  54. render_pragmas: function(template) {
  55. // no pragmas
  56. if(!this.includes("%", template)) {
  57. return template;
  58. }
  59. var that = this;
  60. var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
  61. this.ctag);
  62. return template.replace(regex, function(match, pragma, options) {
  63. if(!that.pragmas_implemented[pragma]) {
  64. throw({message:
  65. "This implementation of mustache doesn't understand the '" +
  66. pragma + "' pragma"});
  67. }
  68. that.pragmas[pragma] = {};
  69. if(options) {
  70. var opts = options.split("=");
  71. that.pragmas[pragma][opts[0]] = opts[1];
  72. }
  73. return "";
  74. // ignore unknown pragmas silently
  75. });
  76. },
  77. /*
  78. Tries to find a partial in the curent scope and render it
  79. */
  80. render_partial: function(name, context, partials) {
  81. name = this.trim(name);
  82. if(!partials || partials[name] === undefined) {
  83. throw({message: "unknown_partial '" + name + "'"});
  84. }
  85. if(typeof(context[name]) != "object") {
  86. return this.render(partials[name], context, partials, true);
  87. }
  88. return this.render(partials[name], context[name], partials, true);
  89. },
  90. /*
  91. Renders inverted (^) and normal (#) sections
  92. */
  93. render_section: function(template, context, partials) {
  94. if(!this.includes("#", template) && !this.includes("^", template)) {
  95. return template;
  96. }
  97. var that = this;
  98. // CSW - Added "+?" so it finds the tighest bound, not the widest
  99. var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
  100. "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
  101. "\\s*", "mg");
  102. // for each {{#foo}}{{/foo}} section do...
  103. return template.replace(regex, function(match, type, name, content) {
  104. var value = that.find(name, context);
  105. if(type == "^") { // inverted section
  106. if(!value || that.is_array(value) && value.length === 0) {
  107. // false or empty list, render it
  108. return that.render(content, context, partials, true);
  109. } else {
  110. return "";
  111. }
  112. } else if(type == "#") { // normal section
  113. if(that.is_array(value)) { // Enumerable, Let's loop!
  114. return that.map(value, function(row) {
  115. return that.render(content, that.create_context(row),
  116. partials, true);
  117. }).join("");
  118. } else if(that.is_object(value)) { // Object, Use it as subcontext!
  119. return that.render(content, that.create_context(value),
  120. partials, true);
  121. } else if(typeof value === "function") {
  122. // higher order section
  123. return value.call(context, content, function(text) {
  124. return that.render(text, context, partials, true);
  125. });
  126. } else if(value) { // boolean section
  127. return that.render(content, context, partials, true);
  128. } else {
  129. return "";
  130. }
  131. }
  132. });
  133. },
  134. /*
  135. Replace {{foo}} and friends with values from our view
  136. */
  137. render_tags: function(template, context, partials, in_recursion) {
  138. // tit for tat
  139. var that = this;
  140. var new_regex = function() {
  141. return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
  142. that.ctag + "+", "g");
  143. };
  144. var regex = new_regex();
  145. var tag_replace_callback = function(match, operator, name) {
  146. switch(operator) {
  147. case "!": // ignore comments
  148. return "";
  149. case "=": // set new delimiters, rebuild the replace regexp
  150. that.set_delimiters(name);
  151. regex = new_regex();
  152. return "";
  153. case ">": // render partial
  154. return that.render_partial(name, context, partials);
  155. case "{": // the triple mustache is unescaped
  156. return that.find(name, context);
  157. default: // escape the value
  158. return that.escape(that.find(name, context));
  159. }
  160. };
  161. var lines = template.split("\n");
  162. for(var i = 0; i < lines.length; i++) {
  163. lines[i] = lines[i].replace(regex, tag_replace_callback, this);
  164. if(!in_recursion) {
  165. this.send(lines[i]);
  166. }
  167. }
  168. if(in_recursion) {
  169. return lines.join("\n");
  170. }
  171. },
  172. set_delimiters: function(delimiters) {
  173. var dels = delimiters.split(" ");
  174. this.otag = this.escape_regex(dels[0]);
  175. this.ctag = this.escape_regex(dels[1]);
  176. },
  177. escape_regex: function(text) {
  178. // thank you Simon Willison
  179. if(!arguments.callee.sRE) {
  180. var specials = [
  181. '/', '.', '*', '+', '?', '|',
  182. '(', ')', '[', ']', '{', '}', '\\'
  183. ];
  184. arguments.callee.sRE = new RegExp(
  185. '(\\' + specials.join('|\\') + ')', 'g'
  186. );
  187. }
  188. return text.replace(arguments.callee.sRE, '\\$1');
  189. },
  190. /*
  191. find `name` in current `context`. That is find me a value
  192. from the view object
  193. */
  194. find: function(name, context) {
  195. name = this.trim(name);
  196. // Checks whether a value is thruthy or false or 0
  197. function is_kinda_truthy(bool) {
  198. return bool === false || bool === 0 || bool;
  199. }
  200. var value;
  201. if(is_kinda_truthy(context[name])) {
  202. value = context[name];
  203. } else if(is_kinda_truthy(this.context[name])) {
  204. value = this.context[name];
  205. }
  206. if(typeof value === "function") {
  207. return value.apply(context);
  208. }
  209. if(value !== undefined) {
  210. return value;
  211. }
  212. // silently ignore unkown variables
  213. return "";
  214. },
  215. // Utility methods
  216. /* includes tag */
  217. includes: function(needle, haystack) {
  218. return haystack.indexOf(this.otag + needle) != -1;
  219. },
  220. /*
  221. Does away with nasty characters
  222. */
  223. escape: function(s) {
  224. s = String(s === null ? "" : s);
  225. return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
  226. switch(s) {
  227. case "&": return "&amp;";
  228. case "\\": return "\\\\";
  229. case '"': return '\"';
  230. case "<": return "&lt;";
  231. case ">": return "&gt;";
  232. default: return s;
  233. }
  234. });
  235. },
  236. // by @langalex, support for arrays of strings
  237. create_context: function(_context) {
  238. if(this.is_object(_context)) {
  239. return _context;
  240. } else {
  241. var iterator = ".";
  242. if(this.pragmas["IMPLICIT-ITERATOR"]) {
  243. iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
  244. }
  245. var ctx = {};
  246. ctx[iterator] = _context;
  247. return ctx;
  248. }
  249. },
  250. is_object: function(a) {
  251. return a && typeof a == "object";
  252. },
  253. is_array: function(a) {
  254. return Object.prototype.toString.call(a) === '[object Array]';
  255. },
  256. /*
  257. Gets rid of leading and trailing whitespace
  258. */
  259. trim: function(s) {
  260. return s.replace(/^\s*|\s*$/g, "");
  261. },
  262. /*
  263. Why, why, why? Because IE. Cry, cry cry.
  264. */
  265. map: function(array, fn) {
  266. if (typeof array.map == "function") {
  267. return array.map(fn);
  268. } else {
  269. var r = [];
  270. var l = array.length;
  271. for(var i = 0; i < l; i++) {
  272. r.push(fn(array[i]));
  273. }
  274. return r;
  275. }
  276. }
  277. };
  278. return({
  279. name: "mustache.js",
  280. version: "0.3.1-dev",
  281. /*
  282. Turns a template and view into HTML
  283. */
  284. to_html: function(template, view, partials, send_fun) {
  285. var renderer = new Renderer();
  286. if(send_fun) {
  287. renderer.send = send_fun;
  288. }
  289. renderer.render(template, view, partials);
  290. if(!send_fun) {
  291. return renderer.buffer.join("\n");
  292. }
  293. }
  294. });
  295. }();
  296. exports.name = Mustache.name;
  297. exports.version = Mustache.version;
  298. exports.to_html = function() {
  299. return Mustache.to_html.apply(this, arguments);
  300. };