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.

805 lines
24KB

  1. /*
  2. mustache.js — Logic-less templates in JavaScript
  3. See http://mustache.github.com/ for more info.
  4. */
  5. var Mustache = function() {
  6. function ParserException(message) {
  7. this.message = message;
  8. }
  9. ParserException.prototype = {};
  10. var Renderer = function(send_func) {
  11. this._escapeCompiledRegex = null;
  12. if (!Renderer.TokenizerRegex) {
  13. Renderer.TokenizerRegex = this._createTokenizerRegex('{{', '}}');
  14. }
  15. this.user_send_func = send_func;
  16. this.commandSet = this.compiler;
  17. this.cached_output = [];
  18. this.send_func = function(text) {
  19. this.cached_output.push(text);
  20. }
  21. this.pragmas = {};
  22. // Fix up the stupidness that is IE's split implementation
  23. this._compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group
  24. var hasCapturingSplit = '{{hi}}'.split(/(hi)/).length === 3;
  25. if (!hasCapturingSplit) {
  26. this.splitFunc = this.capturingSplit;
  27. } else {
  28. this.splitFunc = String.prototype.split;
  29. }
  30. };
  31. Renderer.TokenizerRegex = null;
  32. Renderer.prototype = {
  33. capturingSplit: function(separator) {
  34. // fix up the stupidness that is IE's broken String.split implementation
  35. // originally by Steven Levithan
  36. /* Cross-Browser Split 1.0.1
  37. (c) Steven Levithan <stevenlevithan.com>; MIT License
  38. An ECMA-compliant, uniform cross-browser split method */
  39. var str = this;
  40. var limit = undefined;
  41. // if `separator` is not a regex, use the native `split`
  42. if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
  43. return String.prototype.split.call(str, separator, limit);
  44. }
  45. var output = [],
  46. lastLastIndex = 0,
  47. flags = (separator.ignoreCase ? "i" : "") +
  48. (separator.multiline ? "m" : "") +
  49. (separator.sticky ? "y" : ""),
  50. separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy
  51. separator2, match, lastIndex, lastLength;
  52. str = str + ""; // type conversion
  53. if (!this._compliantExecNpcg) {
  54. separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt
  55. }
  56. /* behavior for `limit`: if it's...
  57. - `undefined`: no limit.
  58. - `NaN` or zero: return an empty array.
  59. - a positive number: use `Math.floor(limit)`.
  60. - a negative number: no limit.
  61. - other: type-convert, then use the above rules. */
  62. if (limit === undefined || +limit < 0) {
  63. limit = Infinity;
  64. } else {
  65. limit = Math.floor(+limit);
  66. if (!limit) {
  67. return [];
  68. }
  69. }
  70. while (match = separator.exec(str)) {
  71. lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser
  72. if (lastIndex > lastLastIndex) {
  73. output.push(str.slice(lastLastIndex, match.index));
  74. // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups
  75. if (!this._compliantExecNpcg && match.length > 1) {
  76. match[0].replace(separator2, function () {
  77. for (var i = 1; i < arguments.length - 2; i++) {
  78. if (arguments[i] === undefined) {
  79. match[i] = undefined;
  80. }
  81. }
  82. });
  83. }
  84. if (match.length > 1 && match.index < str.length) {
  85. Array.prototype.push.apply(output, match.slice(1));
  86. }
  87. lastLength = match[0].length;
  88. lastLastIndex = lastIndex;
  89. if (output.length >= limit) {
  90. break;
  91. }
  92. }
  93. if (separator.lastIndex === match.index) {
  94. separator.lastIndex++; // avoid an infinite loop
  95. }
  96. }
  97. if (lastLastIndex === str.length) {
  98. if (lastLength || !separator.test("")) {
  99. output.push("");
  100. }
  101. } else {
  102. output.push(str.slice(lastLastIndex));
  103. }
  104. return output.length > limit ? output.slice(0, limit) : output;
  105. },
  106. render: function(template, context, partials) {
  107. template = this.parse_pragmas(template, '{{', '}}');
  108. var tokens = this.tokenize(template, '{{', '}}');
  109. this.parse(this.createParserContext(tokens, partials, '{{', '}}'), [context]);
  110. },
  111. createParserContext: function(tokens, partials, openTag, closeTag) {
  112. return {
  113. tokens: tokens,
  114. token: tokens[0],
  115. index: 0,
  116. length: tokens.length,
  117. partials: partials,
  118. stack: [],
  119. openTag: openTag,
  120. closeTag: closeTag
  121. };
  122. },
  123. _createTokenizerRegex: function(openTag, closeTag) {
  124. var delimiters = [
  125. '\\{',
  126. '&',
  127. '\\}',
  128. '#',
  129. '\\^',
  130. '\\/',
  131. '>',
  132. '=',
  133. '%',
  134. '!',
  135. '\\s+'
  136. ];
  137. delimiters.unshift(this.escape_regex(openTag));
  138. delimiters.unshift(this.escape_regex(closeTag));
  139. return new RegExp('(' + delimiters.join('|') + ')');
  140. },
  141. tokenize: function(template, openTag, closeTag) {
  142. var regex;
  143. if (openTag==='{{' && closeTag==='}}') {
  144. // the common case, use the stored compiled regex
  145. regex = Renderer.TokenizerRegex;
  146. } else {
  147. regex = this._createTokenizerRegex(openTag, closeTag);
  148. }
  149. return this.splitFunc.call(template, regex);
  150. },
  151. /*
  152. Looks for %PRAGMAS
  153. */
  154. parse_pragmas: function(template, openTag, closeTag) {
  155. /* includes tag */
  156. function includes(needle, haystack) {
  157. return haystack.indexOf(openTag + needle) !== -1;
  158. }
  159. // no pragmas, easy escape
  160. if(!includes("%", template)) {
  161. return template;
  162. }
  163. var that = this;
  164. var regex = new RegExp(this.escape_regex(openTag) + "%([\\w-]+)(\\s*)(.*?(?=" + this.escape_regex(closeTag) + "))" + this.escape_regex(closeTag));
  165. return template.replace(regex, function(match, pragma, space, suffix) {
  166. var options = undefined;
  167. if (suffix.length>0) {
  168. var optionPairs = suffix.split(',');
  169. var scratch;
  170. options = {};
  171. for (var i=0, n=optionPairs.length; i<n; ++i) {
  172. scratch = optionPairs[i].split('=');
  173. if (scratch.length !== 2) {
  174. throw new ParserException('Malformed pragma options');
  175. }
  176. options[scratch[0]] = scratch[1];
  177. }
  178. }
  179. if (that.is_function(that.pragmaDirectives[pragma])) {
  180. that.pragmaDirectives[pragma].call(that, options);
  181. } else {
  182. throw new ParserException("This implementation of mustache doesn't understand the '" + pragma + "' pragma");
  183. }
  184. return ""; // blank out all pragmas
  185. });
  186. },
  187. parse: function(parserContext, contextStack) {
  188. var state = 'text';
  189. for (; parserContext.index<parserContext.length; ++parserContext.index) {
  190. parserContext.token = parserContext.tokens[parserContext.index];
  191. if (parserContext.token === '') continue;
  192. state = this.stateMachine[state].call(this, parserContext, contextStack);
  193. }
  194. // make sure the parser finished at an appropriate terminal state
  195. if (state!=='text') {
  196. this.stateMachine['endOfDoc'].call(this, parserContext, contextStack);
  197. } else {
  198. this.commandSet.text.call(this);
  199. }
  200. },
  201. escape_regex: function(text) {
  202. // thank you Simon Willison
  203. if (!this._escapeCompiledRegex) {
  204. var specials = [
  205. '/', '.', '*', '+', '?', '|',
  206. '(', ')', '[', ']', '{', '}', '\\'
  207. ];
  208. this._escapeCompiledRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
  209. }
  210. return text.replace(this._escapeCompiledRegex, '\\$1');
  211. },
  212. isWhitespace: function(token) {
  213. return token.match(/^\s+$/)!==null;
  214. },
  215. stateMachine: {
  216. text: function(parserContext, contextStack) {
  217. switch (parserContext.token) {
  218. case parserContext.openTag:
  219. this.commandSet.text.call(this);
  220. return 'openMustache';
  221. default:
  222. this.send_func(parserContext.token);
  223. return 'text';
  224. }
  225. },
  226. openMustache: function(parserContext, contextStack) {
  227. switch (parserContext.token) {
  228. case '{':
  229. parserContext.stack.push({tagType:'unescapedVariable', subtype: 'tripleMustache'});
  230. return 'keyName';
  231. case '&':
  232. parserContext.stack.push({tagType:'unescapedVariable'});
  233. return 'keyName';
  234. case '#':
  235. parserContext.stack.push({tagType:'section'});
  236. return 'keyName';
  237. case '^':
  238. parserContext.stack.push({tagType:'invertedSection'});
  239. return 'keyName';
  240. case '>':
  241. parserContext.stack.push({tagType:'partial'});
  242. return 'simpleKeyName';
  243. case '=':
  244. parserContext.stack.push({tagType: 'setDelimiter'});
  245. return 'setDelimiterStart';
  246. case '!':
  247. return 'discard';
  248. case '%':
  249. throw new ParserException('Pragmas are only supported as a preprocessing directive.');
  250. case '/': // close mustache
  251. throw new ParserException('Unexpected closing tag.');
  252. case '}': // close triple mustache
  253. throw new ParserException('Unexpected token encountered.');
  254. default:
  255. parserContext.stack.push({tagType:'variable'});
  256. return this.stateMachine.keyName.call(this, parserContext, contextStack);
  257. }
  258. },
  259. closeMustache: function(parserContext, contextStack) {
  260. if (this.isWhitespace(parserContext.token)) {
  261. return 'closeMustache';
  262. } else if (parserContext.token===parserContext.closeTag) {
  263. return this.dispatchCommand(parserContext, contextStack);
  264. }
  265. },
  266. expectClosingMustache: function(parserContext, contextStack) {
  267. if (parserContext.closeTag==='}}' &&
  268. parserContext.token==='}}') {
  269. return 'expectClosingParenthesis';
  270. } else if (parserContext.token==='}') {
  271. return 'closeMustache';
  272. } else {
  273. throw new ParserException('Unexpected token encountered.');
  274. }
  275. },
  276. expectClosingParenthesis: function(parserContext, contextStack) {
  277. if (parserContext.token==='}') {
  278. return this.dispatchCommand(parserContext, contextStack);
  279. } else {
  280. throw new ParserException('Unexpected token encountered.');
  281. }
  282. },
  283. keyName: function(parserContext, contextStack) {
  284. var result = this.stateMachine.simpleKeyName.call(this, parserContext, contextStack);
  285. if (result==='closeMustache') {
  286. var tagKey = parserContext.stack[parserContext.stack.length-1],
  287. tag = parserContext.stack[parserContext.stack.length-2];
  288. if (tag.tagType==='unescapedVariable' && tag.subtype==='tripleMustache') {
  289. parserContext.stack[parserContext.stack.length-2] = {tagType:'unescapedVariable'};
  290. return 'expectClosingMustache';
  291. } else {
  292. return 'closeMustache';
  293. }
  294. } else if (result==='simpleKeyName') {
  295. return 'keyName';
  296. } else {
  297. throw new ParserException('Unexpected branch in tag name: ' + result);
  298. }
  299. },
  300. simpleKeyName: function(parserContext, contextStack) {
  301. if (this.isWhitespace(parserContext.token)) {
  302. return 'simpleKeyName';
  303. } else {
  304. parserContext.stack.push(parserContext.token);
  305. return 'closeMustache';
  306. }
  307. },
  308. setDelimiterStart: function(parserContext, contextStack) {
  309. if (this.isWhitespace(parserContext.token) ||
  310. parserContext.token==='=') {
  311. throw new ParserException('Syntax error in Set Delimiter tag');
  312. } else {
  313. parserContext.stack.push(parserContext.token);
  314. return 'setDelimiterStartOrWhitespace';
  315. }
  316. },
  317. setDelimiterStartOrWhitespace: function(parserContext, contextStack) {
  318. if (this.isWhitespace(parserContext.token)) {
  319. return 'setDelimiterEnd';
  320. } else if (parserContext.token==='='){
  321. throw new ParserException('Syntax error in Set Delimiter tag');
  322. } else {
  323. parserContext.stack[parserContext.stack.length-1] += parserContext.token;
  324. return 'setDelimiterStartOrWhitespace';
  325. }
  326. },
  327. setDelimiterEnd: function(parserContext, contextStack) {
  328. if (this.isWhitespace(parserContext.token)) {
  329. return 'setDelimiterEnd';
  330. } else if (parserContext.token==='=') {
  331. throw new ParserException('Syntax error in Set Delimiter tag');
  332. } else {
  333. parserContext.stack.push(parserContext.token);
  334. return 'setDelimiterEndOrEqualSign';
  335. }
  336. },
  337. setDelimiterEndOrEqualSign: function(parserContext, contextStack) {
  338. if (parserContext.token==='=') {
  339. return 'setDelimiterExpectClosingTag';
  340. } else if (this.isWhitespace(parserContext.token)) {
  341. throw new ParserException('Syntax error in Set Delimiter tag');
  342. } else {
  343. parserContext.stack[parserContext.stack.length-1] += parserContext.token;
  344. return 'setDelimiterEndOrEqualSign';
  345. }
  346. },
  347. setDelimiterExpectClosingTag: function(parserContext, contextStack) {
  348. if (parserContext.token===parserContext.closeTag) {
  349. var newCloseTag = parserContext.stack.pop();
  350. var newOpenTag = parserContext.stack.pop();
  351. var command = parserContext.stack.pop();
  352. if (command.tagType!=='setDelimiter') {
  353. throw new ParserException('Syntax error in Set Delimiter tag');
  354. } else {
  355. var tokens = this.tokenize(
  356. parserContext.tokens.slice(parserContext.index+1).join(''),
  357. newOpenTag,
  358. newCloseTag);
  359. var newParserContext = this.createParserContext(tokens,
  360. parserContext.partials,
  361. newOpenTag,
  362. newCloseTag);
  363. parserContext.tokens = newParserContext.tokens;
  364. parserContext.index = -1;
  365. parserContext.length = newParserContext.length;
  366. parserContext.openTag = newParserContext.openTag;
  367. parserContext.closeTag = newParserContext.closeTag;
  368. return 'text';
  369. }
  370. } else {
  371. throw new ParserException('Syntax error in Set Delimiter tag');
  372. }
  373. },
  374. endSectionScan: function(parserContext, contextStack) {
  375. switch (parserContext.token) {
  376. case parserContext.openTag:
  377. return 'expectSectionOrEndSection';
  378. default:
  379. parserContext.stack[parserContext.stack.length-1].content.push(parserContext.token);
  380. return 'endSectionScan';
  381. }
  382. },
  383. expectSectionOrEndSection: function(parserContext, contextStack) {
  384. switch (parserContext.token) {
  385. case '#':
  386. case '^':
  387. parserContext.stack[parserContext.stack.length-1].depth++;
  388. parserContext.stack[parserContext.stack.length-1].content.push(parserContext.openTag, parserContext.token);
  389. return 'endSectionScan';
  390. case '/':
  391. parserContext.stack.push({tagType:'endSection'});
  392. return 'simpleKeyName';
  393. default:
  394. parserContext.stack[parserContext.stack.length-1].content.push(parserContext.openTag, parserContext.token);
  395. return 'endSectionScan';
  396. }
  397. },
  398. discard: function(parserContext, contextStack) {
  399. if (parserContext.token==='!') {
  400. return 'closeComment';
  401. } else {
  402. return 'discard';
  403. }
  404. },
  405. closeComment: function(parserContext, contextStack) {
  406. if (parserContext.token!==parserContext.closeTag) {
  407. return 'discard';
  408. } else {
  409. return 'text';
  410. }
  411. },
  412. endOfDoc: function(parserContext, contextStack) {
  413. // eventually we may want to give better error messages
  414. throw new ParserException('Unexpected end of document.');
  415. }
  416. },
  417. dispatchCommand: function(parserContext, contextStack) {
  418. var key = parserContext.stack.pop();
  419. var command = parserContext.stack.pop();
  420. switch (command.tagType) {
  421. case 'section':
  422. case 'invertedSection':
  423. parserContext.stack.push({sectionType:command.tagType, key:key, content:[], depth:1});
  424. return 'endSectionScan';
  425. case 'variable':
  426. this.commandSet.variable.call(this, key, contextStack);
  427. return 'text';
  428. case 'unescapedVariable':
  429. this.commandSet.unescaped_variable.call(this, key, contextStack);
  430. return 'text';
  431. case 'partial':
  432. this.commandSet.partial.call(this, key,
  433. contextStack,
  434. parserContext.partials,
  435. parserContext.openTag,
  436. parserContext.closeTag);
  437. return 'text';
  438. case 'endSection':
  439. var section = parserContext.stack[parserContext.stack.length-1];
  440. if (--section.depth === 0) {
  441. if (section.key === key) {
  442. parserContext.stack.pop();
  443. this.commandSet.section.call(this, section.sectionType,
  444. section.content,
  445. key,
  446. contextStack,
  447. parserContext.partials,
  448. parserContext.openTag,
  449. parserContext.closeTag);
  450. return 'text';
  451. } else {
  452. throw new ParserException('Unbalanced open/close section tags');
  453. }
  454. } else {
  455. section.content.push('{{', '/', key, '}}');
  456. return 'endSectionScan';
  457. }
  458. default:
  459. throw new ParserException('Unknown dispatch command: ' + command.tagType);
  460. }
  461. },
  462. pragmaDirectives: {
  463. 'IMPLICIT-ITERATOR': function(options) {
  464. this.pragmas['IMPLICIT-ITERATOR'] = {};
  465. if (options) {
  466. this.pragmas['IMPLICIT-ITERATOR'].iterator = options['iterator'];
  467. }
  468. }
  469. },
  470. /*
  471. find `name` in current `context`. That is find me a value
  472. from the view object
  473. */
  474. find: function(name, context) {
  475. // Checks whether a value is truthy or false or 0
  476. function is_kinda_truthy(bool) {
  477. return bool === false || bool === 0 || bool;
  478. }
  479. var value;
  480. if (is_kinda_truthy(context[name])) {
  481. value = context[name];
  482. }
  483. if (this.is_function(value)) {
  484. return value.apply(context);
  485. }
  486. return value;
  487. },
  488. find_in_stack: function(name, contextStack) {
  489. var value;
  490. value = this.find(name, contextStack[contextStack.length-1]);
  491. if (value!==undefined) { return value; }
  492. if (contextStack.length>1) {
  493. value = this.find(name, contextStack[0]);
  494. if (value!==undefined) { return value; }
  495. }
  496. return undefined;
  497. },
  498. is_function: function(a) {
  499. return a && typeof a === 'function';
  500. },
  501. is_object: function(a) {
  502. return a && typeof a === 'object';
  503. },
  504. is_array: function(a) {
  505. return Object.prototype.toString.call(a) === '[object Array]';
  506. },
  507. compiler: {
  508. text: function() {
  509. var outputText = this.cached_output.join('');
  510. this.cached_output = [];
  511. this.user_send_func(function(contextStack, send_func) {
  512. send_func(outputText);
  513. });
  514. },
  515. variable: function(key/*, contextStack*/) {
  516. function escapeHTML(str) {
  517. return ('' + str).replace(/&/g,'&amp;')
  518. .replace(/</g,'&lt;')
  519. .replace(/>/g,'&gt;');
  520. }
  521. var that = this;
  522. this.user_send_func(function(contextStack, send_func) {
  523. var result = that.find_in_stack(key, contextStack);
  524. if (result!==undefined) {
  525. send_func(escapeHTML(result));
  526. }
  527. });
  528. },
  529. unescaped_variable: function(key/*, contextStack*/) {
  530. var that = this;
  531. this.user_send_func(function(contextStack, send_func) {
  532. var result = that.find_in_stack(key, contextStack);
  533. if (result!==undefined) {
  534. send_func(result);
  535. }
  536. });
  537. },
  538. partial: function(key, reserved/*contextStack*/, partials, openTag, closeTag) {
  539. if (!partials || partials[key] === undefined) {
  540. throw new ParserException('Unknown partial \'' + key + '\'');
  541. }
  542. if (!this.is_function(partials[key])) {
  543. var old_user_send_func = this.user_send_func;
  544. var commands = [];
  545. this.user_send_func = function(command) { commands.push(command); };
  546. var tokens = this.tokenize(partials[key], openTag, closeTag);
  547. partials[key] = function() {}; // blank out the paritals so that infinite recursion doesn't happen
  548. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved);
  549. this.user_send_func = old_user_send_func;
  550. var that = this;
  551. partials[key] = function(contextStack, send_func) {
  552. var res = that.find_in_stack(key, contextStack),
  553. isObj = that.is_object(res);
  554. if (isObj) {
  555. contextStack.push(res);
  556. }
  557. for (var i=0,n=commands.length; i<n; ++i) {
  558. commands[i](contextStack, send_func);
  559. }
  560. if (isObj) {
  561. contextStack.pop();
  562. }
  563. };
  564. }
  565. this.user_send_func(function(contextStack, send_func) { partials[key](contextStack, send_func); });
  566. },
  567. section: function(sectionType, fragmentTokens, key, reserved/*contextStack*/, partials, openTag, closeTag) {
  568. // by @langalex, support for arrays of strings
  569. var that = this;
  570. function create_context(_context) {
  571. if(that.is_object(_context)) {
  572. return _context;
  573. } else {
  574. var iterator = '.';
  575. if(that.pragmas["IMPLICIT-ITERATOR"] &&
  576. that.pragmas["IMPLICIT-ITERATOR"].iterator) {
  577. iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
  578. }
  579. var ctx = {};
  580. ctx[iterator] = _context;
  581. return ctx;
  582. }
  583. }
  584. var old_user_send_func = this.user_send_func;
  585. var commands = [];
  586. this.user_send_func = function(command) { commands.push(command); };
  587. this.parse(this.createParserContext(fragmentTokens, partials, openTag, closeTag), reserved);
  588. this.user_send_func = old_user_send_func;
  589. var section_command = function(contextStack, send_func) {
  590. for (var i=0, n=commands.length; i<n; ++i) {
  591. commands[i](contextStack, send_func);
  592. }
  593. };
  594. if (sectionType==='invertedSection') {
  595. this.user_send_func(function(contextStack, send_func) {
  596. var value = that.find_in_stack(key, contextStack);
  597. if (!value || that.is_array(value) && value.length === 0) {
  598. // false or empty list, render it
  599. section_command(contextStack, send_func);
  600. }
  601. });
  602. } else if (sectionType==='section') {
  603. this.user_send_func(function(contextStack, send_func) {
  604. var value = that.find_in_stack(key, contextStack);
  605. if (that.is_array(value)) { // Enumerable, Let's loop!
  606. for (var i=0, n=value.length; i<n; ++i) {
  607. contextStack.push(create_context(value[i]));
  608. section_command(contextStack, send_func);
  609. contextStack.pop();
  610. }
  611. } else if (that.is_object(value)) { // Object, Use it as subcontext!
  612. contextStack.push(value);
  613. section_command(contextStack, send_func);
  614. contextStack.pop();
  615. } else if (that.is_function(value)) {
  616. // higher order section
  617. // note that HOS triggers a compilation on the resultFragment.
  618. // this is slow (in relation to a fully compiled template)
  619. // since it invokes a call to the parser
  620. var result = value.call(contextStack[contextStack.length-1], fragmentTokens.join(''), function(resultFragment) {
  621. var cO = [];
  622. var s = function(command) { cO.push(command); };
  623. var hos_renderer = new Renderer(s, 'compiler');
  624. resultFragment = hos_renderer.parse_pragmas(resultFragment, openTag, closeTag);
  625. var tokens = hos_renderer.tokenize(resultFragment, openTag, closeTag);
  626. hos_renderer.parse(hos_renderer.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  627. var o = [];
  628. var sT = function(output) { o.push(output); };
  629. for (var i=0,n=cO.length; i<n; ++i) {
  630. commands[i](contextStack, sT);
  631. }
  632. return o.join('');
  633. });
  634. send_func(result);
  635. } else if (value) {
  636. section_command(contextStack, send_func);
  637. }
  638. });
  639. } else {
  640. throw new ParserException('Unknown section type ' + sectionType);
  641. }
  642. }
  643. }
  644. }
  645. return({
  646. name: "mustache.js",
  647. version: "0.4.0-vcs",
  648. /*
  649. Turns a template and view into HTML
  650. */
  651. to_html: function(template, view, partials, send_func) {
  652. return (Mustache.compile(template, partials))(view, send_func);
  653. },
  654. /*
  655. Compiles a template into an equivalent JS function for faster
  656. repeated execution.
  657. */
  658. compile: function(template, partials) {
  659. if (!template) { return function() { return '' }; }
  660. var p = {};
  661. if (partials) {
  662. for (var key in partials) {
  663. if (partials.hasOwnProperty(key)) {
  664. p[key] = partials[key];
  665. }
  666. }
  667. }
  668. var commands = [];
  669. var s = function(command) { commands.push(command); };
  670. (new Renderer(s)).render(template, {}, p);
  671. return function(view, send_func) {
  672. view = [view || {}];
  673. var o = send_func ? undefined : [];
  674. var s = send_func || function(output) { o.push(output); };
  675. for (var i=0,n=commands.length; i<n; ++i) {
  676. commands[i](view, s);
  677. }
  678. if (!send_func) {
  679. return o.join('');
  680. }
  681. };
  682. }
  683. });
  684. }();