hogan-3.0.1.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. /*!
  2. * Copyright 2011 Twitter, Inc.
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. var Hogan = {};
  16. (function (Hogan) {
  17. Hogan.Template = function (codeObj, text, compiler, options) {
  18. codeObj = codeObj || {};
  19. this.r = codeObj.code || this.r;
  20. this.c = compiler;
  21. this.options = options || {};
  22. this.text = text || '';
  23. this.partials = codeObj.partials || {};
  24. this.subs = codeObj.subs || {};
  25. this.buf = '';
  26. }
  27. Hogan.Template.prototype = {
  28. // render: replaced by generated code.
  29. r: function (context, partials, indent) { return ''; },
  30. // variable escaping
  31. v: hoganEscape,
  32. // triple stache
  33. t: coerceToString,
  34. render: function render(context, partials, indent) {
  35. return this.ri([context], partials || {}, indent);
  36. },
  37. // render internal -- a hook for overrides that catches partials too
  38. ri: function (context, partials, indent) {
  39. return this.r(context, partials, indent);
  40. },
  41. // ensurePartial
  42. ep: function(symbol, partials) {
  43. var partial = this.partials[symbol];
  44. // check to see that if we've instantiated this partial before
  45. var template = partials[partial.name];
  46. if (partial.instance && partial.base == template) {
  47. return partial.instance;
  48. }
  49. if (typeof template == 'string') {
  50. if (!this.c) {
  51. throw new Error("No compiler available.");
  52. }
  53. template = this.c.compile(template, this.options);
  54. }
  55. if (!template) {
  56. return null;
  57. }
  58. // We use this to check whether the partials dictionary has changed
  59. this.partials[symbol].base = template;
  60. if (partial.subs) {
  61. // Make sure we consider parent template now
  62. if (!partials.stackText) partials.stackText = {};
  63. for (key in partial.subs) {
  64. if (!partials.stackText[key]) {
  65. partials.stackText[key] = (this.activeSub !== undefined && partials.stackText[this.activeSub]) ? partials.stackText[this.activeSub] : this.text;
  66. }
  67. }
  68. template = createSpecializedPartial(template, partial.subs, partial.partials,
  69. this.stackSubs, this.stackPartials, partials.stackText);
  70. }
  71. this.partials[symbol].instance = template;
  72. return template;
  73. },
  74. // tries to find a partial in the current scope and render it
  75. rp: function(symbol, context, partials, indent) {
  76. var partial = this.ep(symbol, partials);
  77. if (!partial) {
  78. return '';
  79. }
  80. return partial.ri(context, partials, indent);
  81. },
  82. // render a section
  83. rs: function(context, partials, section) {
  84. var tail = context[context.length - 1];
  85. if (!isArray(tail)) {
  86. section(context, partials, this);
  87. return;
  88. }
  89. for (var i = 0; i < tail.length; i++) {
  90. context.push(tail[i]);
  91. section(context, partials, this);
  92. context.pop();
  93. }
  94. },
  95. // maybe start a section
  96. s: function(val, ctx, partials, inverted, start, end, tags) {
  97. var pass;
  98. if (isArray(val) && val.length === 0) {
  99. return false;
  100. }
  101. if (typeof val == 'function') {
  102. val = this.ms(val, ctx, partials, inverted, start, end, tags);
  103. }
  104. pass = !!val;
  105. if (!inverted && pass && ctx) {
  106. ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
  107. }
  108. return pass;
  109. },
  110. // find values with dotted names
  111. d: function(key, ctx, partials, returnFound) {
  112. var found,
  113. names = key.split('.'),
  114. val = this.f(names[0], ctx, partials, returnFound),
  115. doModelGet = this.options.modelGet,
  116. cx = null;
  117. if (key === '.' && isArray(ctx[ctx.length - 2])) {
  118. val = ctx[ctx.length - 1];
  119. } else {
  120. for (var i = 1; i < names.length; i++) {
  121. found = findInScope(names[i], val, doModelGet);
  122. if (found != null) {
  123. cx = val;
  124. val = found;
  125. } else {
  126. val = '';
  127. }
  128. }
  129. }
  130. if (returnFound && !val) {
  131. return false;
  132. }
  133. if (!returnFound && typeof val == 'function') {
  134. ctx.push(cx);
  135. val = this.mv(val, ctx, partials);
  136. ctx.pop();
  137. }
  138. return val;
  139. },
  140. // find values with normal names
  141. f: function(key, ctx, partials, returnFound) {
  142. var val = false,
  143. v = null,
  144. found = false,
  145. doModelGet = this.options.modelGet;
  146. for (var i = ctx.length - 1; i >= 0; i--) {
  147. v = ctx[i];
  148. val = findInScope(key, v, doModelGet);
  149. if (val != null) {
  150. found = true;
  151. break;
  152. }
  153. }
  154. if (!found) {
  155. return (returnFound) ? false : "";
  156. }
  157. if (!returnFound && typeof val == 'function') {
  158. val = this.mv(val, ctx, partials);
  159. }
  160. return val;
  161. },
  162. // higher order templates
  163. ls: function(func, cx, partials, text, tags) {
  164. var oldTags = this.options.delimiters;
  165. this.options.delimiters = tags;
  166. this.b(this.ct(coerceToString(func.call(cx, text)), cx, partials));
  167. this.options.delimiters = oldTags;
  168. return false;
  169. },
  170. // compile text
  171. ct: function(text, cx, partials) {
  172. if (this.options.disableLambda) {
  173. throw new Error('Lambda features disabled.');
  174. }
  175. return this.c.compile(text, this.options).render(cx, partials);
  176. },
  177. // template result buffering
  178. b: function(s) { this.buf += s; },
  179. fl: function() { var r = this.buf; this.buf = ''; return r; },
  180. // method replace section
  181. ms: function(func, ctx, partials, inverted, start, end, tags) {
  182. var textSource,
  183. cx = ctx[ctx.length - 1],
  184. result = func.call(cx);
  185. if (typeof result == 'function') {
  186. if (inverted) {
  187. return true;
  188. } else {
  189. textSource = (this.activeSub && this.subsText && this.subsText[this.activeSub]) ? this.subsText[this.activeSub] : this.text;
  190. return this.ls(result, cx, partials, textSource.substring(start, end), tags);
  191. }
  192. }
  193. return result;
  194. },
  195. // method replace variable
  196. mv: function(func, ctx, partials) {
  197. var cx = ctx[ctx.length - 1];
  198. var result = func.call(cx);
  199. if (typeof result == 'function') {
  200. return this.ct(coerceToString(result.call(cx)), cx, partials);
  201. }
  202. return result;
  203. },
  204. sub: function(name, context, partials, indent) {
  205. var f = this.subs[name];
  206. if (f) {
  207. this.activeSub = name;
  208. f(context, partials, this, indent);
  209. this.activeSub = false;
  210. }
  211. }
  212. };
  213. //Find a key in an object
  214. function findInScope(key, scope, doModelGet) {
  215. var val, checkVal;
  216. if (scope && typeof scope == 'object') {
  217. if (scope[key] != null) {
  218. val = scope[key];
  219. // try lookup with get for backbone or similar model data
  220. } else if (doModelGet && scope.get && typeof scope.get == 'function') {
  221. val = scope.get(key);
  222. }
  223. }
  224. return val;
  225. }
  226. function createSpecializedPartial(instance, subs, partials, stackSubs, stackPartials, stackText) {
  227. function PartialTemplate() {};
  228. PartialTemplate.prototype = instance;
  229. function Substitutions() {};
  230. Substitutions.prototype = instance.subs;
  231. var key;
  232. var partial = new PartialTemplate();
  233. partial.subs = new Substitutions();
  234. partial.subsText = {}; //hehe. substext.
  235. partial.buf = '';
  236. stackSubs = stackSubs || {};
  237. partial.stackSubs = stackSubs;
  238. partial.subsText = stackText;
  239. for (key in subs) {
  240. if (!stackSubs[key]) stackSubs[key] = subs[key];
  241. }
  242. for (key in stackSubs) {
  243. partial.subs[key] = stackSubs[key];
  244. }
  245. stackPartials = stackPartials || {};
  246. partial.stackPartials = stackPartials;
  247. for (key in partials) {
  248. if (!stackPartials[key]) stackPartials[key] = partials[key];
  249. }
  250. for (key in stackPartials) {
  251. partial.partials[key] = stackPartials[key];
  252. }
  253. return partial;
  254. }
  255. var rAmp = /&/g,
  256. rLt = /</g,
  257. rGt = />/g,
  258. rApos = /\'/g,
  259. rQuot = /\"/g,
  260. hChars = /[&<>\"\']/;
  261. function coerceToString(val) {
  262. return String((val === null || val === undefined) ? '' : val);
  263. }
  264. function hoganEscape(str) {
  265. str = coerceToString(str);
  266. return hChars.test(str) ?
  267. str
  268. .replace(rAmp, '&amp;')
  269. .replace(rLt, '&lt;')
  270. .replace(rGt, '&gt;')
  271. .replace(rApos, '&#39;')
  272. .replace(rQuot, '&quot;') :
  273. str;
  274. }
  275. var isArray = Array.isArray || function(a) {
  276. return Object.prototype.toString.call(a) === '[object Array]';
  277. };
  278. })(typeof exports !== 'undefined' ? exports : Hogan);
  279. (function (Hogan) {
  280. // Setup regex assignments
  281. // remove whitespace according to Mustache spec
  282. var rIsWhitespace = /\S/,
  283. rQuot = /\"/g,
  284. rNewline = /\n/g,
  285. rCr = /\r/g,
  286. rSlash = /\\/g;
  287. Hogan.tags = {
  288. '#': 1, '^': 2, '<': 3, '$': 4,
  289. '/': 5, '!': 6, '>': 7, '=': 8, '_v': 9,
  290. '{': 10, '&': 11, '_t': 12
  291. };
  292. Hogan.scan = function scan(text, delimiters) {
  293. var len = text.length,
  294. IN_TEXT = 0,
  295. IN_TAG_TYPE = 1,
  296. IN_TAG = 2,
  297. state = IN_TEXT,
  298. tagType = null,
  299. tag = null,
  300. buf = '',
  301. tokens = [],
  302. seenTag = false,
  303. i = 0,
  304. lineStart = 0,
  305. otag = '{{',
  306. ctag = '}}';
  307. function addBuf() {
  308. if (buf.length > 0) {
  309. tokens.push({tag: '_t', text: new String(buf)});
  310. buf = '';
  311. }
  312. }
  313. function lineIsWhitespace() {
  314. var isAllWhitespace = true;
  315. for (var j = lineStart; j < tokens.length; j++) {
  316. isAllWhitespace =
  317. (Hogan.tags[tokens[j].tag] < Hogan.tags['_v']) ||
  318. (tokens[j].tag == '_t' && tokens[j].text.match(rIsWhitespace) === null);
  319. if (!isAllWhitespace) {
  320. return false;
  321. }
  322. }
  323. return isAllWhitespace;
  324. }
  325. function filterLine(haveSeenTag, noNewLine) {
  326. addBuf();
  327. if (haveSeenTag && lineIsWhitespace()) {
  328. for (var j = lineStart, next; j < tokens.length; j++) {
  329. if (tokens[j].text) {
  330. if ((next = tokens[j+1]) && next.tag == '>') {
  331. // set indent to token value
  332. next.indent = tokens[j].text.toString()
  333. }
  334. tokens.splice(j, 1);
  335. }
  336. }
  337. } else if (!noNewLine) {
  338. tokens.push({tag:'\n'});
  339. }
  340. seenTag = false;
  341. lineStart = tokens.length;
  342. }
  343. function changeDelimiters(text, index) {
  344. var close = '=' + ctag,
  345. closeIndex = text.indexOf(close, index),
  346. delimiters = trim(
  347. text.substring(text.indexOf('=', index) + 1, closeIndex)
  348. ).split(' ');
  349. otag = delimiters[0];
  350. ctag = delimiters[delimiters.length - 1];
  351. return closeIndex + close.length - 1;
  352. }
  353. if (delimiters) {
  354. delimiters = delimiters.split(' ');
  355. otag = delimiters[0];
  356. ctag = delimiters[1];
  357. }
  358. for (i = 0; i < len; i++) {
  359. if (state == IN_TEXT) {
  360. if (tagChange(otag, text, i)) {
  361. --i;
  362. addBuf();
  363. state = IN_TAG_TYPE;
  364. } else {
  365. if (text.charAt(i) == '\n') {
  366. filterLine(seenTag);
  367. } else {
  368. buf += text.charAt(i);
  369. }
  370. }
  371. } else if (state == IN_TAG_TYPE) {
  372. i += otag.length - 1;
  373. tag = Hogan.tags[text.charAt(i + 1)];
  374. tagType = tag ? text.charAt(i + 1) : '_v';
  375. if (tagType == '=') {
  376. i = changeDelimiters(text, i);
  377. state = IN_TEXT;
  378. } else {
  379. if (tag) {
  380. i++;
  381. }
  382. state = IN_TAG;
  383. }
  384. seenTag = i;
  385. } else {
  386. if (tagChange(ctag, text, i)) {
  387. tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
  388. i: (tagType == '/') ? seenTag - otag.length : i + ctag.length});
  389. buf = '';
  390. i += ctag.length - 1;
  391. state = IN_TEXT;
  392. if (tagType == '{') {
  393. if (ctag == '}}') {
  394. i++;
  395. } else {
  396. cleanTripleStache(tokens[tokens.length - 1]);
  397. }
  398. }
  399. } else {
  400. buf += text.charAt(i);
  401. }
  402. }
  403. }
  404. filterLine(seenTag, true);
  405. return tokens;
  406. }
  407. function cleanTripleStache(token) {
  408. if (token.n.substr(token.n.length - 1) === '}') {
  409. token.n = token.n.substring(0, token.n.length - 1);
  410. }
  411. }
  412. function trim(s) {
  413. if (s.trim) {
  414. return s.trim();
  415. }
  416. return s.replace(/^\s*|\s*$/g, '');
  417. }
  418. function tagChange(tag, text, index) {
  419. if (text.charAt(index) != tag.charAt(0)) {
  420. return false;
  421. }
  422. for (var i = 1, l = tag.length; i < l; i++) {
  423. if (text.charAt(index + i) != tag.charAt(i)) {
  424. return false;
  425. }
  426. }
  427. return true;
  428. }
  429. // the tags allowed inside super templates
  430. var allowedInSuper = {'_t': true, '\n': true, '$': true, '/': true};
  431. function buildTree(tokens, kind, stack, customTags) {
  432. var instructions = [],
  433. opener = null,
  434. tail = null,
  435. token = null;
  436. tail = stack[stack.length - 1];
  437. while (tokens.length > 0) {
  438. token = tokens.shift();
  439. if (tail && tail.tag == '<' && !(token.tag in allowedInSuper)) {
  440. throw new Error('Illegal content in < super tag.');
  441. }
  442. if (Hogan.tags[token.tag] <= Hogan.tags['$'] || isOpener(token, customTags)) {
  443. stack.push(token);
  444. token.nodes = buildTree(tokens, token.tag, stack, customTags);
  445. } else if (token.tag == '/') {
  446. if (stack.length === 0) {
  447. throw new Error('Closing tag without opener: /' + token.n);
  448. }
  449. opener = stack.pop();
  450. if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
  451. throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
  452. }
  453. opener.end = token.i;
  454. return instructions;
  455. } else if (token.tag == '\n') {
  456. token.last = (tokens.length == 0) || (tokens[0].tag == '\n');
  457. }
  458. instructions.push(token);
  459. }
  460. if (stack.length > 0) {
  461. throw new Error('missing closing tag: ' + stack.pop().n);
  462. }
  463. return instructions;
  464. }
  465. function isOpener(token, tags) {
  466. for (var i = 0, l = tags.length; i < l; i++) {
  467. if (tags[i].o == token.n) {
  468. token.tag = '#';
  469. return true;
  470. }
  471. }
  472. }
  473. function isCloser(close, open, tags) {
  474. for (var i = 0, l = tags.length; i < l; i++) {
  475. if (tags[i].c == close && tags[i].o == open) {
  476. return true;
  477. }
  478. }
  479. }
  480. function stringifySubstitutions(obj) {
  481. var items = [];
  482. for (var key in obj) {
  483. items.push('"' + esc(key) + '": function(c,p,t,i) {' + obj[key] + '}');
  484. }
  485. return "{ " + items.join(",") + " }";
  486. }
  487. function stringifyPartials(codeObj) {
  488. var partials = [];
  489. for (var key in codeObj.partials) {
  490. partials.push('"' + esc(key) + '":{name:"' + esc(codeObj.partials[key].name) + '", ' + stringifyPartials(codeObj.partials[key]) + "}");
  491. }
  492. return "partials: {" + partials.join(",") + "}, subs: " + stringifySubstitutions(codeObj.subs);
  493. }
  494. Hogan.stringify = function(codeObj, text, options) {
  495. return "{code: function (c,p,i) { " + Hogan.wrapMain(codeObj.code) + " }," + stringifyPartials(codeObj) + "}";
  496. }
  497. var serialNo = 0;
  498. Hogan.generate = function(tree, text, options) {
  499. serialNo = 0;
  500. var context = { code: '', subs: {}, partials: {} };
  501. Hogan.walk(tree, context);
  502. if (options.asString) {
  503. return this.stringify(context, text, options);
  504. }
  505. return this.makeTemplate(context, text, options);
  506. }
  507. Hogan.wrapMain = function(code) {
  508. return 'var t=this;t.b(i=i||"");' + code + 'return t.fl();';
  509. }
  510. Hogan.template = Hogan.Template;
  511. Hogan.makeTemplate = function(codeObj, text, options) {
  512. var template = this.makePartials(codeObj);
  513. template.code = new Function('c', 'p', 'i', this.wrapMain(codeObj.code));
  514. return new this.template(template, text, this, options);
  515. }
  516. Hogan.makePartials = function(codeObj) {
  517. var key, template = {subs: {}, partials: codeObj.partials, name: codeObj.name};
  518. for (key in template.partials) {
  519. template.partials[key] = this.makePartials(template.partials[key]);
  520. }
  521. for (key in codeObj.subs) {
  522. template.subs[key] = new Function('c', 'p', 't', 'i', codeObj.subs[key]);
  523. }
  524. return template;
  525. }
  526. function esc(s) {
  527. return s.replace(rSlash, '\\\\')
  528. .replace(rQuot, '\\\"')
  529. .replace(rNewline, '\\n')
  530. .replace(rCr, '\\r');
  531. }
  532. function chooseMethod(s) {
  533. return (~s.indexOf('.')) ? 'd' : 'f';
  534. }
  535. function createPartial(node, context) {
  536. var prefix = "<" + (context.prefix || "");
  537. var sym = prefix + node.n + serialNo++;
  538. context.partials[sym] = {name: node.n, partials: {}};
  539. context.code += 't.b(t.rp("' + esc(sym) + '",c,p,"' + (node.indent || '') + '"));';
  540. return sym;
  541. }
  542. Hogan.codegen = {
  543. '#': function(node, context) {
  544. context.code += 'if(t.s(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,1),' +
  545. 'c,p,0,' + node.i + ',' + node.end + ',"' + node.otag + " " + node.ctag + '")){' +
  546. 't.rs(c,p,' + 'function(c,p,t){';
  547. Hogan.walk(node.nodes, context);
  548. context.code += '});c.pop();}';
  549. },
  550. '^': function(node, context) {
  551. context.code += 'if(!t.s(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,1),c,p,1,0,0,"")){';
  552. Hogan.walk(node.nodes, context);
  553. context.code += '};';
  554. },
  555. '>': createPartial,
  556. '<': function(node, context) {
  557. var ctx = {partials: {}, code: '', subs: {}, inPartial: true};
  558. Hogan.walk(node.nodes, ctx);
  559. var template = context.partials[createPartial(node, context)];
  560. template.subs = ctx.subs;
  561. template.partials = ctx.partials;
  562. },
  563. '$': function(node, context) {
  564. var ctx = {subs: {}, code: '', partials: context.partials, prefix: node.n};
  565. Hogan.walk(node.nodes, ctx);
  566. context.subs[node.n] = ctx.code;
  567. if (!context.inPartial) {
  568. context.code += 't.sub("' + esc(node.n) + '",c,p,i);';
  569. }
  570. },
  571. '\n': function(node, context) {
  572. context.code += write('"\\n"' + (node.last ? '' : ' + i'));
  573. },
  574. '_v': function(node, context) {
  575. context.code += 't.b(t.v(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,0)));';
  576. },
  577. '_t': function(node, context) {
  578. context.code += write('"' + esc(node.text) + '"');
  579. },
  580. '{': tripleStache,
  581. '&': tripleStache
  582. }
  583. function tripleStache(node, context) {
  584. context.code += 't.b(t.t(t.' + chooseMethod(node.n) + '("' + esc(node.n) + '",c,p,0)));';
  585. }
  586. function write(s) {
  587. return 't.b(' + s + ');';
  588. }
  589. Hogan.walk = function(nodelist, context) {
  590. var func;
  591. for (var i = 0, l = nodelist.length; i < l; i++) {
  592. func = Hogan.codegen[nodelist[i].tag];
  593. func && func(nodelist[i], context);
  594. }
  595. return context;
  596. }
  597. Hogan.parse = function(tokens, text, options) {
  598. options = options || {};
  599. return buildTree(tokens, '', [], options.sectionTags || []);
  600. }
  601. Hogan.cache = {};
  602. Hogan.cacheKey = function(text, options) {
  603. return [text, !!options.asString, !!options.disableLambda, options.delimiters, !!options.modelGet].join('||');
  604. }
  605. Hogan.compile = function(text, options) {
  606. options = options || {};
  607. var key = Hogan.cacheKey(text, options);
  608. var template = this.cache[key];
  609. if (template) {
  610. var partials = template.partials;
  611. for (var name in partials) {
  612. delete partials[name].instance;
  613. }
  614. return template;
  615. }
  616. template = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
  617. return this.cache[key] = template;
  618. }
  619. })(typeof exports !== 'undefined' ? exports : Hogan);