Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

542 linhas
14KB

  1. YUI.add("mustache", function(Y) {
  2. /*!
  3. * mustache.js - Logic-less {{mustache}} templates with JavaScript
  4. * http://github.com/janl/mustache.js
  5. */
  6. var Mustache = (typeof module !== "undefined" && module.exports) || {};
  7. (function (exports) {
  8. exports.name = "mustache.js";
  9. exports.version = "0.5.14";
  10. exports.tags = ["{{", "}}"];
  11. exports.parse = parse;
  12. exports.compile = compile;
  13. exports.render = render;
  14. exports.clearCache = clearCache;
  15. // This is here for backwards compatibility with 0.4.x.
  16. exports.to_html = function (template, view, partials, send) {
  17. var result = render(template, view, partials);
  18. if (typeof send === "function") {
  19. send(result);
  20. } else {
  21. return result;
  22. }
  23. };
  24. var _toString = Object.prototype.toString;
  25. var _isArray = Array.isArray;
  26. var _forEach = Array.prototype.forEach;
  27. var _trim = String.prototype.trim;
  28. var isArray;
  29. if (_isArray) {
  30. isArray = _isArray;
  31. } else {
  32. isArray = function (obj) {
  33. return _toString.call(obj) === "[object Array]";
  34. };
  35. }
  36. var forEach;
  37. if (_forEach) {
  38. forEach = function (obj, callback, scope) {
  39. return _forEach.call(obj, callback, scope);
  40. };
  41. } else {
  42. forEach = function (obj, callback, scope) {
  43. for (var i = 0, len = obj.length; i < len; ++i) {
  44. callback.call(scope, obj[i], i, obj);
  45. }
  46. };
  47. }
  48. var spaceRe = /^\s*$/;
  49. function isWhitespace(string) {
  50. return spaceRe.test(string);
  51. }
  52. var trim;
  53. if (_trim) {
  54. trim = function (string) {
  55. return string == null ? "" : _trim.call(string);
  56. };
  57. } else {
  58. var trimLeft, trimRight;
  59. if (isWhitespace("\xA0")) {
  60. trimLeft = /^\s+/;
  61. trimRight = /\s+$/;
  62. } else {
  63. // IE doesn't match non-breaking spaces with \s, thanks jQuery.
  64. trimLeft = /^[\s\xA0]+/;
  65. trimRight = /[\s\xA0]+$/;
  66. }
  67. trim = function (string) {
  68. return string == null ? "" :
  69. String(string).replace(trimLeft, "").replace(trimRight, "");
  70. };
  71. }
  72. var escapeMap = {
  73. "&": "&amp;",
  74. "<": "&lt;",
  75. ">": "&gt;",
  76. '"': '&quot;',
  77. "'": '&#39;'
  78. };
  79. function escapeHTML(string) {
  80. return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
  81. return escapeMap[s] || s;
  82. });
  83. }
  84. /**
  85. * Adds the `template`, `line`, and `file` properties to the given error
  86. * object and alters the message to provide more useful debugging information.
  87. */
  88. function debug(e, template, line, file) {
  89. file = file || "<template>";
  90. var lines = template.split("\n"),
  91. start = Math.max(line - 3, 0),
  92. end = Math.min(lines.length, line + 3),
  93. context = lines.slice(start, end);
  94. var c;
  95. for (var i = 0, len = context.length; i < len; ++i) {
  96. c = i + start + 1;
  97. context[i] = (c === line ? " >> " : " ") + context[i];
  98. }
  99. e.template = template;
  100. e.line = line;
  101. e.file = file;
  102. e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
  103. return e;
  104. }
  105. /**
  106. * Looks up the value of the given `name` in the given context `stack`.
  107. */
  108. function lookup(name, stack, defaultValue) {
  109. if (name === ".") {
  110. return stack[stack.length - 1];
  111. }
  112. var names = name.split(".");
  113. var lastIndex = names.length - 1;
  114. var target = names[lastIndex];
  115. var value, context, i = stack.length, j, localStack;
  116. while (i) {
  117. localStack = stack.slice(0);
  118. context = stack[--i];
  119. j = 0;
  120. while (j < lastIndex) {
  121. context = context[names[j++]];
  122. if (context == null) {
  123. break;
  124. }
  125. localStack.push(context);
  126. }
  127. if (context && typeof context === "object" && target in context) {
  128. value = context[target];
  129. break;
  130. }
  131. }
  132. // If the value is a function, call it in the current context.
  133. if (typeof value === "function") {
  134. value = value.call(localStack[localStack.length - 1]);
  135. }
  136. if (value == null) {
  137. return defaultValue;
  138. }
  139. return value;
  140. }
  141. function renderSection(name, stack, callback, inverted) {
  142. var buffer = "";
  143. var value = lookup(name, stack);
  144. if (inverted) {
  145. // From the spec: inverted sections may render text once based on the
  146. // inverse value of the key. That is, they will be rendered if the key
  147. // doesn't exist, is false, or is an empty list.
  148. if (value == null || value === false || (isArray(value) && value.length === 0)) {
  149. buffer += callback();
  150. }
  151. } else if (isArray(value)) {
  152. forEach(value, function (value) {
  153. stack.push(value);
  154. buffer += callback();
  155. stack.pop();
  156. });
  157. } else if (typeof value === "object") {
  158. stack.push(value);
  159. buffer += callback();
  160. stack.pop();
  161. } else if (typeof value === "function") {
  162. var scope = stack[stack.length - 1];
  163. var scopedRender = function (template) {
  164. return render(template, scope);
  165. };
  166. buffer += value.call(scope, callback(), scopedRender) || "";
  167. } else if (value) {
  168. buffer += callback();
  169. }
  170. return buffer;
  171. }
  172. /**
  173. * Parses the given `template` and returns the source of a function that,
  174. * with the proper arguments, will render the template. Recognized options
  175. * include the following:
  176. *
  177. * - file The name of the file the template comes from (displayed in
  178. * error messages)
  179. * - tags An array of open and close tags the `template` uses. Defaults
  180. * to the value of Mustache.tags
  181. * - debug Set `true` to log the body of the generated function to the
  182. * console
  183. * - space Set `true` to preserve whitespace from lines that otherwise
  184. * contain only a {{tag}}. Defaults to `false`
  185. */
  186. function parse(template, options) {
  187. options = options || {};
  188. var tags = options.tags || exports.tags,
  189. openTag = tags[0],
  190. closeTag = tags[tags.length - 1];
  191. var code = [
  192. 'var buffer = "";', // output buffer
  193. "\nvar line = 1;", // keep track of source line number
  194. "\ntry {",
  195. '\nbuffer += "'
  196. ];
  197. var spaces = [], // indices of whitespace in code on the current line
  198. hasTag = false, // is there a {{tag}} on the current line?
  199. nonSpace = false; // is there a non-space char on the current line?
  200. // Strips all space characters from the code array for the current line
  201. // if there was a {{tag}} on it and otherwise only spaces.
  202. var stripSpace = function () {
  203. if (hasTag && !nonSpace && !options.space) {
  204. while (spaces.length) {
  205. code.splice(spaces.pop(), 1);
  206. }
  207. } else {
  208. spaces = [];
  209. }
  210. hasTag = false;
  211. nonSpace = false;
  212. };
  213. var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
  214. var setTags = function (source) {
  215. tags = trim(source).split(/\s+/);
  216. nextOpenTag = tags[0];
  217. nextCloseTag = tags[tags.length - 1];
  218. };
  219. var includePartial = function (source) {
  220. code.push(
  221. '";',
  222. updateLine,
  223. '\nvar partial = partials["' + trim(source) + '"];',
  224. '\nif (partial) {',
  225. '\n buffer += render(partial,stack[stack.length - 1],partials);',
  226. '\n}',
  227. '\nbuffer += "'
  228. );
  229. };
  230. var openSection = function (source, inverted) {
  231. var name = trim(source);
  232. if (name === "") {
  233. throw debug(new Error("Section name may not be empty"), template, line, options.file);
  234. }
  235. sectionStack.push({name: name, inverted: inverted});
  236. code.push(
  237. '";',
  238. updateLine,
  239. '\nvar name = "' + name + '";',
  240. '\nvar callback = (function () {',
  241. '\n return function () {',
  242. '\n var buffer = "";',
  243. '\nbuffer += "'
  244. );
  245. };
  246. var openInvertedSection = function (source) {
  247. openSection(source, true);
  248. };
  249. var closeSection = function (source) {
  250. var name = trim(source);
  251. var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
  252. if (!openName || name != openName) {
  253. throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
  254. }
  255. var section = sectionStack.pop();
  256. code.push(
  257. '";',
  258. '\n return buffer;',
  259. '\n };',
  260. '\n})();'
  261. );
  262. if (section.inverted) {
  263. code.push("\nbuffer += renderSection(name,stack,callback,true);");
  264. } else {
  265. code.push("\nbuffer += renderSection(name,stack,callback);");
  266. }
  267. code.push('\nbuffer += "');
  268. };
  269. var sendPlain = function (source) {
  270. code.push(
  271. '";',
  272. updateLine,
  273. '\nbuffer += lookup("' + trim(source) + '",stack,"");',
  274. '\nbuffer += "'
  275. );
  276. };
  277. var sendEscaped = function (source) {
  278. code.push(
  279. '";',
  280. updateLine,
  281. '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
  282. '\nbuffer += "'
  283. );
  284. };
  285. var line = 1, c, callback;
  286. for (var i = 0, len = template.length; i < len; ++i) {
  287. if (template.slice(i, i + openTag.length) === openTag) {
  288. i += openTag.length;
  289. c = template.substr(i, 1);
  290. updateLine = '\nline = ' + line + ';';
  291. nextOpenTag = openTag;
  292. nextCloseTag = closeTag;
  293. hasTag = true;
  294. switch (c) {
  295. case "!": // comment
  296. i++;
  297. callback = null;
  298. break;
  299. case "=": // change open/close tags, e.g. {{=<% %>=}}
  300. i++;
  301. closeTag = "=" + closeTag;
  302. callback = setTags;
  303. break;
  304. case ">": // include partial
  305. i++;
  306. callback = includePartial;
  307. break;
  308. case "#": // start section
  309. i++;
  310. callback = openSection;
  311. break;
  312. case "^": // start inverted section
  313. i++;
  314. callback = openInvertedSection;
  315. break;
  316. case "/": // end section
  317. i++;
  318. callback = closeSection;
  319. break;
  320. case "{": // plain variable
  321. closeTag = "}" + closeTag;
  322. // fall through
  323. case "&": // plain variable
  324. i++;
  325. nonSpace = true;
  326. callback = sendPlain;
  327. break;
  328. default: // escaped variable
  329. nonSpace = true;
  330. callback = sendEscaped;
  331. }
  332. var end = template.indexOf(closeTag, i);
  333. if (end === -1) {
  334. throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
  335. }
  336. var source = template.substring(i, end);
  337. if (callback) {
  338. callback(source);
  339. }
  340. // Maintain line count for \n in source.
  341. var n = 0;
  342. while (~(n = source.indexOf("\n", n))) {
  343. line++;
  344. n++;
  345. }
  346. i = end + closeTag.length - 1;
  347. openTag = nextOpenTag;
  348. closeTag = nextCloseTag;
  349. } else {
  350. c = template.substr(i, 1);
  351. switch (c) {
  352. case '"':
  353. case "\\":
  354. nonSpace = true;
  355. code.push("\\" + c);
  356. break;
  357. case "\r":
  358. // Ignore carriage returns.
  359. break;
  360. case "\n":
  361. spaces.push(code.length);
  362. code.push("\\n");
  363. stripSpace(); // Check for whitespace on the current line.
  364. line++;
  365. break;
  366. default:
  367. if (isWhitespace(c)) {
  368. spaces.push(code.length);
  369. } else {
  370. nonSpace = true;
  371. }
  372. code.push(c);
  373. }
  374. }
  375. }
  376. if (sectionStack.length != 0) {
  377. throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
  378. }
  379. // Clean up any whitespace from a closing {{tag}} that was at the end
  380. // of the template without a trailing \n.
  381. stripSpace();
  382. code.push(
  383. '";',
  384. "\nreturn buffer;",
  385. "\n} catch (e) { throw {error: e, line: line}; }"
  386. );
  387. // Ignore `buffer += "";` statements.
  388. var body = code.join("").replace(/buffer \+= "";\n/g, "");
  389. if (options.debug) {
  390. if (typeof console != "undefined" && console.log) {
  391. console.log(body);
  392. } else if (typeof print === "function") {
  393. print(body);
  394. }
  395. }
  396. return body;
  397. }
  398. /**
  399. * Used by `compile` to generate a reusable function for the given `template`.
  400. */
  401. function _compile(template, options) {
  402. var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
  403. var body = parse(template, options);
  404. var fn = new Function(args, body);
  405. // This anonymous function wraps the generated function so we can do
  406. // argument coercion, setup some variables, and handle any errors
  407. // encountered while executing it.
  408. return function (view, partials) {
  409. partials = partials || {};
  410. var stack = [view]; // context stack
  411. try {
  412. return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
  413. } catch (e) {
  414. throw debug(e.error, template, e.line, options.file);
  415. }
  416. };
  417. }
  418. // Cache of pre-compiled templates.
  419. var _cache = {};
  420. /**
  421. * Clear the cache of compiled templates.
  422. */
  423. function clearCache() {
  424. _cache = {};
  425. }
  426. /**
  427. * Compiles the given `template` into a reusable function using the given
  428. * `options`. In addition to the options accepted by Mustache.parse,
  429. * recognized options include the following:
  430. *
  431. * - cache Set `false` to bypass any pre-compiled version of the given
  432. * template. Otherwise, a given `template` string will be cached
  433. * the first time it is parsed
  434. */
  435. function compile(template, options) {
  436. options = options || {};
  437. // Use a pre-compiled version from the cache if we have one.
  438. if (options.cache !== false) {
  439. if (!_cache[template]) {
  440. _cache[template] = _compile(template, options);
  441. }
  442. return _cache[template];
  443. }
  444. return _compile(template, options);
  445. }
  446. /**
  447. * High-level function that renders the given `template` using the given
  448. * `view` and `partials`. If you need to use any of the template options (see
  449. * `compile` above), you must compile in a separate step, and then call that
  450. * compiled function.
  451. */
  452. function render(template, view, partials) {
  453. return compile(template)(view, partials);
  454. }
  455. })(Mustache);
  456. Y.mustache = Mustache.render;
  457. }, "0");