Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

545 lignes
14KB

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