UNPKG

101 kBJavaScriptView Raw
1(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vash = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2var debug = require('debug')
3var lg = debug('vash:main');
4
5var Lexer = require('./lib/lexer');
6var Parser = require('./lib/parser');
7var codegen = require('./lib/codegen');
8var runtime = require('./runtime');
9var helperbatch = require('./lib/helperbatch');
10var copyrtl = require('./lib/util/copyrtl');
11
12// Attach all runtime exports to enable backwards compatible behavior,
13// like `vash.install` to still be accessible in a full build.
14require('./lib/helpers');
15copyrtl(exports, runtime);
16
17exports.config = {
18
19 // Parser Options
20 favorText: false,
21 // TODO: are these even needed with proper codegen?
22 saveAT: false,
23 saveTextTag: false,
24
25 // Compiler Options
26 useWith: false,
27 htmlEscape: true,
28 helpersName: 'html',
29 modelName: 'model',
30 debug: true,
31 source: null,
32 simple: false,
33
34 // Runtime options
35 asHelper: false,
36 args: null // Internal, for compiled helpers
37}
38
39exports.version = require('./package.json').version;
40
41exports.compileStream = function() {
42 // This could eventually handle waiting until a `null`
43 // is pushed into the lexer, etc.
44 throw new Error('NotImplemented');
45}
46
47exports.compile = function(markup, options) {
48
49 if(markup === '' || typeof markup !== 'string') {
50 throw new Error('Empty or non-string cannot be compiled');
51 }
52
53 var opts = copyrtl({}, exports.config, options || {});
54
55 var l = new Lexer();
56
57 l.write(markup);
58 var tokens = l.read();
59
60 var p = new Parser(opts);
61 p.write(tokens);
62 var more = true;
63 while(more !== null) more = p.read();
64
65 p.checkStack();
66
67 // Stash the original input (new lines normalized by the lexer).
68 opts.source = l.originalInput;
69
70 p.lg(p.dumpAST());
71
72 var compiled = codegen(p.stack[0], opts);
73 lg(compiled);
74 var tpl = runtime.link(compiled, opts);
75
76 return tpl;
77}
78
79///////////////////////////////////////////////////////////////////////////
80// VASH.COMPILEHELPER
81//
82// Allow multiple helpers to be compiled as templates, for helpers that
83// do a lot of markup output.
84//
85// Takes a template such as:
86//
87// vash.helpers.p = function(text){
88// <p>@text</p>
89// }
90//
91// And compiles it. The template is then added to `vash.helpers`.
92//
93// Returns the compiled templates as named properties of an object.
94//
95// This is string manipulation at its... something. It grabs the arguments
96// and function name using a regex, not actual parsing. Definitely error-
97// prone, but good enough. This is meant to facilitate helpers with complex
98// markup, but if something more advanced needs to happen, a plain helper
99// can be defined and markup added using the manual Buffer API.
100exports['compileHelper'] = helperbatch.bind(null, 'helper', exports.compile);
101
102///////////////////////////////////////////////////////////////////////////
103// VASH.COMPILEBATCH
104//
105// Allow multiple templates to be contained within the same string.
106// Templates are separated via a sourceURL-esque string:
107//
108// //@batch = tplname/or/path
109//
110// The separator is forgiving in terms of whitespace:
111//
112// // @ batch=tplname/or/path
113//
114// Is just as valid.
115//
116// Returns the compiled templates as named properties of an object.
117exports['compileBatch'] = exports['batch'] = helperbatch.bind(null, 'batch', exports.compile);
118},{"./lib/codegen":2,"./lib/helperbatch":4,"./lib/helpers":6,"./lib/lexer":9,"./lib/parser":24,"./lib/util/copyrtl":26,"./package.json":34,"./runtime":35,"debug":29}],2:[function(require,module,exports){
119
120var debug = require('debug');
121var lg = debug('vash:codegen');
122
123var gens = {}
124
125gens.VashProgram = function(node, opts, generate) {
126 return node.body.map(generate).join('');
127}
128
129gens.VashExplicitExpression = function(node, opts, generate) {
130 var str = node.values.map(generate).join('');
131 str = '(' + maybeHTMLEscape(node, opts, str) + ')';
132 if (parentIsContent(node)) {
133 str = bewrap(str);
134 }
135 return str;
136}
137
138gens.VashExpression = function(node, opts, generate) {
139 var str = node.values.map(generate).join('');
140 str = bewrap(maybeHTMLEscape(node, opts, str));
141 return str;
142}
143
144gens.VashRegex = function(node, opts, generate) {
145 var str = node.values.map(generate).join('');
146 str = maybeHTMLEscape(node, opts, str);
147 if (parentIsContent(node)) {
148 str = bewrap(str);
149 }
150 return str;
151}
152
153gens.VashMarkup = function(node, opts, generate) {
154 var isText = node.name === 'text';
155 var name = node.name ? bcwrap(node.name) : '';
156 var tagNameValue = name
157 + (node.expression ? generate(node.expression) : '');
158
159 var tagOpen = ''
160 + bcwrap('<')
161 + tagNameValue
162 + bcwrap(node.attributes.length ? ' ' : '')
163 + node.attributes.map(generate).join(bcwrap(' '))
164
165 var values;
166 var tagClose;
167
168 if (node.isVoid) {
169 tagOpen += bcwrap(node.voidClosed ? ' />' : '>');
170 values = '';
171 tagClose = '';
172 } else {
173 tagOpen += bcwrap('>');
174 values = node.values.map(generate).join('');
175 tagClose = node.isClosed ? bcwrap('</') + tagNameValue + bcwrap('>') : '';
176 }
177
178 if (isText) {
179 tagOpen = tagClose = '';
180 }
181
182 return ''
183 + (parentIsExpression(node) ? '(function () {' : '')
184 + dbgstart(node, opts)
185 + tagOpen
186 + values
187 + tagClose
188 + dbgend(node, opts)
189 + (parentIsExpression(node) ? '}())' : '')
190}
191
192gens.VashMarkupAttribute = function(node, opts, generate) {
193 var quote = node.rightIsQuoted || '';
194 quote = escapeMarkupContent(quote);
195 return ''
196 + dbgstart(node, opts)
197 + node.left.map(generate).join('')
198 + (node.right.length || node.rightIsQuoted
199 ? bcwrap('=' + quote)
200 + node.right.map(generate).join('')
201 + bcwrap(quote)
202 : '')
203 + dbgend(node, opts);
204}
205
206gens.VashMarkupContent = function(node, opts, generate) {
207 return ''
208 + dbgstart(node, opts)
209 + node.values.map(generate).join('')
210 + dbgend(node, opts);
211}
212
213gens.VashMarkupComment = function(node, opts, generate) {
214 return ''
215 + bcwrap('<!--')
216 + dbgstart(node, opts)
217 + node.values.map(generate).join('')
218 + dbgend(node, opts)
219 + bcwrap('-->');
220}
221
222gens.VashBlock = function(node, opts, generate) {
223 var hasValues = node.values.length > 0;
224 var unsafeForDbg = node.keyword === 'switch'
225 || !node.name
226 || !hasValues;
227 var openBrace = hasValues || node.hasBraces
228 ? '{' + (unsafeForDbg ? '' : dbgstart(node, opts))
229 : '';
230 var closeBrace = hasValues || node.hasBraces
231 ? (unsafeForDbg ? '' : dbgend(node, opts)) + '}'
232 : '';
233 return ''
234 + (node.keyword ? node.keyword : '')
235 + node.head.map(generate).join('')
236 + openBrace
237 + node.values.map(generate).join('')
238 + closeBrace
239 + node.tail.map(generate).join('');
240}
241
242gens.VashIndexExpression = function(node, opts, generate) {
243 var str = node.values.map(generate).join('');
244 return '[' + str + ']';
245}
246
247gens.VashText = function(node, opts, generate) {
248 if (!node.value.length) return '';
249 return parentIsContent(node)
250 ? ''
251 + dbgstart(node, opts)
252 + bcwrap(escapeMarkupContent(node.value))
253 + dbgend(node, opts)
254 : node.value;
255}
256
257gens.VashComment = function(node, opts, generate) {
258 return '';
259}
260
261var reQuote = /(['"])/g;
262var reEscapedQuote = /\\+(["'])/g;
263var reLineBreak = /\n/g;
264var reHelpersName = /HELPERSNAME/g;
265var reModelName = /MODELNAME/g;
266var reOriginalMarkup = /ORIGINALMARKUP/g;
267
268function escapeMarkupContent(str) {
269 return str
270 .replace(/\\/g, '\\\\')
271 .replace(reQuote, '\\$1')
272 .replace(reLineBreak, '\\n');
273}
274
275var BUFFER_HEAD = '\n__vbuffer.push(';
276var BUFFER_TAIL = ');\n';
277
278// buffer content wrap
279function bcwrap(str) {
280 return BUFFER_HEAD + '\'' + str.replace(/\n/, '\\n') + '\'' + BUFFER_TAIL;
281}
282
283// buffer expression wrap
284function bewrap(str) {
285 return BUFFER_HEAD + str + BUFFER_TAIL;
286}
287
288function parentIsContent(node) {
289 return node.parent.type === 'VashMarkup'
290 || node.parent.type === 'VashMarkupContent'
291 || node.parent.type === 'VashMarkupComment'
292 || node.parent.type === 'VashMarkupAttribute'
293 || node.parent.type === 'VashProgram';
294}
295
296function parentIsExpression(node) {
297 return node.parent.type === 'VashExpression'
298 || node.parent.type === 'VashExplicitExpression'
299 || node.parent.type === 'VashIndexExpression';
300}
301
302function dbgstart(node, opts) {
303 return opts.debug
304 ? ''
305 + opts.helpersName + '.vl = ' + node.startloc.line + ', '
306 + opts.helpersName + '.vc = ' + node.startloc.column + '; \n'
307 : '';
308}
309
310function dbgend(node, opts) {
311 return opts.debug
312 ? ''
313 + opts.helpersName + '.vl = ' + node.endloc.line + ', '
314 + opts.helpersName + '.vc = ' + node.endloc.column + '; \n'
315 : '';
316}
317
318function maybeHTMLEscape(node, opts, str) {
319 if (parentIsContent(node) && opts.htmlEscape) {
320 return opts.helpersName + '.escape(' + str + ').toHtmlString()';
321 } else {
322 return str;
323 }
324}
325
326function replaceDevTokens(str, opts){
327 return str
328 .replace( reHelpersName, opts.helpersName )
329 .replace( reModelName, opts.modelName );
330}
331
332function head(opts){
333 var str = ''
334 + (opts.debug ? 'try { \n' : '')
335 + 'var __vbuffer = HELPERSNAME.buffer; \n'
336 + 'HELPERSNAME.options = __vopts; \n'
337 + 'MODELNAME = MODELNAME || {}; \n'
338 + (opts.useWith ? 'with( MODELNAME ){ \n' : '');
339
340 str = replaceDevTokens(str, opts);
341 return str;
342}
343
344function helperHead(opts){
345 var str = ''
346 + (opts.debug ? 'try { \n' : '')
347 + 'var __vbuffer = this.buffer; \n'
348 + 'var MODELNAME = this.model; \n'
349 + 'var HELPERSNAME = this; \n';
350
351 str = replaceDevTokens(str, opts);
352 return str;
353}
354
355function tail(opts){
356 var str = ''
357 + (opts.simple
358 ? 'return HELPERSNAME.buffer.join(""); \n'
359 : ';(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, HELPERSNAME)); \n'
360 + 'return (__vopts && __vopts.asContext) \n'
361 + ' ? HELPERSNAME \n'
362 + ' : HELPERSNAME.toString(); \n' )
363 + (opts.useWith ? '} \n' : '')
364 + (opts.debug ? '} catch( e ){ \n'
365 + ' HELPERSNAME.reportError( e, HELPERSNAME.vl, HELPERSNAME.vc, "ORIGINALMARKUP", "!LB!", true ); \n'
366 + '} \n' : '');
367
368 str = replaceDevTokens(str, opts)
369 .replace(reOriginalMarkup, escapeForDebug(opts.source));
370
371 return str;
372}
373
374 function helperTail(opts){
375 var str = ''
376 + (opts.debug ? '} catch( e ){ \n'
377 + ' HELPERSNAME.reportError( e, HELPERSNAME.vl, HELPERSNAME.vc, "ORIGINALMARKUP", "!LB!", true ); \n'
378 + '} \n' : '');
379
380 str = replaceDevTokens(str, opts)
381 .replace(reOriginalMarkup, escapeForDebug(opts.source));
382
383 return str;
384}
385
386function escapeForDebug( str ){
387 return str
388 .replace(reLineBreak, '!LB!')
389 .replace(reQuote, '\\$1')
390 .replace(reEscapedQuote, '\\$1')
391}
392
393// Not necessary, but provides faster execution when not in debug mode
394// and looks nicer.
395function condenseContent(str) {
396 return str
397 .replace(/'\);\n+__vbuffer.push\('/g, '')
398 .replace(/\n+/g, '\n');
399}
400
401function generate(node, opts) {
402
403 function gen(opts, node) {
404 lg('Entering ' + node.type);
405 var str = gens[node.type](node, opts, genChild);
406 lg('Leaving ' + node.type);
407 return str;
408
409 function genChild(child) {
410 if (!child.parent) child.parent = node;
411 lg('Generating child type %s of parent type %s', child.type, node.type)
412 return gen(opts, child);
413 }
414 }
415
416 var generated = gen(opts, node);
417
418 var body;
419 if(!opts.asHelper){
420 body = head(opts) + generated + tail(opts);
421 } else {
422 body = helperHead(opts) + generated + helperTail(opts);
423 }
424
425 return opts.debug
426 ? body
427 : condenseContent(body);
428}
429
430module.exports = generate;
431
432},{"debug":29}],3:[function(require,module,exports){
433
434exports.context = function(input, lineno, columnno, linebreak) {
435 linebreak = linebreak || '!LB!';
436
437 var lines = input.split(linebreak)
438 , contextSize = lineno === 0 && columnno === 0 ? lines.length - 1 : 3
439 , start = Math.max(0, lineno - contextSize)
440 , end = Math.min(lines.length, lineno + contextSize);
441
442 return lines
443 .slice(start, end)
444 .map(function(line, i, all){
445 var curr = i + start + 1;
446
447 return (curr === lineno ? ' > ' : ' ')
448 + (curr < 10 ? ' ' : '')
449 + curr
450 + ' | '
451 + line;
452 }).join('\n');
453}
454},{}],4:[function(require,module,exports){
455
456var slice = Array.prototype.slice
457
458 , reHelperFuncHead = /vash\.helpers\.([^= ]+?)\s*=\s*function([^(]*?)\(([^)]*?)\)\s*{/
459 , reHelperFuncTail = /\}$/
460
461 , reBatchSeparator = /^\/\/\s*@\s*batch\s*=\s*(.*?)$/
462
463// The logic for compiling a giant batch of templates or several
464// helpers is nearly exactly the same. The only difference is the
465// actual compilation method called, and the regular expression that
466// determines how the giant string is split into named, uncompiled
467// template strings.
468module.exports = function compile(type, compile, str, options){
469
470 var separator = type === 'helper'
471 ? reHelperFuncHead
472 : reBatchSeparator;
473
474 var tpls = splitByNamedTpl(separator, str, function(ma, name){
475 return name.replace(/^\s+|\s+$/, '');
476 }, type === 'helper' ? true : false);
477
478 if(tpls){
479 Object.keys(tpls).forEach(function(path){
480 tpls[path] = type === 'helper'
481 ? compileSingleHelper(compile, tpls[path], options)
482 : compile('@{' + tpls[path] + '}', options);
483 });
484
485 tpls.toClientString = function(){
486 return Object.keys(tpls).reduce(function(prev, curr){
487 if(curr === 'toClientString'){
488 return prev;
489 }
490 return prev + tpls[curr].toClientString() + '\n';
491 }, '')
492 }
493 }
494
495 return tpls;
496}
497
498// Given a separator regex and a function to transform the regex result
499// into a name, take a string, split it, and group the rejoined strings
500// into an object.
501// This is useful for taking a string, such as
502//
503// // tpl1
504// what what
505// and more
506//
507// // tpl2
508// what what again
509//
510// and returning:
511//
512// {
513// tpl1: 'what what\nand more\n',
514// tpl2: 'what what again'
515// }
516var splitByNamedTpl = function(reSeparator, markup, resultHandler, keepSeparator){
517
518 var lines = markup.split(/[\n\r]/g)
519 ,tpls = {}
520 ,paths = []
521 ,currentPath = ''
522
523 lines.forEach(function(line, i){
524
525 var pathResult = reSeparator.exec(line)
526 ,handlerResult = pathResult ? resultHandler.apply(pathResult, pathResult) : null
527
528 if(handlerResult){
529 currentPath = handlerResult;
530 tpls[currentPath] = [];
531 }
532
533 if((!handlerResult || keepSeparator) && line){
534 tpls[currentPath].push(line);
535 }
536 });
537
538 Object.keys(tpls).forEach(function(key){
539 tpls[key] = tpls[key].join('\n');
540 })
541
542 return tpls;
543}
544
545var compileSingleHelper = function(compile, str, options){
546
547 options = options || {};
548
549 // replace leading/trailing spaces, and parse the function head
550 var def = str.replace(/^[\s\n\r]+|[\s\n\r]+$/, '').match(reHelperFuncHead)
551 // split the function arguments, kill all whitespace
552 ,args = def[3].split(',').map(function(arg){ return arg.replace(' ', '') })
553 ,name = def[1]
554 ,body = str
555 .replace( reHelperFuncHead, '' )
556 .replace( reHelperFuncTail, '' )
557
558 // Wrap body in @{} to simulate it actually being inside a function
559 // definition, since we manually stripped it. Without this, statements
560 // such as `this.what = "what";` that are at the beginning of the body
561 // will be interpreted as markup.
562 body = '@{' + body + '}';
563
564 // `args` and `asHelper` inform `vash.compile/link` that this is a helper
565 options.args = args;
566 options.asHelper = name;
567 return compile(body, options);
568}
569
570},{}],5:[function(require,module,exports){
571var helpers = require('../../runtime').helpers;
572
573///////////////////////////////////////////////////////////////////////////
574// EXAMPLE HELPER: syntax highlighting
575
576helpers.config.highlighter = null;
577
578helpers.highlight = function(lang, cb){
579
580 // context (this) is and instance of Helpers, aka a rendering context
581
582 // mark() returns an internal `Mark` object
583 // Use it to easily capture output...
584 var startMark = this.buffer.mark();
585
586 // cb() is simply a user-defined function. It could (and should) contain
587 // buffer additions, so we call it...
588 cb( this.model );
589
590 // ... and then use fromMark() to grab the output added by cb().
591 var cbOutLines = this.buffer.fromMark(startMark);
592
593 // The internal buffer should now be back to where it was before this
594 // helper started, and the output is completely contained within cbOutLines.
595
596 this.buffer.push( '<pre><code>' );
597
598 if( helpers.config.highlighter ){
599 this.buffer.push( helpers.config.highlighter(lang, cbOutLines.join('')).value );
600 } else {
601 this.buffer.push( cbOutLines );
602 }
603
604 this.buffer.push( '</code></pre>' );
605
606 // returning is allowed, but could cause surprising effects. A return
607 // value will be directly added to the output directly following the above.
608}
609
610},{"../../runtime":35}],6:[function(require,module,exports){
611require('./trim');
612require('./highlight');
613require('./layout');
614module.exports = require('../../runtime');
615},{"../../runtime":35,"./highlight":5,"./layout":7,"./trim":8}],7:[function(require,module,exports){
616var helpers = require('../../runtime').helpers;
617var copyrtl = require('../util/copyrtl');
618
619// For now, using the layout helpers requires a full build. For now.
620var vash = require('../../index');
621module.exports = vash;
622
623///////////////////////////////////////////////////////////////////////////
624// LAYOUT HELPERS
625
626// semi hacky guard to prevent non-nodejs erroring
627if( typeof window === 'undefined' ){
628 var fs = require('fs')
629 ,path = require('path')
630}
631
632// TRUE implies that all TPLS are loaded and waiting in cache
633helpers.config.browser = false;
634
635vash.loadFile = function(filepath, options, cb){
636
637 // options are passed in via Express
638 // {
639 // settings:
640 // {
641 // env: 'development',
642 // 'jsonp callback name': 'callback',
643 // 'json spaces': 2,
644 // views: '/Users/drew/Dropbox/js/vash/test/fixtures/views',
645 // 'view engine': 'vash'
646 // },
647 // _locals: [Function: locals],
648 // cache: false
649 // }
650
651 // The only required options are:
652 //
653 // settings: {
654 // views: ''
655 // }
656
657 options = copyrtl({}, vash.config, options || {});
658
659 var browser = helpers.config.browser
660 ,tpl
661
662 if( !browser && options.settings && options.settings.views ){
663 // this will really only have an effect on windows
664 filepath = path.normalize( filepath );
665
666 if( filepath.indexOf( path.normalize( options.settings.views ) ) === -1 ){
667 // not an absolute path
668 filepath = path.join( options.settings.views, filepath );
669 }
670
671 if( !path.extname( filepath ) ){
672 filepath += '.' + ( options.settings['view engine'] || 'vash' )
673 }
674 }
675
676 // TODO: auto insert 'model' into arguments
677 try {
678 // if browser, tpl must exist in tpl cache
679 tpl = options.cache || browser
680 ? helpers.tplcache[filepath] || ( helpers.tplcache[filepath] = vash.compile(fs.readFileSync(filepath, 'utf8')) )
681 : vash.compile( fs.readFileSync(filepath, 'utf8') )
682
683 cb && cb(null, tpl);
684 } catch(e) {
685 cb && cb(e, null);
686 }
687}
688
689vash.renderFile = vash.__express = function(filepath, options, cb){
690
691 vash.loadFile(filepath, options, function(err, tpl){
692 // auto setup an `onRenderEnd` callback to seal the layout
693 var prevORE = options.onRenderEnd;
694
695 cb( err, !err && tpl(options, function(err, ctx){
696 ctx.finishLayout()
697 if( prevORE ) prevORE(err, ctx);
698 }) );
699 })
700}
701
702helpers._ensureLayoutProps = function(){
703 this.appends = this.appends || {};
704 this.prepends = this.prepends || {};
705 this.blocks = this.blocks || {};
706
707 this.blockMarks = this.blockMarks || {};
708}
709
710helpers.finishLayout = function(){
711 this._ensureLayoutProps();
712
713 var self = this, name, marks, blocks, prepends, appends, injectMark, m, content, block
714
715 // each time `.block` is called, a mark is added to the buffer and
716 // the `blockMarks` stack. Find the newest/"highest" mark on the stack
717 // for each named block, and insert the rendered content (prepends, block, appends)
718 // in place of that mark
719
720 for( name in this.blockMarks ){
721
722 marks = this.blockMarks[name];
723
724 prepends = this.prepends[name];
725 blocks = this.blocks[name];
726 appends = this.appends[name];
727
728 injectMark = marks.pop();
729
730 // mark current point in buffer in prep to grab rendered content
731 m = this.buffer.mark();
732
733 prepends && prepends.forEach(function(p){ self.buffer.pushConcat( p ); });
734
735 // a block might never have a callback defined, e.g. is optional
736 // with no default content
737 block = blocks.pop();
738 block && this.buffer.pushConcat( block );
739
740 appends && appends.forEach(function(a){ self.buffer.pushConcat( a ); });
741
742 // grab rendered content
743 content = this.buffer.fromMark( m )
744
745 // Join, but split out the VASHMARKS so further buffer operations are still
746 // sane. Join is required to prevent max argument errors when large templates
747 // are being used.
748 content = compactContent(content);
749
750 // Prep for apply, ensure the right location (mark) is used for injection.
751 content.unshift( injectMark, 0 );
752 this.buffer.spliceMark.apply( this.buffer, content );
753 }
754
755 for( name in this.blockMarks ){
756
757 // kill all other marks registered as blocks
758 this.blockMarks[name].forEach(function(m){ m.destroy(); });
759 }
760
761 // this should only be able to happen once
762 delete this.blockMarks;
763 delete this.prepends;
764 delete this.blocks;
765 delete this.appends;
766
767 // and return the whole thing
768 return this.toString();
769}
770
771// Given an array, condense all the strings to as few array elements
772// as possible, while preserving `Mark`s as individual elements.
773function compactContent(content) {
774 var re = vash.Mark.re;
775 var parts = [];
776 var str = '';
777
778 content.forEach(function(part) {
779 if (re.exec(part)) {
780 parts.push(str, part);
781 str = '';
782 } else {
783 // Ensure `undefined`s are not `toString`ed
784 str += (part || '');
785 }
786 });
787
788 // And don't forget the rest.
789 parts.push(str);
790
791 return parts;
792}
793
794helpers.extend = function(path, ctn){
795 var self = this
796 ,buffer = this.buffer
797 ,origModel = this.model
798 ,layoutCtx;
799
800 this._ensureLayoutProps();
801
802 // this is a synchronous callback
803 vash.loadFile(path, this.model, function(err, tpl){
804
805 if (err) throw err;
806
807 // any content that is outside of a block but within an "extend"
808 // callback is completely thrown away, as the destination for such
809 // content is undefined
810 var start = self.buffer.mark();
811
812 ctn(self.model);
813
814 // ... and just throw it away
815 var content = self.buffer.fromMark( start )
816 // TODO: unless it's a mark id? Removing everything means a block
817 // MUST NOT be defined in an extend callback
818 //,filtered = content.filter( vash.Mark.uidLike )
819
820 //self.buffer.push( filtered );
821
822 // `isExtending` is necessary because named blocks in the layout
823 // will be interpreted after named blocks in the content. Since
824 // layout named blocks should only be used as placeholders in the
825 // event that their content is redefined, `block` must know to add
826 // the defined content at the head or tail or the block stack.
827 self.isExtending = true;
828 tpl( self.model, { context: self } );
829 self.isExtending = false;
830 });
831
832 this.model = origModel;
833}
834
835helpers.include = function(name, model){
836
837 var self = this
838 ,buffer = this.buffer
839 ,origModel = this.model;
840
841 // TODO: should this be in a new context? Jade looks like an include
842 // is not shared with parent context
843
844 // this is a synchronous callback
845 vash.loadFile(name, this.model, function(err, tpl){
846 if (err) throw err;
847 tpl( model || self.model, { context: self } );
848 });
849
850 this.model = origModel;
851}
852
853helpers.block = function(name, ctn){
854 this._ensureLayoutProps();
855
856 var self = this
857 // ensure that we have a list of marks for this name
858 ,marks = this.blockMarks[name] || ( this.blockMarks[name] = [] )
859 // ensure a list of blocks for this name
860 ,blocks = this.blocks[name] || ( this.blocks[name] = [] )
861 ,start
862 ,content;
863
864 // render out the content immediately, if defined, to attempt to grab
865 // "dependencies" like other includes, blocks, etc
866 if( ctn ){
867 start = this.buffer.mark();
868 ctn( this.model );
869 content = this.buffer.fromMark( start );
870
871 // add rendered content to named list of blocks
872 if( content.length && !this.isExtending ){
873 blocks.push( content );
874 }
875
876 // if extending the rendered content must be allowed to be redefined
877 if( content.length && this.isExtending ){
878 blocks.unshift( content );
879 }
880 }
881
882 // mark the current location as "where this block will end up"
883 marks.push( this.buffer.mark( 'block-' + name ) );
884}
885
886helpers._handlePrependAppend = function( type, name, ctn ){
887 this._ensureLayoutProps();
888
889 var start = this.buffer.mark()
890 ,content
891 ,stack = this[type]
892 ,namedStack = stack[name] || ( stack[name] = [] )
893
894 ctn( this.model );
895 content = this.buffer.fromMark( start );
896
897 namedStack.push( content );
898}
899
900helpers.append = function(name, ctn){
901 this._handlePrependAppend( 'appends', name, ctn );
902}
903
904helpers.prepend = function(name, ctn){
905 this._handlePrependAppend( 'prepends', name, ctn );
906}
907
908},{"../../index":1,"../../runtime":35,"../util/copyrtl":26,"fs":28,"path":32}],8:[function(require,module,exports){
909var helpers = require('../../runtime').helpers;
910
911// Trim whitespace from the start and end of a string
912helpers.trim = function(val){
913 return val.replace(/^\s*|\s*$/g, '');
914}
915},{"../../runtime":35}],9:[function(require,module,exports){
916var debug = require('debug');
917var tokens = require('./tokens');
918
919// This pattern and basic lexer code were originally from the
920// Jade lexer, but have been modified:
921// https://github.com/visionmedia/jade/blob/master/lib/lexer.js
922
923function VLexer(){
924 this.lg = debug('vash:lexer');
925 this.input = '';
926 this.originalInput = '';
927 this.lineno = 1;
928 this.charno = 0;
929}
930
931module.exports = VLexer;
932
933VLexer.prototype = {
934
935 write: function(input) {
936 var normalized = input.replace(/\r\n|\r/g, '\n');
937
938 // Kill BOM if this is the first chunk.
939 if (this.originalInput.length == 0) {
940 normalized = normalized.replace(/^\uFEFF/, '');
941 }
942
943 this.input += normalized;
944 this.originalInput += normalized;
945 return true;
946 },
947
948 read: function() {
949 var out = []
950 , result;
951 while(this.input.length) {
952 result = this.advance();
953 if (result) {
954 out.push(result);
955 this.lg('Read %s at line %d, column %d with content %s',
956 result.type, result.line, result.chr, result.val.replace(/(\n)/, '\\n'));
957 }
958 }
959 return out;
960 },
961
962 scan: function(regexp, type){
963 var captures, token;
964 if (captures = regexp.exec(this.input)) {
965 this.input = this.input.substr((captures[1].length));
966
967 token = {
968 type: type
969 ,line: this.lineno
970 ,chr: this.charno
971 ,val: captures[1] || ''
972 ,toString: function(){
973 return '[' + this.type
974 + ' (' + this.line + ',' + this.chr + '): '
975 + this.val.replace(/(\n)/, '\\n') + ']';
976 }
977 };
978
979 this.charno += captures[0].length;
980 return token;
981 }
982 }
983
984 ,advance: function() {
985
986 var i, name, test, result;
987
988 for(i = 0; i < tokens.tests.length; i += 2){
989 test = tokens.tests[i+1];
990 test.displayName = tokens.tests[i];
991
992 if(typeof test === 'function'){
993 // assume complex callback
994 result = test.call(this);
995 }
996
997 if(typeof test.exec === 'function'){
998 // assume regex
999 result = this.scan(test, tokens.tests[i]);
1000 }
1001
1002 if( result ){
1003 return result;
1004 }
1005 }
1006 }
1007}
1008
1009},{"./tokens":25,"debug":29}],10:[function(require,module,exports){
1010var Node = module.exports = function BlockNode() {
1011 this.type = 'VashBlock';
1012 this.keyword = null;
1013 this.head = [];
1014 this.values = [];
1015 this.tail = [];
1016 this.hasBraces = null;
1017 this.startloc = null;
1018 this.endloc = null;
1019
1020 this._reachedOpenBrace = false;
1021 this._reachedCloseBrace = false;
1022 this._withinCommentLine = false;
1023 this._waitingForEndQuote = null;
1024}
1025
1026Node.prototype.endOk = function() {
1027 var gradeSchool = this.hasBraces
1028 && (!this._reachedOpenBrace || !this._reachedCloseBrace);
1029
1030 return (gradeSchool || this._withinCommentLine || this._waitingForEndQuote)
1031 ? false
1032 : true;
1033}
1034},{}],11:[function(require,module,exports){
1035var Node = module.exports = function CommentNode() {
1036 this.type = 'VashComment';
1037 this.values = [];
1038 this.startloc = null;
1039 this.endloc = null;
1040
1041 this._waitingForClose = null;
1042}
1043
1044Node.prototype.endOk = function() {
1045 return this._waitingForClose
1046 ? false
1047 : true;
1048}
1049},{}],12:[function(require,module,exports){
1050var Node = module.exports = function ExplicitExpressionNode() {
1051 this.type = 'VashExplicitExpression';
1052 this.values = [];
1053 this.startloc = null;
1054 this.endloc = null;
1055
1056 this._waitingForParenClose = null;
1057 this._waitingForEndQuote = null;
1058}
1059
1060Node.prototype.endOk = function() {
1061 return this._waitingForEndQuote || this._waitingForParenClose
1062 ? false
1063 : true;
1064}
1065},{}],13:[function(require,module,exports){
1066var Node = module.exports = function ExpressionNode() {
1067 this.type = 'VashExpression';
1068 this.values = [];
1069 this.startloc = null;
1070 this.endloc = null;
1071}
1072},{}],14:[function(require,module,exports){
1073var Node = module.exports = function IndexExpressionNode() {
1074 this.type = 'VashIndexExpression';
1075 this.values = [];
1076 this.startloc = null;
1077 this.endloc = null;
1078
1079 this._waitingForEndQuote = null;
1080 this._waitingForHardParenClose = null;
1081}
1082
1083Node.prototype.endOk = function() {
1084 return (this._waitingForEndQuote || this._waitingForHardParenClose)
1085 ? false
1086 : true;
1087}
1088},{}],15:[function(require,module,exports){
1089module.exports = function LocationNode() {
1090 this.line = 1;
1091 this.column = 0;
1092}
1093},{}],16:[function(require,module,exports){
1094var Node = module.exports = function MarkupNode() {
1095 this.type = 'VashMarkup';
1096 this.name = null;
1097 this.expression = null; // or ExpressionNode
1098 this.attributes = [];
1099 this.values = [];
1100 this.isVoid = false;
1101 this.voidClosed = false;
1102 this.isClosed = false;
1103 this.startloc = null;
1104 this.endloc = null;
1105
1106 this._finishedOpen = false;
1107 // Waiting for the finishing > of the </close>
1108 this._waitingForFinishedClose = false;
1109}
1110
1111var voids = module.exports.voids = [
1112
1113 // Just a little bit of cheating.
1114 '!DOCTYPE', '!doctype', 'doctype',
1115
1116 // From the spec
1117 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
1118 'link', 'meta', 'param', 'source', 'track', 'wbr'
1119];
1120
1121Node.isVoid = function(name) {
1122 return voids.indexOf(name) > -1;
1123}
1124
1125// HTML5 allows these to be non-closed.
1126// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#generate-implied-end-tags
1127var implieds = [
1128 'dd', 'dt', 'li', 'option', 'optgroup', 'p', 'rp', 'rt'
1129]
1130
1131Node.isImplied = function(name) {
1132 return implieds.indexOf(name) > -1;
1133}
1134
1135Node.prototype.endOk = function() {
1136
1137 if (
1138 this._finishedOpen
1139 && (this.isClosed || this.voidClosed)
1140 ) {
1141 return true;
1142 }
1143
1144 return false;
1145}
1146},{}],17:[function(require,module,exports){
1147var Node = module.exports = function MarkupAttributeNode() {
1148 this.type = 'VashMarkupAttribute';
1149 this.left = [];
1150 this.right = [];
1151 this.rightIsQuoted = false;
1152 this.startloc = null;
1153 this.endloc = null;
1154
1155 this._finishedLeft = false;
1156 this._expectRight = false;
1157}
1158
1159Node.prototype.endOk = function() {
1160 // TODO: this should include expecting right + found quotes or not.
1161 return this._finishedLeft
1162 ? true
1163 : false;
1164}
1165},{}],18:[function(require,module,exports){
1166var Node = module.exports = function MarkupCommentNode() {
1167 this.type = 'VashMarkupComment';
1168 this.values = [];
1169 this.startloc = null;
1170 this.endloc = null;
1171
1172 this._finishedOpen = false
1173 this._waitingForClose = null;
1174}
1175
1176Node.prototype.endOk = function() {
1177 return this._waitingForClose || this._finishedOpen
1178 ? false
1179 : true;
1180}
1181},{}],19:[function(require,module,exports){
1182var Node = module.exports = function MarkupContentNode() {
1183 this.type = 'VashMarkupContent';
1184 this.values = [];
1185 this.startloc = null;
1186 this.endloc = null;
1187
1188 this._waitingForNewline = null;
1189}
1190
1191Node.prototype.endOk = function() {
1192 return this._waitingForNewline
1193 ? false
1194 : true;
1195}
1196},{}],20:[function(require,module,exports){
1197module.exports = function ProgramNode() {
1198 this.type = 'VashProgram';
1199 this.body = [];
1200}
1201},{}],21:[function(require,module,exports){
1202
1203// Need to handle:
1204// if (true) /abc/.test()
1205// if (/abc/.test('what'))
1206// @(/abc/.exec('abc'))
1207// @{ var re = /abc/gi; }
1208// if (a/b) {}
1209// @(a/=b) // Previous is IDENTIFIER or WHITESPACE
1210
1211var Node = module.exports = function RegexNode() {
1212 this.type = 'VashRegex';
1213 this.values = [];
1214 this.startloc = null;
1215 this.endloc = null;
1216
1217 this._waitingForForwardSlash = null;
1218 this._waitingForFlags = null;
1219}
1220
1221Node.prototype.endOk = function() {
1222 return this._waitingForForwardSlash || this._waitingForFlags
1223 ? false
1224 : true;
1225}
1226},{}],22:[function(require,module,exports){
1227module.exports = function TextNode() {
1228 this.type = 'VashText';
1229 this.value = '';
1230 this.startloc = null;
1231 this.endloc = null;
1232}
1233},{}],23:[function(require,module,exports){
1234function clean(node) {
1235 return Object.keys(node).reduce(function(out, key) {
1236 var value = node[key];
1237 if (key[0] !== '_' && typeof value !== 'function') {
1238 if (Array.isArray(value)) {
1239 out[key] = value.map(clean);
1240 } else {
1241 out[key] = value;
1242 }
1243 }
1244 return out;
1245 }, {});
1246}
1247
1248exports.clean = clean;
1249
1250},{}],24:[function(require,module,exports){
1251
1252var debug = require('debug');
1253
1254var tks = require('./tokens');
1255var nodestuff = require('./nodestuff');
1256var error = require('./error');
1257var namer = require('./util/fn-namer');
1258
1259var ProgramNode = namer(require('./nodes/program'));
1260var TextNode = namer(require('./nodes/text'));
1261var MarkupNode = namer(require('./nodes/markup'));
1262var MarkupCommentNode = namer(require('./nodes/markupcomment'));
1263var MarkupContentNode = namer(require('./nodes/markupcontent'));
1264var MarkupAttributeNode = namer(require('./nodes/markupattribute'));
1265var ExpressionNode = namer(require('./nodes/expression'));
1266var ExplicitExpressionNode = namer(require('./nodes/explicitexpression'));
1267var IndexExpressionNode = namer(require('./nodes/indexexpression'));
1268var LocationNode = namer(require('./nodes/location'));
1269var BlockNode = namer(require('./nodes/block'));
1270var CommentNode = namer(require('./nodes/comment'));
1271var RegexNode = namer(require('./nodes/regex'));
1272
1273function Parser(opts) {
1274 this.lg = debug('vash:parser');
1275 this.tokens = [];
1276 this.deferredTokens = [];
1277 this.node = null;
1278 this.stack = [];
1279 this.inputText = '';
1280 this.opts = opts || {};
1281 this.previousNonWhitespace = null
1282}
1283
1284module.exports = Parser;
1285
1286Parser.prototype.decorateError = function(err, line, column) {
1287 err.message = ''
1288 + err.message
1289 + ' at template line ' + line
1290 + ', column ' + column + '\n\n'
1291 + 'Context: \n'
1292 + error.context(this.inputText, line, column, '\n')
1293 + '\n';
1294
1295 return err;
1296}
1297
1298Parser.prototype.write = function(tokens) {
1299 if (!Array.isArray(tokens)) tokens = [tokens];
1300 this.inputText += tokens.map(function(tok) { return tok.val; }).join('');
1301 this.tokens.unshift.apply(this.tokens, tokens.reverse());
1302}
1303
1304Parser.prototype.read = function() {
1305 if (!this.tokens.length && !this.deferredTokens.length) return null;
1306
1307 if (!this.node) {
1308 this.openNode(new ProgramNode());
1309 this.openNode(new MarkupNode(), this.node.body);
1310 this.node._finishedOpen = true;
1311 this.node.name = 'text';
1312 updateLoc(this.node, { line: 0, chr: 0 })
1313 this.openNode(new MarkupContentNode(), this.node.values);
1314 updateLoc(this.node, { line: 0, chr: 0 })
1315 }
1316
1317 var curr = this.deferredTokens.pop() || this.tokens.pop();
1318
1319 // To find this value we must search through both deferred and
1320 // non-deferred tokens, since there could be more than just 3
1321 // deferred tokens.
1322 // nextNonWhitespaceOrNewline
1323 var nnwon = null;
1324
1325 for (var i = this.deferredTokens.length-1; i >= 0; i--) {
1326 if (
1327 nnwon
1328 && nnwon.type !== tks.WHITESPACE
1329 && nnwon.type !== tks.NEWLINE
1330 ) break;
1331 nnwon = this.deferredTokens[i];
1332 }
1333
1334 for (var i = this.tokens.length-1; i >= 0; i--) {
1335 if (
1336 nnwon
1337 && nnwon.type !== tks.WHITESPACE
1338 && nnwon.type !== tks.NEWLINE
1339 ) break;
1340 nnwon = this.tokens[i];
1341 }
1342
1343 var next = this.deferredTokens.pop() || this.tokens.pop();
1344 var ahead = this.deferredTokens.pop() || this.tokens.pop();
1345
1346 var dispatch = 'continue' + this.node.constructor.name;
1347
1348 this.lg('Read: %s', dispatch);
1349 this.lg(' curr %s', curr);
1350 this.lg(' next %s', next);
1351 this.lg(' ahead %s', ahead);
1352 this.lg(' nnwon %s', nnwon);
1353
1354 if (curr._considerEscaped) {
1355 this.lg(' Previous token was marked as escaping');
1356 }
1357
1358 var consumed = this[dispatch](this.node, curr, next, ahead, nnwon);
1359
1360 if (ahead) {
1361 // ahead may be undefined when about to run out of tokens.
1362 this.deferredTokens.push(ahead);
1363 }
1364
1365 if (next) {
1366 // Next may be undefined when about to run out of tokens.
1367 this.deferredTokens.push(next);
1368 }
1369
1370 if (!consumed) {
1371 this.lg('Deferring curr %s', curr);
1372 this.deferredTokens.push(curr);
1373 } else {
1374
1375 if (curr.type !== tks.WHITESPACE) {
1376 this.lg('set previousNonWhitespace %s', curr);
1377 this.previousNonWhitespace = curr;
1378 }
1379
1380 // Poor man's ASI.
1381 if (curr.type === tks.NEWLINE) {
1382 this.lg('set previousNonWhitespace %s', null);
1383 this.previousNonWhitespace = null;
1384 }
1385
1386 if (!curr._considerEscaped && curr.type === tks.BACKSLASH) {
1387 next._considerEscaped = true;
1388 }
1389 }
1390}
1391
1392Parser.prototype.checkStack = function() {
1393 // Throw if something is unclosed that should be.
1394 var i = this.stack.length-1;
1395 var node;
1396 var msg;
1397 // A full AST is always:
1398 // Program, Markup, MarkupContent, ...
1399 while(i >= 2) {
1400 node = this.stack[i];
1401 if (node.endOk && !node.endOk()) {
1402 // Attempt to make the error readable
1403 delete node.values;
1404 msg = 'Found unclosed ' + node.type;
1405 var err = new Error(msg);
1406 err.name = 'UnclosedNodeError';
1407 throw this.decorateError(
1408 err,
1409 node.startloc.line,
1410 node.startloc.column);
1411 }
1412 i--;
1413 }
1414}
1415
1416// This is purely a utility for debugging, to more easily inspect
1417// what happened while parsing something.
1418Parser.prototype.flag = function(node, name, value) {
1419 var printVal = (value && typeof value === 'object')
1420 ? value.type
1421 : value;
1422 this.lg('Flag %s on node %s was %s now %s',
1423 name, node.type, node[name], printVal);
1424 node[name] = value;
1425}
1426
1427Parser.prototype.dumpAST = function() {
1428 if (!this.stack.length) {
1429 var msg = 'No AST to dump.';
1430 throw new Error(msg);
1431 }
1432
1433 return JSON.stringify(this.stack[0], null, ' ');
1434}
1435
1436Parser.prototype.openNode = function(node, opt_insertArr) {
1437 this.stack.push(node);
1438 this.lg('Opened node %s from %s',
1439 node.type, (this.node ? this.node.type : null));
1440 this.node = node;
1441
1442 if (opt_insertArr) {
1443 opt_insertArr.push(node);
1444 }
1445
1446 return node;
1447}
1448
1449Parser.prototype.closeNode = function(node) {
1450 var toClose = this.stack[this.stack.length-1];
1451 if (node !== toClose) {
1452 var msg = 'InvalidCloseAction: '
1453 + 'Expected ' + node.type + ' in stack, instead found '
1454 + toClose.type;
1455 throw new Error(msg);
1456 }
1457
1458 this.stack.pop();
1459 var last = this.stack[this.stack.length-1];
1460
1461 this.lg('Closing node %s (%s), returning to node %s',
1462 node.type, node.name, last.type)
1463
1464 this.node = last;
1465}
1466
1467Parser.prototype.continueCommentNode = function(node, curr, next) {
1468 var valueNode = ensureTextNode(node.values);
1469
1470 if (curr.type === tks.AT_STAR_OPEN && !node._waitingForClose) {
1471 this.flag(node, '_waitingForClose', tks.AT_STAR_CLOSE)
1472 updateLoc(node, curr);
1473 return true;
1474 }
1475
1476 if (curr.type === node._waitingForClose) {
1477 this.flag(node, '_waitingForClose', null)
1478 updateLoc(node, curr);
1479 this.closeNode(node);
1480 return true;
1481 }
1482
1483 if (curr.type === tks.DOUBLE_FORWARD_SLASH && !node._waitingForClose){
1484 this.flag(node, '_waitingForClose', tks.NEWLINE);
1485 updateLoc(node, curr);
1486 return true;
1487 }
1488
1489 appendTextValue(valueNode, curr);
1490 return true;
1491}
1492
1493Parser.prototype.continueMarkupNode = function(node, curr, next) {
1494 var valueNode = node.values[node.values.length-1];
1495
1496 if (curr.type === tks.LT_SIGN && !node._finishedOpen) {
1497 updateLoc(node, curr);
1498 return true;
1499 }
1500
1501 if (
1502 !node._finishedOpen
1503 && curr.type !== tks.GT_SIGN
1504 && curr.type !== tks.LT_SIGN
1505 && curr.type !== tks.WHITESPACE
1506 && curr.type !== tks.NEWLINE
1507 && curr.type !== tks.HTML_TAG_VOID_CLOSE
1508 ) {
1509
1510 // Assume tag name
1511
1512 if (
1513 curr.type === tks.AT
1514 && !curr._considerEscaped
1515 && next
1516 && next.type === tks.AT
1517 ) {
1518 next._considerEscaped = true;
1519 return true;
1520 }
1521
1522 if (curr.type === tks.AT && !curr._considerEscaped) {
1523 this.flag(node, 'expression', this.openNode(new ExpressionNode()));
1524 updateLoc(node.expression, curr);
1525 return true;
1526 }
1527
1528 node.name = node.name
1529 ? node.name + curr.val
1530 : curr.val;
1531 updateLoc(node, curr);
1532 return true;
1533 }
1534
1535 if (curr.type === tks.GT_SIGN && !node._waitingForFinishedClose) {
1536 this.flag(node, '_finishedOpen', true);
1537
1538 if (MarkupNode.isVoid(node.name)) {
1539 this.flag(node, 'isVoid', true);
1540 this.closeNode(node);
1541 updateLoc(node, curr);
1542 } else {
1543 valueNode = this.openNode(new MarkupContentNode(), node.values);
1544 updateLoc(valueNode, curr);
1545 }
1546
1547 return true;
1548 }
1549
1550 if (curr.type === tks.GT_SIGN && node._waitingForFinishedClose) {
1551 this.flag(node, '_waitingForFinishedClose', false);
1552 this.closeNode(node);
1553 updateLoc(node, curr);
1554 return true;
1555 }
1556
1557 // </VOID
1558 if (
1559 curr.type === tks.HTML_TAG_CLOSE
1560 && next
1561 && next.type === tks.IDENTIFIER
1562 && MarkupNode.isVoid(next.val)
1563 ) {
1564 throw newUnexpectedClosingTagError(this, curr, curr.val + next.val);
1565 }
1566
1567 // </
1568 if (curr.type === tks.HTML_TAG_CLOSE) {
1569 this.flag(node, '_waitingForFinishedClose', true);
1570 this.flag(node, 'isClosed', true);
1571 return true;
1572 }
1573
1574 // -->
1575 if (curr.type === tks.HTML_COMMENT_CLOSE) {
1576 this.flag(node, '_waitingForFinishedClose', false);
1577 this.closeNode(node);
1578 return false;
1579 }
1580
1581 if (curr.type === tks.HTML_TAG_VOID_CLOSE) {
1582 this.closeNode(node);
1583 this.flag(node, 'isVoid', true);
1584 this.flag(node, 'voidClosed', true);
1585 this.flag(node, 'isClosed', true);
1586 updateLoc(node, curr);
1587 return true;
1588 }
1589
1590 if (node._waitingForFinishedClose) {
1591 this.lg('Ignoring %s while waiting for closing GT_SIGN',
1592 curr);
1593 return true;
1594 }
1595
1596 if (
1597 (curr.type === tks.WHITESPACE || curr.type === tks.NEWLINE)
1598 && !node._finishedOpen
1599 && next.type !== tks.HTML_TAG_VOID_CLOSE
1600 && next.type !== tks.GT_SIGN
1601 && next.type !== tks.NEWLINE
1602 && next.type !== tks.WHITESPACE
1603 ) {
1604 // enter attribute
1605 valueNode = this.openNode(new MarkupAttributeNode(), node.attributes);
1606 updateLoc(valueNode, curr);
1607 return true;
1608 }
1609
1610 // Whitespace between attributes should be ignored.
1611 if (
1612 (curr.type === tks.WHITESPACE || curr.type === tks.NEWLINE)
1613 && !node._finishedOpen
1614 ) {
1615 updateLoc(node, curr);
1616 return true;
1617 }
1618
1619 // Can't really have non-markupcontent within markup, so implicitly open
1620 // a node. #68.
1621 if (node._finishedOpen) {
1622 valueNode = this.openNode(new MarkupContentNode(), this.node.values);
1623 updateLoc(valueNode, curr);
1624 return false; // defer
1625 }
1626
1627 // Default
1628
1629 //valueNode = ensureTextNode(node.values);
1630 //appendTextValue(valueNode, curr);
1631 //return true;
1632}
1633
1634Parser.prototype.continueMarkupAttributeNode = function(node, curr, next) {
1635
1636 var valueNode;
1637
1638 if (
1639 curr.type === tks.AT
1640 && !curr._considerEscaped
1641 && next
1642 && next.type === tks.AT
1643 ) {
1644 next._considerEscaped = true;
1645 return true;
1646 }
1647
1648 if (curr.type === tks.AT && !curr._considerEscaped) {
1649 // To expression
1650
1651 valueNode = this.openNode(new ExpressionNode(), !node._finishedLeft
1652 ? node.left
1653 : node.right);
1654
1655 updateLoc(valueNode, curr);
1656 return true;
1657 }
1658
1659 // End of left, value only
1660 if (
1661 !node._expectRight
1662 && (curr.type === tks.WHITESPACE
1663 || curr.type === tks.GT_SIGN
1664 || curr.type === tks.HTML_TAG_VOID_CLOSE)
1665 ) {
1666 this.flag(node, '_finishedLeft', true);
1667 updateLoc(node, curr);
1668 this.closeNode(node);
1669 return false; // defer
1670 }
1671
1672 // End of left.
1673 if (curr.type === tks.EQUAL_SIGN && !node._finishedLeft) {
1674 this.flag(node, '_finishedLeft', true);
1675 this.flag(node, '_expectRight', true);
1676 return true;
1677 }
1678
1679 // Beginning of quoted value.
1680 if (
1681 node._expectRight
1682 && !node.rightIsQuoted
1683 && (curr.type === tks.DOUBLE_QUOTE
1684 || curr.type === tks.SINGLE_QUOTE)
1685 ) {
1686 this.flag(node, 'rightIsQuoted', curr.val);
1687 return true;
1688 }
1689
1690 // End of quoted value.
1691 if (node.rightIsQuoted === curr.val) {
1692 updateLoc(node, curr);
1693 this.closeNode(node);
1694 return true;
1695 }
1696
1697 // Default
1698
1699 if (!node._finishedLeft) {
1700 valueNode = ensureTextNode(node.left);
1701 } else {
1702 valueNode = ensureTextNode(node.right);
1703 }
1704
1705 appendTextValue(valueNode, curr);
1706 return true;
1707}
1708
1709Parser.prototype.continueMarkupContentNode = function(node, curr, next, ahead) {
1710 var valueNode = ensureTextNode(node.values);
1711
1712 if (curr.type === tks.HTML_COMMENT_OPEN) {
1713 valueNode = this.openNode(new MarkupCommentNode(), node.values);
1714 updateLoc(valueNode, curr);
1715 return false;
1716 }
1717
1718 if (curr.type === tks.HTML_COMMENT_CLOSE) {
1719 updateLoc(node, curr);
1720 this.closeNode(node);
1721 return false;
1722 }
1723
1724 if (curr.type === tks.AT_COLON && !curr._considerEscaped) {
1725 this.flag(node, '_waitingForNewline', true);
1726 updateLoc(valueNode, curr);
1727 return true;
1728 }
1729
1730 if (curr.type === tks.NEWLINE && node._waitingForNewline === true) {
1731 this.flag(node, '_waitingForNewline', false);
1732 appendTextValue(valueNode, curr);
1733 updateLoc(node, curr);
1734 this.closeNode(node);
1735 return true;
1736 }
1737
1738 if (
1739 curr.type === tks.AT
1740 && !curr._considerEscaped
1741 && next.type === tks.BRACE_OPEN
1742 ) {
1743 valueNode = this.openNode(new BlockNode(), node.values);
1744 updateLoc(valueNode, curr);
1745 return true;
1746 }
1747
1748 if (
1749 curr.type === tks.AT
1750 && !curr._considerEscaped
1751 && (next.type === tks.BLOCK_KEYWORD
1752 || next.type === tks.FUNCTION)
1753 ) {
1754 valueNode = this.openNode(new BlockNode(), node.values);
1755 updateLoc(valueNode, curr);
1756 return true;
1757 }
1758
1759 // Mark @@: or @@ as escaped.
1760 if (
1761 curr.type === tks.AT
1762 && !curr._considerEscaped
1763 && next
1764 && (
1765 next.type === tks.AT_COLON
1766 || next.type === tks.AT
1767 || next.type === tks.AT_STAR_OPEN
1768 )
1769 ) {
1770 next._considerEscaped = true;
1771 return true;
1772 }
1773
1774 // @something
1775 if (curr.type === tks.AT && !curr._considerEscaped) {
1776 valueNode = this.openNode(new ExpressionNode(), node.values);
1777 updateLoc(valueNode, curr);
1778 return true;
1779 }
1780
1781 if (curr.type === tks.AT_STAR_OPEN && !curr._considerEscaped) {
1782 this.openNode(new CommentNode(), node.values);
1783 return false;
1784 }
1785
1786 var parent = this.stack[this.stack.length-2];
1787
1788 // If this MarkupContent is the direct child of a block, it has no way to
1789 // know when to close. So in this case it should assume a } means it's
1790 // done. Or if it finds a closing html tag, of course.
1791 if (
1792 curr.type === tks.HTML_TAG_CLOSE
1793 || (curr.type === tks.BRACE_CLOSE
1794 && parent && parent.type === 'VashBlock')
1795 ) {
1796 this.closeNode(node);
1797 updateLoc(node, curr);
1798 return false;
1799 }
1800
1801 if (
1802 curr.type === tks.LT_SIGN
1803 && next
1804 && (
1805 // If next is an IDENTIFIER, then try to ensure that it's likely an HTML
1806 // tag, which really can only be something like:
1807 // <identifier>
1808 // <identifer morestuff (whitespace)
1809 // <identifier\n
1810 // <identifier@
1811 // <identifier-
1812 // <identifier:identifier // XML namespaces etc etc
1813 (next.type === tks.IDENTIFIER
1814 && ahead
1815 && (
1816 ahead.type === tks.GT_SIGN
1817 || ahead.type === tks.WHITESPACE
1818 || ahead.type === tks.NEWLINE
1819 || ahead.type === tks.AT
1820 || ahead.type === tks.UNARY_OPERATOR
1821 || ahead.type === tks.COLON
1822 )
1823 )
1824 || next.type === tks.AT)
1825 ) {
1826 // TODO: possibly check for same tag name, and if HTML5 incompatible,
1827 // such as p within p, then close current.
1828 valueNode = this.openNode(new MarkupNode(), node.values);
1829 updateLoc(valueNode, curr);
1830 return false;
1831 }
1832
1833 // Ignore whitespace if the direct parent is a block. This is for backwards
1834 // compatibility with { @what() }, where the ' ' between ) and } should not
1835 // be included as content. This rule should not be followed if the
1836 // whitespace is contained within an @: escape or within favorText mode.
1837 if (
1838 curr.type === tks.WHITESPACE
1839 && !node._waitingForNewline
1840 && !this.opts.favorText
1841 && parent
1842 && parent.type === 'VashBlock'
1843 ) {
1844 return true;
1845 }
1846
1847 appendTextValue(valueNode, curr);
1848 return true;
1849}
1850
1851Parser.prototype.continueMarkupCommentNode = function(node, curr, next) {
1852 var valueNode = node.values[node.values.length-1];
1853
1854 if (curr.type === tks.HTML_COMMENT_OPEN) {
1855 this.flag(node, '_finishedOpen', true);
1856 this.flag(node, '_waitingForClose', tks.HTML_COMMENT_CLOSE);
1857 updateLoc(node, curr);
1858 valueNode = this.openNode(new MarkupContentNode(), node.values);
1859 return true;
1860 }
1861
1862 if (curr.type === tks.HTML_COMMENT_CLOSE && node._finishedOpen) {
1863 this.flag(node, '_waitingForClose', null);
1864 updateLoc(node, curr);
1865 this.closeNode(node);
1866 return true;
1867 }
1868
1869 valueNode = ensureTextNode(node.values);
1870 appendTextValue(valueNode, curr);
1871 return true;
1872}
1873
1874Parser.prototype.continueExpressionNode = function(node, curr, next) {
1875 var valueNode = node.values[node.values.length-1];
1876 var pnw = this.previousNonWhitespace;
1877
1878 if (
1879 curr.type === tks.AT
1880 && next.type === tks.HARD_PAREN_OPEN
1881 ) {
1882 // LEGACY: @[], which means a legacy escape to content.
1883 updateLoc(node, curr);
1884 this.closeNode(node);
1885 return true;
1886 }
1887
1888 if (curr.type === tks.PAREN_OPEN) {
1889 this.openNode(new ExplicitExpressionNode(), node.values);
1890 return false;
1891 }
1892
1893 if (
1894 curr.type === tks.HARD_PAREN_OPEN
1895 && node.values[0]
1896 && node.values[0].type === 'VashExplicitExpression'
1897 ) {
1898 // @()[0], hard parens should be content
1899 updateLoc(node, curr);
1900 this.closeNode(node);
1901 return false;
1902 }
1903
1904 if (
1905 curr.type === tks.HARD_PAREN_OPEN
1906 && next.type === tks.HARD_PAREN_CLOSE
1907 ) {
1908 // [], empty index should be content (php forms...)
1909 updateLoc(node, curr);
1910 this.closeNode(node);
1911 return false;
1912 }
1913
1914 if (curr.type === tks.HARD_PAREN_OPEN) {
1915 this.openNode(new IndexExpressionNode(), node.values);
1916 return false;
1917 }
1918
1919 if (
1920 curr.type === tks.FORWARD_SLASH
1921 && pnw
1922 && pnw.type === tks.AT
1923 ) {
1924 this.openNode(new RegexNode(), node.values)
1925 return false;
1926 }
1927
1928 // Default
1929 // Consume only specific cases, otherwise close.
1930
1931 if (curr.type === tks.PERIOD && next && next.type === tks.IDENTIFIER) {
1932 valueNode = ensureTextNode(node.values);
1933 appendTextValue(valueNode, curr);
1934 return true;
1935 }
1936
1937 if (curr.type === tks.IDENTIFIER) {
1938
1939 if (node.values.length > 0 && valueNode && valueNode.type !== 'VashText') {
1940 // Assume we just ended an explicit expression.
1941 this.closeNode(node);
1942 return false;
1943 }
1944
1945 valueNode = ensureTextNode(node.values);
1946 appendTextValue(valueNode, curr);
1947 return true;
1948 } else {
1949 this.closeNode(node);
1950 return false;
1951 }
1952}
1953
1954Parser.prototype.continueExplicitExpressionNode = function(node, curr, next) {
1955
1956 var valueNode = node.values[node.values.length-1];
1957
1958 if (
1959 node.values.length === 0
1960 && (curr.type === tks.AT || curr.type === tks.PAREN_OPEN)
1961 ) {
1962 // This is the beginning of the explicit (mark as consumed)
1963 this.flag(node, '_waitingForParenClose', true);
1964 updateLoc(node, curr);
1965 return true;
1966 }
1967
1968 if (curr.type === tks.PAREN_OPEN && !node._waitingForEndQuote) {
1969 // New explicit expression
1970 valueNode = this.openNode(new ExplicitExpressionNode(), node.values);
1971 updateLoc(valueNode, curr);
1972 // And do nothing with the token (mark as consumed)
1973 return true;
1974 }
1975
1976 if (curr.type === tks.PAREN_CLOSE && !node._waitingForEndQuote) {
1977 // Close current explicit expression
1978 this.flag(node, '_waitingForParenClose', false);
1979 updateLoc(node, curr);
1980 this.closeNode(node);
1981 // And do nothing with the token (mark as consumed)
1982 return true;
1983 }
1984
1985 if (curr.type === tks.FUNCTION && !node._waitingForEndQuote) {
1986 valueNode = this.openNode(new BlockNode(), node.values);
1987 updateLoc(valueNode, curr);
1988 return false;
1989 }
1990
1991 if (
1992 curr.type === tks.LT_SIGN
1993 && next.type === tks.IDENTIFIER
1994 && !node._waitingForEndQuote
1995 ) {
1996 // Markup within expression
1997 valueNode = this.openNode(new MarkupNode(), node.values);
1998 updateLoc(valueNode, curr);
1999 return false;
2000 }
2001
2002 var pnw = this.previousNonWhitespace;
2003
2004 if (
2005 curr.type === tks.FORWARD_SLASH
2006 && !node._waitingForEndQuote
2007 && pnw
2008 && pnw.type !== tks.IDENTIFIER
2009 && pnw.type !== tks.NUMERAL
2010 && pnw.type !== tks.PAREN_CLOSE
2011 ) {
2012 valueNode = this.openNode(new RegexNode(), node.values);
2013 updateLoc(valueNode, curr);
2014 return false;
2015 }
2016
2017 // Default
2018 valueNode = ensureTextNode(node.values);
2019
2020 if (
2021 !node._waitingForEndQuote
2022 && (curr.type === tks.SINGLE_QUOTE || curr.type === tks.DOUBLE_QUOTE)
2023 ) {
2024 this.flag(node, '_waitingForEndQuote', curr.val);
2025 appendTextValue(valueNode, curr);
2026 return true;
2027 }
2028
2029 if (
2030 curr.val === node._waitingForEndQuote
2031 && !curr._considerEscaped
2032 ) {
2033 this.flag(node, '_waitingForEndQuote', null);
2034 appendTextValue(valueNode, curr);
2035 return true;
2036 }
2037
2038 appendTextValue(valueNode, curr);
2039 return true;
2040}
2041
2042Parser.prototype.continueRegexNode = function(node, curr, next) {
2043 var valueNode = ensureTextNode(node.values);
2044
2045 if (
2046 curr.type === tks.FORWARD_SLASH
2047 && !node._waitingForForwardSlash
2048 && !curr._considerEscaped
2049 ) {
2050 // Start of regex.
2051 this.flag(node, '_waitingForForwardSlash', true);
2052 appendTextValue(valueNode, curr);
2053 return true;
2054 }
2055
2056 if (
2057 curr.type === tks.FORWARD_SLASH
2058 && node._waitingForForwardSlash
2059 && !curr._considerEscaped
2060 ) {
2061 // "End" of regex.
2062 this.flag(node, '_waitingForForwardSlash', null);
2063 this.flag(node, '_waitingForFlags', true);
2064 appendTextValue(valueNode, curr);
2065 return true;
2066 }
2067
2068 if (node._waitingForFlags) {
2069 this.flag(node, '_waitingForFlags', null);
2070 this.closeNode(node);
2071
2072 if (curr.type === tks.IDENTIFIER) {
2073 appendTextValue(valueNode, curr);
2074 return true;
2075 } else {
2076 return false;
2077 }
2078 }
2079
2080 if (
2081 curr.type === tks.BACKSLASH
2082 && !curr._considerEscaped
2083 ) {
2084 next._considerEscaped = true;
2085 }
2086
2087 appendTextValue(valueNode, curr);
2088 return true;
2089}
2090
2091Parser.prototype.continueBlockNode = function(node, curr, next, ahead, nnwon) {
2092
2093 var valueNode = node.values[node.values.length-1];
2094
2095 if (curr.type === tks.AT_STAR_OPEN) {
2096 this.openNode(new CommentNode(), node.body);
2097 return false;
2098 }
2099
2100 if (curr.type === tks.DOUBLE_FORWARD_SLASH && !node._waitingForEndQuote) {
2101 this.openNode(new CommentNode(), node.body);
2102 return false;
2103 }
2104
2105 if (
2106 curr.type === tks.AT_COLON
2107 && (!node.hasBraces || node._reachedOpenBrace)
2108 ) {
2109 valueNode = this.openNode(new MarkupContentNode(), node.values);
2110 return false;
2111 }
2112
2113 if (
2114 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
2115 && !node._reachedOpenBrace
2116 && !node.keyword
2117 ) {
2118 this.flag(node, 'keyword', curr.val);
2119 return true;
2120 }
2121
2122 if (
2123 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
2124 && !node._reachedOpenBrace
2125 ) {
2126 // Assume something like if (test) expressionstatement;
2127 this.flag(node, 'hasBraces', false);
2128 valueNode = this.openNode(new BlockNode(), node.values);
2129 updateLoc(valueNode, curr);
2130 return false;
2131 }
2132
2133 if (
2134 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
2135 && !node._reachedCloseBrace
2136 && node.hasBraces
2137 && !node._waitingForEndQuote
2138 ) {
2139 valueNode = this.openNode(new BlockNode(), node.values);
2140 updateLoc(valueNode, curr);
2141 return false;
2142 }
2143
2144 if (
2145 (curr.type === tks.BLOCK_KEYWORD || curr.type === tks.FUNCTION)
2146 && node._reachedCloseBrace
2147 && !node._waitingForEndQuote
2148 ) {
2149 valueNode = this.openNode(new BlockNode(), node.tail);
2150 updateLoc(valueNode, curr);
2151 return false;
2152 }
2153
2154 if (
2155 curr.type === tks.BRACE_OPEN
2156 && !node._reachedOpenBrace
2157 && !node._waitingForEndQuote
2158 ) {
2159 this.flag(node, '_reachedOpenBrace', true);
2160 this.flag(node, 'hasBraces', true);
2161 if (this.opts.favorText) {
2162 valueNode = this.openNode(new MarkupContentNode(), node.values);
2163 updateLoc(valueNode, curr);
2164 }
2165 return true;
2166 }
2167
2168 if (
2169 curr.type === tks.BRACE_OPEN
2170 && !node._waitingForEndQuote
2171 ) {
2172 valueNode = this.openNode(new BlockNode(), node.values);
2173 updateLoc(valueNode, curr);
2174 return false;
2175 }
2176
2177 if (
2178 curr.type === tks.BRACE_CLOSE
2179 && node.hasBraces
2180 && !node._reachedCloseBrace
2181 && !node._waitingForEndQuote
2182 ) {
2183 updateLoc(node, curr);
2184 this.flag(node, '_reachedCloseBrace', true);
2185
2186 // Try to leave whitespace where it belongs, and allow `else {` to
2187 // be continued as the tail of this block.
2188 if (
2189 nnwon
2190 && nnwon.type !== tks.BLOCK_KEYWORD
2191 ) {
2192 this.closeNode(node);
2193 }
2194
2195 return true;
2196 }
2197
2198 if (
2199 curr.type === tks.BRACE_CLOSE
2200 && !node.hasBraces
2201 ) {
2202 // Probably something like:
2203 // @{ if() <span></span> }
2204 this.closeNode(node);
2205 updateLoc(node, curr);
2206 return false;
2207 }
2208
2209 if (
2210 curr.type === tks.LT_SIGN
2211 && (next.type === tks.AT || next.type === tks.IDENTIFIER)
2212 && !node._waitingForEndQuote
2213 && node._reachedCloseBrace
2214 ) {
2215 this.closeNode(node);
2216 updateLoc(node, curr);
2217 return false;
2218 }
2219
2220 if (
2221 curr.type === tks.LT_SIGN
2222 && (next.type === tks.AT || next.type === tks.IDENTIFIER)
2223 && !node._waitingForEndQuote
2224 && !node._reachedCloseBrace
2225 ) {
2226 valueNode = this.openNode(new MarkupNode(), node.values);
2227 updateLoc(valueNode, curr);
2228 return false;
2229 }
2230
2231 if (curr.type === tks.HTML_TAG_CLOSE) {
2232 if (
2233 (node.hasBraces && node._reachedCloseBrace)
2234 || !node._reachedOpenBrace
2235 ) {
2236 updateLoc(node, curr);
2237 this.closeNode(node);
2238 return false;
2239 }
2240
2241 // This is likely an invalid markup configuration, something like:
2242 // @if(bla) { <img></img> }
2243 // where <img> is an implicit void. Try to help the user in this
2244 // specific case.
2245 if (
2246 next
2247 && next.type === tks.IDENTIFIER
2248 && MarkupNode.isVoid(next.val)
2249 ){
2250 throw newUnexpectedClosingTagError(this, curr, curr.val + next.val);
2251 }
2252 }
2253
2254 if (
2255 curr.type === tks.AT
2256 && (next.type === tks.BLOCK_KEYWORD
2257 || next.type === tks.BRACE_OPEN
2258 || next.type === tks.FUNCTION)
2259 ) {
2260 // Backwards compatibility, allowing for @for() { @for() { @{ } } }
2261 valueNode = this.openNode(new BlockNode(), node.values);
2262 updateLoc(valueNode, curr);
2263 // TODO: shouldn't this need a more accurate target (tail, values, head)?
2264 return true;
2265 }
2266
2267 if (
2268 curr.type === tks.AT && next.type === tks.PAREN_OPEN
2269 ) {
2270 // Backwards compatibility, allowing for @for() { @(exp) }
2271 valueNode = this.openNode(new ExpressionNode(), node.values);
2272 updateLoc(valueNode, curr);
2273 return true;
2274 }
2275
2276 var attachmentNode;
2277
2278 if (node._reachedOpenBrace && node._reachedCloseBrace) {
2279 attachmentNode = node.tail;
2280 } else if (!node._reachedOpenBrace) {
2281 attachmentNode = node.head;
2282 } else {
2283 attachmentNode = node.values;
2284 }
2285
2286 valueNode = attachmentNode[attachmentNode.length-1];
2287
2288 if (
2289 curr.type === tks.AT
2290 && next.type === tks.IDENTIFIER
2291 && !node._waitingForEndQuote
2292 ) {
2293
2294 if (node._reachedCloseBrace) {
2295 this.closeNode(node);
2296 return false;
2297 } else {
2298 // something like @for() { @i }
2299 valueNode = this.openNode(new MarkupContentNode(), attachmentNode);
2300 updateLoc(valueNode, curr);
2301 return false;
2302 }
2303 }
2304
2305 if (
2306 curr.type !== tks.BLOCK_KEYWORD
2307 && curr.type !== tks.PAREN_OPEN
2308 && curr.type !== tks.WHITESPACE
2309 && curr.type !== tks.NEWLINE
2310 && node.hasBraces
2311 && node._reachedCloseBrace
2312 ) {
2313 // Handle if (test) { } content
2314 updateLoc(node, curr);
2315 this.closeNode(node);
2316 return false;
2317 }
2318
2319 if (curr.type === tks.PAREN_OPEN) {
2320 valueNode = this.openNode(new ExplicitExpressionNode(), attachmentNode);
2321 updateLoc(valueNode, curr);
2322 return false;
2323 }
2324
2325 valueNode = ensureTextNode(attachmentNode);
2326
2327 if (
2328 curr.val === node._waitingForEndQuote
2329 && !curr._considerEscaped
2330 ) {
2331 this.flag(node, '_waitingForEndQuote', null);
2332 appendTextValue(valueNode, curr);
2333 return true;
2334 }
2335
2336 if (
2337 !node._waitingForEndQuote
2338 && (curr.type === tks.DOUBLE_QUOTE || curr.type === tks.SINGLE_QUOTE)
2339 ) {
2340 this.flag(node, '_waitingForEndQuote', curr.val);
2341 appendTextValue(valueNode, curr);
2342 return true;
2343 }
2344
2345 var pnw = this.previousNonWhitespace;
2346
2347 if (
2348 curr.type === tks.FORWARD_SLASH
2349 && !node._waitingForEndQuote
2350 && pnw
2351 && pnw.type !== tks.IDENTIFIER
2352 && pnw.type !== tks.NUMERAL
2353 ) {
2354 // OH GAWD IT MIGHT BE A REGEX.
2355 valueNode = this.openNode(new RegexNode(), attachmentNode);
2356 updateLoc(valueNode, curr);
2357 return false;
2358 }
2359
2360 appendTextValue(valueNode, curr);
2361 return true;
2362}
2363
2364// These are really only used when continuing on an expression (for now):
2365// @model.what[0]()
2366// And apparently work for array literals...
2367Parser.prototype.continueIndexExpressionNode = function(node, curr, next) {
2368 var valueNode = node.values[node.values.length-1];
2369
2370 if (node._waitingForEndQuote) {
2371 if (curr.val === node._waitingForEndQuote) {
2372 this.flag(node, '_waitingForEndQuote', null);
2373 }
2374
2375 appendTextValue(valueNode, curr);
2376 return true;
2377 }
2378
2379 if (
2380 curr.type === tks.HARD_PAREN_OPEN
2381 && !valueNode
2382 ) {
2383 this.flag(node, '_waitingForHardParenClose', true);
2384 updateLoc(node, curr);
2385 return true;
2386 }
2387
2388 if (curr.type === tks.HARD_PAREN_CLOSE) {
2389 this.flag(node, '_waitingForHardParenClose', false);
2390 this.closeNode(node);
2391 updateLoc(node, curr);
2392 return true;
2393 }
2394
2395 if (curr.type === tks.PAREN_OPEN) {
2396 valueNode = this.openNode(new ExplicitExpressionNode(), node.values);
2397 updateLoc(valueNode, curr);
2398 return false;
2399 }
2400
2401 valueNode = ensureTextNode(node.values);
2402
2403 if (!node._waitingForEndQuote
2404 && (curr.type === tks.DOUBLE_QUOTE
2405 || curr.type === tks.SINGLE_QUOTE)
2406 ) {
2407 this.flag(node, '_waitingForEndQuote', curr.val);
2408 appendTextValue(valueNode, curr);
2409 return true;
2410 }
2411
2412 // Default.
2413
2414 appendTextValue(valueNode, curr);
2415 return true;
2416}
2417
2418function updateLoc(node, token) {
2419 var loc;
2420 loc = new LocationNode();
2421 loc.line = token.line;
2422 loc.column = token.chr;
2423
2424 if (node.startloc === null) {
2425 node.startloc = loc;
2426 }
2427
2428 node.endloc = loc;
2429}
2430
2431function ensureTextNode(valueList) {
2432 var valueNode = valueList[valueList.length-1];
2433
2434 if (!valueNode || valueNode.type !== 'VashText') {
2435 valueNode = new TextNode();
2436 valueList.push(valueNode);
2437 }
2438
2439 return valueNode;
2440}
2441
2442function appendTextValue(textNode, token) {
2443 if (!('value' in textNode)) {
2444 var msg = 'Expected TextNode but found ' + textNode.type
2445 + ' when appending token ' + token;
2446 throw new Error(msg);
2447 }
2448
2449 textNode.value += token.val;
2450 updateLoc(textNode, token);
2451}
2452
2453function newUnexpectedClosingTagError(parser, tok, tagName) {
2454 var err = new Error(''
2455 + 'Found a closing tag for a known void HTML element: '
2456 + tagName + '.');
2457 err.name = 'UnexpectedClosingTagError';
2458 return parser.decorateError(
2459 err,
2460 tok.line,
2461 tok.chr);
2462}
2463
2464
2465},{"./error":3,"./nodes/block":10,"./nodes/comment":11,"./nodes/explicitexpression":12,"./nodes/expression":13,"./nodes/indexexpression":14,"./nodes/location":15,"./nodes/markup":16,"./nodes/markupattribute":17,"./nodes/markupcomment":18,"./nodes/markupcontent":19,"./nodes/program":20,"./nodes/regex":21,"./nodes/text":22,"./nodestuff":23,"./tokens":25,"./util/fn-namer":27,"debug":29}],25:[function(require,module,exports){
2466// The order of these is important, as it is the order in which
2467// they are run against the input string.
2468// They are separated out here to allow for better minification
2469// with the least amount of effort from me. :)
2470
2471// Any function instead of regex is called with the lexer as the
2472// context.
2473
2474// NOTE: this is an array, not an object literal! The () around
2475// the regexps are for the sake of the syntax highlighter in my
2476// editor... sublimetext2
2477
2478var TESTS = [
2479
2480 // A real email address is considerably more complex, and unfortunately
2481 // this complexity makes it impossible to differentiate between an address
2482 // and an AT expression.
2483 //
2484 // Instead, this regex assumes the only valid characters for the user portion
2485 // of the address are alphanumeric, period, and %. This means that a complex email like
2486 // who-something@example.com will be interpreted as an email, but incompletely. `who-`
2487 // will be content, while `something@example.com` will be the email address.
2488 //
2489 // However, this is "Good Enough"© :).
2490 'EMAIL', (/^([a-zA-Z0-9.%]+@[a-zA-Z0-9.\-]+\.(?:[a-z]{2}|co\.uk|com|edu|net|org))\b/)
2491
2492 , 'AT_STAR_OPEN', (/^(@\*)/)
2493 , 'AT_STAR_CLOSE', (/^(\*@)/)
2494
2495
2496 , 'AT_COLON', (/^(@\:)/)
2497 , 'AT', (/^(@)/)
2498
2499
2500 , 'PAREN_OPEN', (/^(\()/)
2501 , 'PAREN_CLOSE', (/^(\))/)
2502
2503
2504 , 'HARD_PAREN_OPEN', (/^(\[)/)
2505 , 'HARD_PAREN_CLOSE', (/^(\])/)
2506
2507
2508 , 'BRACE_OPEN', (/^(\{)/)
2509 , 'BRACE_CLOSE', (/^(\})/)
2510
2511
2512 , 'HTML_TAG_VOID_CLOSE', (/^(\/>)/)
2513 , 'HTML_TAG_CLOSE', (/^(<\/)/)
2514 , 'HTML_COMMENT_OPEN', (/^(<!--+)/)
2515 , 'HTML_COMMENT_CLOSE', (/^(--+>)/)
2516 , 'LT_SIGN', (/^(<)/)
2517 , 'GT_SIGN', (/^(>)/)
2518
2519 , 'ASSIGNMENT_OPERATOR', (/^(\|=|\^=|&=|>>>=|>>=|<<=|-=|\+=|%=|\/=|\*=)\b/) // Also =
2520 , 'EQUALITY_OPERATOR', (/^(===|==|!==|!=)\b/)
2521 , 'BITWISE_SHIFT_OPERATOR', (/^(<<|>>>|>>)/)
2522 , 'UNARY_OPERATOR', (/^(delete\b|typeof\b|void|\+\+|--|\+|-|~|!)/)
2523 , 'RELATIONAL_OPERATOR', (/^(<=|>=|instanceof|in)\b/) // Also <, >
2524 , 'BINARY_LOGICAL_OPERATOR', (/^(&&|\|\|)\b/)
2525 , 'BINARY_BITWISE_OPERATOR', (/^(&|\^|\|)\b/)
2526 , 'NEW_OPERATOR', (/^(new)\b/)
2527 , 'COMMA_OPERATOR', (/^(,)/)
2528
2529 , 'EQUAL_SIGN', (/^(=)/)
2530 , 'COLON', (/^(:)/)
2531 , 'PERIOD', (/^(\.)/)
2532 , 'NEWLINE', function(){
2533 var token = this.scan(/^(\n)/, exports.NEWLINE);
2534 if(token){
2535 this.lineno++;
2536 this.charno = 0;
2537 }
2538 return token;
2539 }
2540 , 'WHITESPACE', (/^([^\S\n]+)/) // http://stackoverflow.com/a/3469155
2541 , 'FUNCTION', (/^(function)(?![\d\w])/)
2542 , 'BLOCK_KEYWORD', (/^(catch|do|else if|else|finally|for|function|goto|if|switch|try|while|with)(?![\d\w])/)
2543 , 'KEYWORD', (/^(break|case|continue|instanceof|return|var)(?![\d\w])/)
2544 , 'IDENTIFIER', (/^([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)/)
2545
2546 , 'DOUBLE_FORWARD_SLASH', (/^(\/\/)/)
2547
2548 , 'FORWARD_SLASH', (/^(\/)/)
2549
2550 , 'BACKSLASH', (/^(\\)/)
2551 , 'EXCLAMATION_POINT', (/^(!)/)
2552 , 'DOUBLE_QUOTE', (/^(\")/)
2553 , 'SINGLE_QUOTE', (/^(\')/)
2554
2555 , 'NUMERAL', (/^([0-9])/)
2556 , 'CONTENT', (/^([^\s])/)
2557
2558];
2559
2560exports.tests = TESTS;
2561
2562// Export all the tokens as constants.
2563for(var i = 0; i < TESTS.length; i += 2) {
2564 exports[TESTS[i]] = TESTS[i];
2565}
2566},{}],26:[function(require,module,exports){
2567module.exports = function(obj) {
2568 // extend works from right to left, using first arg as target
2569 var next, i, p;
2570
2571 for(i = 1; i < arguments.length; i++){
2572 next = arguments[i];
2573
2574 for(p in next){
2575 obj[p] = next[p];
2576 }
2577 }
2578
2579 return obj;
2580}
2581},{}],27:[function(require,module,exports){
2582var lg = require('debug')('vash:fn-namer');
2583var reName = /^function\s+([A-Za-z0-9_]+)\s*\(/;
2584
2585module.exports = function(fn) {
2586 if (fn.name) {
2587 lg('bailing, found .name %s', fn.name);
2588 return fn;
2589 }
2590 var fnstr = fn.toString();
2591 var match = reName.exec(fnstr);
2592 if (!match) {
2593 lg('bailing, could not match within %s', fnstr);
2594 return fn;
2595 }
2596 fn.name = match[1];
2597 lg('set .name as %s', fn.name);
2598 return fn;
2599}
2600},{"debug":29}],28:[function(require,module,exports){
2601
2602},{}],29:[function(require,module,exports){
2603
2604/**
2605 * This is the web browser implementation of `debug()`.
2606 *
2607 * Expose `debug()` as the module.
2608 */
2609
2610exports = module.exports = require('./debug');
2611exports.log = log;
2612exports.formatArgs = formatArgs;
2613exports.save = save;
2614exports.load = load;
2615exports.useColors = useColors;
2616exports.storage = 'undefined' != typeof chrome
2617 && 'undefined' != typeof chrome.storage
2618 ? chrome.storage.local
2619 : localstorage();
2620
2621/**
2622 * Colors.
2623 */
2624
2625exports.colors = [
2626 'lightseagreen',
2627 'forestgreen',
2628 'goldenrod',
2629 'dodgerblue',
2630 'darkorchid',
2631 'crimson'
2632];
2633
2634/**
2635 * Currently only WebKit-based Web Inspectors, Firefox >= v31,
2636 * and the Firebug extension (any Firefox version) are known
2637 * to support "%c" CSS customizations.
2638 *
2639 * TODO: add a `localStorage` variable to explicitly enable/disable colors
2640 */
2641
2642function useColors() {
2643 // is webkit? http://stackoverflow.com/a/16459606/376773
2644 return ('WebkitAppearance' in document.documentElement.style) ||
2645 // is firebug? http://stackoverflow.com/a/398120/376773
2646 (window.console && (console.firebug || (console.exception && console.table))) ||
2647 // is firefox >= v31?
2648 // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
2649 (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
2650}
2651
2652/**
2653 * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
2654 */
2655
2656exports.formatters.j = function(v) {
2657 return JSON.stringify(v);
2658};
2659
2660
2661/**
2662 * Colorize log arguments if enabled.
2663 *
2664 * @api public
2665 */
2666
2667function formatArgs() {
2668 var args = arguments;
2669 var useColors = this.useColors;
2670
2671 args[0] = (useColors ? '%c' : '')
2672 + this.namespace
2673 + (useColors ? ' %c' : ' ')
2674 + args[0]
2675 + (useColors ? '%c ' : ' ')
2676 + '+' + exports.humanize(this.diff);
2677
2678 if (!useColors) return args;
2679
2680 var c = 'color: ' + this.color;
2681 args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
2682
2683 // the final "%c" is somewhat tricky, because there could be other
2684 // arguments passed either before or after the %c, so we need to
2685 // figure out the correct index to insert the CSS into
2686 var index = 0;
2687 var lastC = 0;
2688 args[0].replace(/%[a-z%]/g, function(match) {
2689 if ('%%' === match) return;
2690 index++;
2691 if ('%c' === match) {
2692 // we only are interested in the *last* %c
2693 // (the user may have provided their own)
2694 lastC = index;
2695 }
2696 });
2697
2698 args.splice(lastC, 0, c);
2699 return args;
2700}
2701
2702/**
2703 * Invokes `console.log()` when available.
2704 * No-op when `console.log` is not a "function".
2705 *
2706 * @api public
2707 */
2708
2709function log() {
2710 // this hackery is required for IE8/9, where
2711 // the `console.log` function doesn't have 'apply'
2712 return 'object' === typeof console
2713 && console.log
2714 && Function.prototype.apply.call(console.log, console, arguments);
2715}
2716
2717/**
2718 * Save `namespaces`.
2719 *
2720 * @param {String} namespaces
2721 * @api private
2722 */
2723
2724function save(namespaces) {
2725 try {
2726 if (null == namespaces) {
2727 exports.storage.removeItem('debug');
2728 } else {
2729 exports.storage.debug = namespaces;
2730 }
2731 } catch(e) {}
2732}
2733
2734/**
2735 * Load `namespaces`.
2736 *
2737 * @return {String} returns the previously persisted debug modes
2738 * @api private
2739 */
2740
2741function load() {
2742 var r;
2743 try {
2744 r = exports.storage.debug;
2745 } catch(e) {}
2746 return r;
2747}
2748
2749/**
2750 * Enable namespaces listed in `localStorage.debug` initially.
2751 */
2752
2753exports.enable(load());
2754
2755/**
2756 * Localstorage attempts to return the localstorage.
2757 *
2758 * This is necessary because safari throws
2759 * when a user disables cookies/localstorage
2760 * and you attempt to access it.
2761 *
2762 * @return {LocalStorage}
2763 * @api private
2764 */
2765
2766function localstorage(){
2767 try {
2768 return window.localStorage;
2769 } catch (e) {}
2770}
2771
2772},{"./debug":30}],30:[function(require,module,exports){
2773
2774/**
2775 * This is the common logic for both the Node.js and web browser
2776 * implementations of `debug()`.
2777 *
2778 * Expose `debug()` as the module.
2779 */
2780
2781exports = module.exports = debug;
2782exports.coerce = coerce;
2783exports.disable = disable;
2784exports.enable = enable;
2785exports.enabled = enabled;
2786exports.humanize = require('ms');
2787
2788/**
2789 * The currently active debug mode names, and names to skip.
2790 */
2791
2792exports.names = [];
2793exports.skips = [];
2794
2795/**
2796 * Map of special "%n" handling functions, for the debug "format" argument.
2797 *
2798 * Valid key names are a single, lowercased letter, i.e. "n".
2799 */
2800
2801exports.formatters = {};
2802
2803/**
2804 * Previously assigned color.
2805 */
2806
2807var prevColor = 0;
2808
2809/**
2810 * Previous log timestamp.
2811 */
2812
2813var prevTime;
2814
2815/**
2816 * Select a color.
2817 *
2818 * @return {Number}
2819 * @api private
2820 */
2821
2822function selectColor() {
2823 return exports.colors[prevColor++ % exports.colors.length];
2824}
2825
2826/**
2827 * Create a debugger with the given `namespace`.
2828 *
2829 * @param {String} namespace
2830 * @return {Function}
2831 * @api public
2832 */
2833
2834function debug(namespace) {
2835
2836 // define the `disabled` version
2837 function disabled() {
2838 }
2839 disabled.enabled = false;
2840
2841 // define the `enabled` version
2842 function enabled() {
2843
2844 var self = enabled;
2845
2846 // set `diff` timestamp
2847 var curr = +new Date();
2848 var ms = curr - (prevTime || curr);
2849 self.diff = ms;
2850 self.prev = prevTime;
2851 self.curr = curr;
2852 prevTime = curr;
2853
2854 // add the `color` if not set
2855 if (null == self.useColors) self.useColors = exports.useColors();
2856 if (null == self.color && self.useColors) self.color = selectColor();
2857
2858 var args = Array.prototype.slice.call(arguments);
2859
2860 args[0] = exports.coerce(args[0]);
2861
2862 if ('string' !== typeof args[0]) {
2863 // anything else let's inspect with %o
2864 args = ['%o'].concat(args);
2865 }
2866
2867 // apply any `formatters` transformations
2868 var index = 0;
2869 args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
2870 // if we encounter an escaped % then don't increase the array index
2871 if (match === '%%') return match;
2872 index++;
2873 var formatter = exports.formatters[format];
2874 if ('function' === typeof formatter) {
2875 var val = args[index];
2876 match = formatter.call(self, val);
2877
2878 // now we need to remove `args[index]` since it's inlined in the `format`
2879 args.splice(index, 1);
2880 index--;
2881 }
2882 return match;
2883 });
2884
2885 if ('function' === typeof exports.formatArgs) {
2886 args = exports.formatArgs.apply(self, args);
2887 }
2888 var logFn = enabled.log || exports.log || console.log.bind(console);
2889 logFn.apply(self, args);
2890 }
2891 enabled.enabled = true;
2892
2893 var fn = exports.enabled(namespace) ? enabled : disabled;
2894
2895 fn.namespace = namespace;
2896
2897 return fn;
2898}
2899
2900/**
2901 * Enables a debug mode by namespaces. This can include modes
2902 * separated by a colon and wildcards.
2903 *
2904 * @param {String} namespaces
2905 * @api public
2906 */
2907
2908function enable(namespaces) {
2909 exports.save(namespaces);
2910
2911 var split = (namespaces || '').split(/[\s,]+/);
2912 var len = split.length;
2913
2914 for (var i = 0; i < len; i++) {
2915 if (!split[i]) continue; // ignore empty strings
2916 namespaces = split[i].replace(/\*/g, '.*?');
2917 if (namespaces[0] === '-') {
2918 exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
2919 } else {
2920 exports.names.push(new RegExp('^' + namespaces + '$'));
2921 }
2922 }
2923}
2924
2925/**
2926 * Disable debug output.
2927 *
2928 * @api public
2929 */
2930
2931function disable() {
2932 exports.enable('');
2933}
2934
2935/**
2936 * Returns true if the given mode name is enabled, false otherwise.
2937 *
2938 * @param {String} name
2939 * @return {Boolean}
2940 * @api public
2941 */
2942
2943function enabled(name) {
2944 var i, len;
2945 for (i = 0, len = exports.skips.length; i < len; i++) {
2946 if (exports.skips[i].test(name)) {
2947 return false;
2948 }
2949 }
2950 for (i = 0, len = exports.names.length; i < len; i++) {
2951 if (exports.names[i].test(name)) {
2952 return true;
2953 }
2954 }
2955 return false;
2956}
2957
2958/**
2959 * Coerce `val`.
2960 *
2961 * @param {Mixed} val
2962 * @return {Mixed}
2963 * @api private
2964 */
2965
2966function coerce(val) {
2967 if (val instanceof Error) return val.stack || val.message;
2968 return val;
2969}
2970
2971},{"ms":31}],31:[function(require,module,exports){
2972/**
2973 * Helpers.
2974 */
2975
2976var s = 1000;
2977var m = s * 60;
2978var h = m * 60;
2979var d = h * 24;
2980var y = d * 365.25;
2981
2982/**
2983 * Parse or format the given `val`.
2984 *
2985 * Options:
2986 *
2987 * - `long` verbose formatting [false]
2988 *
2989 * @param {String|Number} val
2990 * @param {Object} options
2991 * @return {String|Number}
2992 * @api public
2993 */
2994
2995module.exports = function(val, options){
2996 options = options || {};
2997 if ('string' == typeof val) return parse(val);
2998 return options.long
2999 ? long(val)
3000 : short(val);
3001};
3002
3003/**
3004 * Parse the given `str` and return milliseconds.
3005 *
3006 * @param {String} str
3007 * @return {Number}
3008 * @api private
3009 */
3010
3011function parse(str) {
3012 str = '' + str;
3013 if (str.length > 10000) return;
3014 var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);
3015 if (!match) return;
3016 var n = parseFloat(match[1]);
3017 var type = (match[2] || 'ms').toLowerCase();
3018 switch (type) {
3019 case 'years':
3020 case 'year':
3021 case 'yrs':
3022 case 'yr':
3023 case 'y':
3024 return n * y;
3025 case 'days':
3026 case 'day':
3027 case 'd':
3028 return n * d;
3029 case 'hours':
3030 case 'hour':
3031 case 'hrs':
3032 case 'hr':
3033 case 'h':
3034 return n * h;
3035 case 'minutes':
3036 case 'minute':
3037 case 'mins':
3038 case 'min':
3039 case 'm':
3040 return n * m;
3041 case 'seconds':
3042 case 'second':
3043 case 'secs':
3044 case 'sec':
3045 case 's':
3046 return n * s;
3047 case 'milliseconds':
3048 case 'millisecond':
3049 case 'msecs':
3050 case 'msec':
3051 case 'ms':
3052 return n;
3053 }
3054}
3055
3056/**
3057 * Short format for `ms`.
3058 *
3059 * @param {Number} ms
3060 * @return {String}
3061 * @api private
3062 */
3063
3064function short(ms) {
3065 if (ms >= d) return Math.round(ms / d) + 'd';
3066 if (ms >= h) return Math.round(ms / h) + 'h';
3067 if (ms >= m) return Math.round(ms / m) + 'm';
3068 if (ms >= s) return Math.round(ms / s) + 's';
3069 return ms + 'ms';
3070}
3071
3072/**
3073 * Long format for `ms`.
3074 *
3075 * @param {Number} ms
3076 * @return {String}
3077 * @api private
3078 */
3079
3080function long(ms) {
3081 return plural(ms, d, 'day')
3082 || plural(ms, h, 'hour')
3083 || plural(ms, m, 'minute')
3084 || plural(ms, s, 'second')
3085 || ms + ' ms';
3086}
3087
3088/**
3089 * Pluralization helper.
3090 */
3091
3092function plural(ms, n, name) {
3093 if (ms < n) return;
3094 if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
3095 return Math.ceil(ms / n) + ' ' + name + 's';
3096}
3097
3098},{}],32:[function(require,module,exports){
3099(function (process){
3100// Copyright Joyent, Inc. and other Node contributors.
3101//
3102// Permission is hereby granted, free of charge, to any person obtaining a
3103// copy of this software and associated documentation files (the
3104// "Software"), to deal in the Software without restriction, including
3105// without limitation the rights to use, copy, modify, merge, publish,
3106// distribute, sublicense, and/or sell copies of the Software, and to permit
3107// persons to whom the Software is furnished to do so, subject to the
3108// following conditions:
3109//
3110// The above copyright notice and this permission notice shall be included
3111// in all copies or substantial portions of the Software.
3112//
3113// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
3114// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3115// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
3116// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
3117// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
3118// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
3119// USE OR OTHER DEALINGS IN THE SOFTWARE.
3120
3121// resolves . and .. elements in a path array with directory names there
3122// must be no slashes, empty elements, or device names (c:\) in the array
3123// (so also no leading and trailing slashes - it does not distinguish
3124// relative and absolute paths)
3125function normalizeArray(parts, allowAboveRoot) {
3126 // if the path tries to go above the root, `up` ends up > 0
3127 var up = 0;
3128 for (var i = parts.length - 1; i >= 0; i--) {
3129 var last = parts[i];
3130 if (last === '.') {
3131 parts.splice(i, 1);
3132 } else if (last === '..') {
3133 parts.splice(i, 1);
3134 up++;
3135 } else if (up) {
3136 parts.splice(i, 1);
3137 up--;
3138 }
3139 }
3140
3141 // if the path is allowed to go above the root, restore leading ..s
3142 if (allowAboveRoot) {
3143 for (; up--; up) {
3144 parts.unshift('..');
3145 }
3146 }
3147
3148 return parts;
3149}
3150
3151// Split a filename into [root, dir, basename, ext], unix version
3152// 'root' is just a slash, or nothing.
3153var splitPathRe =
3154 /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
3155var splitPath = function(filename) {
3156 return splitPathRe.exec(filename).slice(1);
3157};
3158
3159// path.resolve([from ...], to)
3160// posix version
3161exports.resolve = function() {
3162 var resolvedPath = '',
3163 resolvedAbsolute = false;
3164
3165 for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
3166 var path = (i >= 0) ? arguments[i] : process.cwd();
3167
3168 // Skip empty and invalid entries
3169 if (typeof path !== 'string') {
3170 throw new TypeError('Arguments to path.resolve must be strings');
3171 } else if (!path) {
3172 continue;
3173 }
3174
3175 resolvedPath = path + '/' + resolvedPath;
3176 resolvedAbsolute = path.charAt(0) === '/';
3177 }
3178
3179 // At this point the path should be resolved to a full absolute path, but
3180 // handle relative paths to be safe (might happen when process.cwd() fails)
3181
3182 // Normalize the path
3183 resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
3184 return !!p;
3185 }), !resolvedAbsolute).join('/');
3186
3187 return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
3188};
3189
3190// path.normalize(path)
3191// posix version
3192exports.normalize = function(path) {
3193 var isAbsolute = exports.isAbsolute(path),
3194 trailingSlash = substr(path, -1) === '/';
3195
3196 // Normalize the path
3197 path = normalizeArray(filter(path.split('/'), function(p) {
3198 return !!p;
3199 }), !isAbsolute).join('/');
3200
3201 if (!path && !isAbsolute) {
3202 path = '.';
3203 }
3204 if (path && trailingSlash) {
3205 path += '/';
3206 }
3207
3208 return (isAbsolute ? '/' : '') + path;
3209};
3210
3211// posix version
3212exports.isAbsolute = function(path) {
3213 return path.charAt(0) === '/';
3214};
3215
3216// posix version
3217exports.join = function() {
3218 var paths = Array.prototype.slice.call(arguments, 0);
3219 return exports.normalize(filter(paths, function(p, index) {
3220 if (typeof p !== 'string') {
3221 throw new TypeError('Arguments to path.join must be strings');
3222 }
3223 return p;
3224 }).join('/'));
3225};
3226
3227
3228// path.relative(from, to)
3229// posix version
3230exports.relative = function(from, to) {
3231 from = exports.resolve(from).substr(1);
3232 to = exports.resolve(to).substr(1);
3233
3234 function trim(arr) {
3235 var start = 0;
3236 for (; start < arr.length; start++) {
3237 if (arr[start] !== '') break;
3238 }
3239
3240 var end = arr.length - 1;
3241 for (; end >= 0; end--) {
3242 if (arr[end] !== '') break;
3243 }
3244
3245 if (start > end) return [];
3246 return arr.slice(start, end - start + 1);
3247 }
3248
3249 var fromParts = trim(from.split('/'));
3250 var toParts = trim(to.split('/'));
3251
3252 var length = Math.min(fromParts.length, toParts.length);
3253 var samePartsLength = length;
3254 for (var i = 0; i < length; i++) {
3255 if (fromParts[i] !== toParts[i]) {
3256 samePartsLength = i;
3257 break;
3258 }
3259 }
3260
3261 var outputParts = [];
3262 for (var i = samePartsLength; i < fromParts.length; i++) {
3263 outputParts.push('..');
3264 }
3265
3266 outputParts = outputParts.concat(toParts.slice(samePartsLength));
3267
3268 return outputParts.join('/');
3269};
3270
3271exports.sep = '/';
3272exports.delimiter = ':';
3273
3274exports.dirname = function(path) {
3275 var result = splitPath(path),
3276 root = result[0],
3277 dir = result[1];
3278
3279 if (!root && !dir) {
3280 // No dirname whatsoever
3281 return '.';
3282 }
3283
3284 if (dir) {
3285 // It has a dirname, strip trailing slash
3286 dir = dir.substr(0, dir.length - 1);
3287 }
3288
3289 return root + dir;
3290};
3291
3292
3293exports.basename = function(path, ext) {
3294 var f = splitPath(path)[2];
3295 // TODO: make this comparison case-insensitive on windows?
3296 if (ext && f.substr(-1 * ext.length) === ext) {
3297 f = f.substr(0, f.length - ext.length);
3298 }
3299 return f;
3300};
3301
3302
3303exports.extname = function(path) {
3304 return splitPath(path)[3];
3305};
3306
3307function filter (xs, f) {
3308 if (xs.filter) return xs.filter(f);
3309 var res = [];
3310 for (var i = 0; i < xs.length; i++) {
3311 if (f(xs[i], i, xs)) res.push(xs[i]);
3312 }
3313 return res;
3314}
3315
3316// String.prototype.substr - negative index don't work in IE8
3317var substr = 'ab'.substr(-1) === 'b'
3318 ? function (str, start, len) { return str.substr(start, len) }
3319 : function (str, start, len) {
3320 if (start < 0) start = str.length + start;
3321 return str.substr(start, len);
3322 }
3323;
3324
3325}).call(this,require('_process'))
3326},{"_process":33}],33:[function(require,module,exports){
3327// shim for using process in browser
3328
3329var process = module.exports = {};
3330var queue = [];
3331var draining = false;
3332var currentQueue;
3333var queueIndex = -1;
3334
3335function cleanUpNextTick() {
3336 draining = false;
3337 if (currentQueue.length) {
3338 queue = currentQueue.concat(queue);
3339 } else {
3340 queueIndex = -1;
3341 }
3342 if (queue.length) {
3343 drainQueue();
3344 }
3345}
3346
3347function drainQueue() {
3348 if (draining) {
3349 return;
3350 }
3351 var timeout = setTimeout(cleanUpNextTick);
3352 draining = true;
3353
3354 var len = queue.length;
3355 while(len) {
3356 currentQueue = queue;
3357 queue = [];
3358 while (++queueIndex < len) {
3359 if (currentQueue) {
3360 currentQueue[queueIndex].run();
3361 }
3362 }
3363 queueIndex = -1;
3364 len = queue.length;
3365 }
3366 currentQueue = null;
3367 draining = false;
3368 clearTimeout(timeout);
3369}
3370
3371process.nextTick = function (fun) {
3372 var args = new Array(arguments.length - 1);
3373 if (arguments.length > 1) {
3374 for (var i = 1; i < arguments.length; i++) {
3375 args[i - 1] = arguments[i];
3376 }
3377 }
3378 queue.push(new Item(fun, args));
3379 if (queue.length === 1 && !draining) {
3380 setTimeout(drainQueue, 0);
3381 }
3382};
3383
3384// v8 likes predictible objects
3385function Item(fun, array) {
3386 this.fun = fun;
3387 this.array = array;
3388}
3389Item.prototype.run = function () {
3390 this.fun.apply(null, this.array);
3391};
3392process.title = 'browser';
3393process.browser = true;
3394process.env = {};
3395process.argv = [];
3396process.version = ''; // empty string to avoid regexp issues
3397process.versions = {};
3398
3399function noop() {}
3400
3401process.on = noop;
3402process.addListener = noop;
3403process.once = noop;
3404process.off = noop;
3405process.removeListener = noop;
3406process.removeAllListeners = noop;
3407process.emit = noop;
3408
3409process.binding = function (name) {
3410 throw new Error('process.binding is not supported');
3411};
3412
3413process.cwd = function () { return '/' };
3414process.chdir = function (dir) {
3415 throw new Error('process.chdir is not supported');
3416};
3417process.umask = function() { return 0; };
3418
3419},{}],34:[function(require,module,exports){
3420module.exports={
3421 "name": "vash",
3422 "description": "Razor syntax for JS templating",
3423 "version": "0.12.1",
3424 "author": "Andrew Petersen <senofpeter@gmail.com>",
3425 "homepage": "https://github.com/kirbysayshi/vash",
3426 "bin": {
3427 "vash": "./bin/vash"
3428 },
3429 "keywords": [
3430 "razor",
3431 "parser",
3432 "template",
3433 "express"
3434 ],
3435 "repository": {
3436 "type": "git",
3437 "url": "git://github.com/kirbysayshi/vash"
3438 },
3439 "main": "index.js",
3440 "engines": {
3441 "node": ">= 0.10"
3442 },
3443 "scripts": {
3444 "prepublish": "npm run test && npm run build",
3445 "coverage": "VASHPATH=../../index.js VASHRUNTIMEPATH=../../runtime.js browserify -t envify -t coverify test/vows/vash.test.js | node | coverify",
3446 "build": "browserify index.js --standalone vash > build/vash.js && browserify --standalone vash runtime.js > build/vash-runtime.js && browserify --standalone vash --external fs --external path lib/helpers/index.js > build/vash-runtime-all.js",
3447 "test": "VASHPATH=../../index.js VASHRUNTIMEPATH=../../runtime.js vows test/vows/vash.*.js --spec",
3448 "docs": "scripts/docs.sh",
3449 "docs-dev": "scripts/docs-dev.sh"
3450 },
3451 "dependencies": {
3452 "commander": "~1.1.1",
3453 "debug": "^2.2.0",
3454 "uglify-js": "^2.6.2"
3455 },
3456 "devDependencies": {
3457 "browserify": "^13.0.0",
3458 "coverify": "^1.4.1",
3459 "envify": "^3.4.0",
3460 "jshint": "0.8.0",
3461 "marked": "~0.2.8",
3462 "semver": "~1",
3463 "vows": "^0.8.1"
3464 }
3465}
3466
3467},{}],35:[function(require,module,exports){
3468
3469var error = require('./lib/error');
3470var runtime = {
3471 version: require('./package.json').version
3472};
3473
3474var helpers = runtime['helpers'];
3475
3476module.exports = runtime;
3477
3478function Helpers( model ) {
3479 this.buffer = new Buffer();
3480 this.model = model;
3481 this.options = null; // added at render time
3482
3483 this.vl = 0;
3484 this.vc = 0;
3485};
3486
3487runtime['helpers']
3488 = helpers
3489 = Helpers.prototype
3490 = { constructor: Helpers, config: {}, tplcache: {} };
3491
3492// this allows a template to return the context, and coercion
3493// will handle it
3494helpers.toString = helpers.toHtmlString = function(){
3495 // not calling buffer.toString() results in 2x speedup
3496 return this.buffer._vo.join('');//.toString();
3497}
3498
3499///////////////////////////////////////////////////////////////////////////
3500// HTML ESCAPING
3501
3502var HTML_REGEX = /[&<>"'`]/g
3503 ,HTML_REPLACER = function(match) { return HTML_CHARS[match]; }
3504 ,HTML_CHARS = {
3505 "&": "&amp;"
3506 ,"<": "&lt;"
3507 ,">": "&gt;"
3508 ,'"': "&quot;"
3509 ,"'": "&#x27;"
3510 ,"`": "&#x60;"
3511 };
3512
3513helpers['raw'] = function( val ) {
3514 var func = function() { return val; };
3515
3516 val = val != null ? val : "";
3517
3518 return {
3519 toHtmlString: func
3520 ,toString: func
3521 };
3522};
3523
3524helpers['escape'] = function( val ) {
3525 var func = function() { return val; };
3526
3527 val = val != null ? val : "";
3528
3529 if ( typeof val.toHtmlString !== "function" ) {
3530
3531 val = val.toString().replace( HTML_REGEX, HTML_REPLACER );
3532
3533 return {
3534 toHtmlString: func
3535 ,toString: func
3536 };
3537 }
3538
3539 return val;
3540};
3541
3542// HTML ESCAPING
3543///////////////////////////////////////////////////////////////////////////
3544
3545
3546///////////////////////////////////////////////////////////////////////////
3547// BUFFER MANIPULATION
3548//
3549// These are to be used from within helpers, to allow for manipulation of
3550// output in a sane manner.
3551
3552var Buffer = function() {
3553 this._vo = [];
3554}
3555
3556Buffer.prototype.mark = function( debugName ) {
3557 var mark = new Mark( this, debugName );
3558 mark.markedIndex = this._vo.length;
3559 this._vo.push( mark.uid );
3560 return mark;
3561};
3562
3563Buffer.prototype.fromMark = function( mark ) {
3564 var found = mark.findInBuffer();
3565
3566 if( found > -1 ){
3567 // automatically destroy the mark from the buffer
3568 mark.destroy();
3569 // `found` will still be valid for a manual splice
3570 return this._vo.splice( found, this._vo.length );
3571 }
3572
3573 return [];
3574};
3575
3576Buffer.prototype.spliceMark = function( mark, numToRemove, add ){
3577 var found = mark.findInBuffer();
3578
3579 if( found > -1 ){
3580 mark.destroy();
3581 arguments[0] = found;
3582 return this._vo.splice.apply( this._vo, arguments );
3583 }
3584
3585 return [];
3586};
3587
3588Buffer.prototype.empty = function() {
3589 return this._vo.splice( 0, this._vo.length );
3590};
3591
3592Buffer.prototype.push = function( buffer ) {
3593 return this._vo.push( buffer );
3594};
3595
3596Buffer.prototype.pushConcat = function( buffer ){
3597 var buffers;
3598 if (Array.isArray(buffer)) {
3599 buffers = buffer;
3600 } else if ( arguments.length > 1 ) {
3601 buffers = Array.prototype.slice.call( arguments );
3602 } else {
3603 buffers = [buffer];
3604 }
3605
3606 for (var i = 0; i < buffers.length; i++) {
3607 this._vo.push( buffers[i] );
3608 }
3609
3610 return this.__vo;
3611}
3612
3613Buffer.prototype.indexOf = function( str ){
3614
3615 for( var i = 0; i < this._vo.length; i++ ){
3616 if(
3617 ( str.test && this._vo[i] && this._vo[i].search(str) > -1 )
3618 || this._vo[i] == str
3619 ){
3620 return i;
3621 }
3622 }
3623
3624 return -1;
3625}
3626
3627Buffer.prototype.lastIndexOf = function( str ){
3628 var i = this._vo.length;
3629
3630 while( --i >= 0 ){
3631 if(
3632 ( str.test && this._vo[i] && this._vo[i].search(str) > -1 )
3633 || this._vo[i] == str
3634 ){
3635 return i;
3636 }
3637 }
3638
3639 return -1;
3640}
3641
3642Buffer.prototype.splice = function(){
3643 return this._vo.splice.apply( this._vo, arguments );
3644}
3645
3646Buffer.prototype.index = function( idx ){
3647 return this._vo[ idx ];
3648}
3649
3650Buffer.prototype.flush = function() {
3651 return this.empty().join( "" );
3652};
3653
3654Buffer.prototype.toString = Buffer.prototype.toHtmlString = function(){
3655 // not using flush because then console.log( tpl() ) would artificially
3656 // affect the output
3657 return this._vo.join( "" );
3658}
3659
3660// BUFFER MANIPULATION
3661///////////////////////////////////////////////////////////////////////////
3662
3663///////////////////////////////////////////////////////////////////////////
3664// MARKS
3665// These can be used to manipulate the existing entries in the rendering
3666// context. For an example, see the highlight helper.
3667
3668var Mark = runtime['Mark'] = function( buffer, debugName ){
3669 this.uid = '[VASHMARK-'
3670 + ~~( Math.random() * 10000000 )
3671 + (debugName ? ':' + debugName : '')
3672 + ']';
3673 this.markedIndex = 0;
3674 this.buffer = buffer;
3675 this.destroyed = false;
3676}
3677
3678var reMark = Mark.re = /\[VASHMARK\-\d{1,8}(?::[\s\S]+?)?]/g
3679
3680// tests if a string has a mark-like uid within it
3681Mark.uidLike = function( str ){
3682 return (str || '').search( reMark ) > -1;
3683}
3684
3685Mark.prototype.destroy = function(){
3686
3687 var found = this.findInBuffer();
3688
3689 if( found > -1 ){
3690 this.buffer.splice( found, 1 );
3691 this.markedIndex = -1;
3692 }
3693
3694 this.destroyed = true;
3695}
3696
3697Mark.prototype.findInBuffer = function(){
3698
3699 if( this.destroyed ){
3700 return -1;
3701 }
3702
3703 if( this.markedIndex && this.buffer.index( this.markedIndex ) === this.uid ){
3704 return this.markedIndex;
3705 }
3706
3707 // The mark may be within a string due to block manipulation shenanigans.
3708 var escaped = this.uid.replace(/(\[|\])/g, '\\$1');
3709 var re = new RegExp(escaped);
3710 return this.markedIndex = this.buffer.indexOf( re );
3711}
3712
3713// MARKS
3714///////////////////////////////////////////////////////////////////////////
3715
3716///////////////////////////////////////////////////////////////////////////
3717// ERROR REPORTING
3718
3719// Liberally modified from https://github.com/visionmedia/jade/blob/master/jade.js
3720helpers.constructor.reportError = function(e, lineno, chr, orig, lb, atRenderTime){
3721
3722 lb = lb || '!LB!';
3723
3724 var contextStr = error.context(orig, lineno, chr, lb);
3725
3726 e.vashlineno = lineno;
3727 e.vashcharno = chr;
3728 e.message = 'Problem while '
3729 + (atRenderTime ? 'rendering' : 'compiling')
3730 + ' template at line '
3731 + lineno + ', character ' + chr
3732 + '.\nOriginal message: ' + e.message + '.'
3733 + '\nContext: \n\n' + contextStr + '\n\n';
3734
3735 throw e;
3736};
3737
3738helpers['reportError'] = function() {
3739 this.constructor.reportError.apply( this, arguments );
3740};
3741
3742// ERROR REPORTING
3743///////////////////////////////////////////////////////////////////////////
3744
3745///////////////////////////////////////////////////////////////////////////
3746// VASH.LINK
3747// Take a compiled string or function and "link" it to the current vash
3748// runtime. This is necessary to allow instantiation of `Helpers` and
3749// proper decompilation via `toClientString`.
3750//
3751// If `options.asHelper` and `options.args` are defined, the `cmpFunc` is
3752// interpreted as a compiled helper, and is attached to `runtime.helpers` at
3753// a property name equal to `options.asHelper`.
3754
3755runtime['link'] = function( cmpFunc, options ){
3756
3757 // TODO: allow options.filename to be used as sourceUrl?
3758
3759 var originalFunc
3760 ,cmpOpts;
3761
3762 if( !options.args ){
3763 // every template has these arguments
3764 options.args = [options.modelName, options.helpersName, '__vopts', 'runtime'];
3765 }
3766
3767 if( typeof cmpFunc === 'string' ){
3768 originalFunc = cmpFunc;
3769
3770 try {
3771 // do not pollute the args array for later attachment to the compiled
3772 // function for later decompilation/linking
3773 cmpOpts = options.args.slice();
3774 cmpOpts.push(cmpFunc);
3775 cmpFunc = Function.apply(null, cmpOpts);
3776 } catch(e) {
3777 // TODO: add flag to reportError to know if it's at compile time or runtime
3778 helpers.reportError(e, 0, 0, originalFunc, /\n/, false);
3779 }
3780 }
3781
3782 // need this to enable decompilation / relinking
3783 cmpFunc.options = {
3784 simple: options.simple
3785 ,modelName: options.modelName
3786 ,helpersName: options.helpersName
3787 }
3788
3789 var linked;
3790
3791 if( options.asHelper ){
3792
3793 cmpFunc.options.args = options.args;
3794 cmpFunc.options.asHelper = options.asHelper;
3795
3796 linked = function(){
3797 return cmpFunc.apply(this, slice.call(arguments));
3798 }
3799
3800 helpers[options.asHelper] = linked;
3801
3802 } else {
3803
3804 linked = function( model, opts ){
3805 if( options.simple ){
3806 var ctx = {
3807 buffer: []
3808 ,escape: Helpers.prototype.escape
3809 ,raw: Helpers.prototype.raw
3810 }
3811 return cmpFunc( model, ctx, opts, runtime );
3812 }
3813
3814 opts = divineRuntimeTplOptions( model, opts );
3815 return cmpFunc( model, (opts && opts.context) || new Helpers( model ), opts, runtime );
3816 }
3817 }
3818
3819 // show the template-specific code, instead of the generic linked function
3820 linked['toString'] = function(){ return cmpFunc.toString(); }
3821
3822 // shortcut to show the actual linked function
3823 linked['_toString'] = function(){ return Function.prototype.toString.call(linked) }
3824
3825 // This assumes a vash global, and should be deprecated.
3826 // TODO: @deprecate
3827 linked['toClientString'] = function(){
3828 return 'vash.link( '
3829 + cmpFunc.toString() + ', '
3830 + JSON.stringify( cmpFunc.options ) + ' )';
3831 }
3832
3833 return linked;
3834}
3835
3836// given a model and options, allow for various tpl signatures and options:
3837// ( model, {} )
3838// ( model, function onRenderEnd(){} )
3839// ( model )
3840// and model.onRenderEnd
3841function divineRuntimeTplOptions( model, opts ){
3842
3843 // allow for signature: model, callback
3844 if( typeof opts === 'function' ) {
3845 opts = { onRenderEnd: opts };
3846 }
3847
3848 // allow for passing in onRenderEnd via model
3849 if( model && model.onRenderEnd ){
3850 opts = opts || {};
3851
3852 if( !opts.onRenderEnd ){
3853 opts.onRenderEnd = model.onRenderEnd;
3854 }
3855
3856 delete model.onRenderEnd;
3857 }
3858
3859 // ensure options can be referenced
3860 if( !opts ){
3861 opts = {};
3862 }
3863
3864 return opts;
3865}
3866
3867// shortcut for compiled helpers
3868var slice = Array.prototype.slice;
3869
3870// VASH.LINK
3871///////////////////////////////////////////////////////////////////////////
3872
3873///////////////////////////////////////////////////////////////////////////
3874// TPL CACHE
3875
3876runtime['lookup'] = function( path, model ){
3877 var tpl = runtime.helpers.tplcache[path];
3878 if( !tpl ){ throw new Error('Could not find template: ' + path); }
3879 if( model ){ return tpl(model); }
3880 else return tpl;
3881};
3882
3883runtime['install'] = function( path, tpl ){
3884 var cache = runtime.helpers.tplcache;
3885 if( typeof tpl === 'string' ){
3886 // Super hacky: if the calling context has a `compile` function,
3887 // then `this` is likely full vash. This is simply for backwards
3888 // compatibility.
3889 // TODO: @deprecate
3890 if ( typeof this.compile === 'function') {
3891 tpl = this.compile(tpl);
3892 } else {
3893 throw new Error('.install(path, [string]) is not available in the standalone runtime.');
3894 }
3895 } else if( typeof path === 'object' ){
3896 tpl = path;
3897 Object.keys(tpl).forEach(function(path){
3898 cache[path] = tpl[path];
3899 });
3900 return cache;
3901 }
3902 return cache[path] = tpl;
3903};
3904
3905runtime['uninstall'] = function( path ){
3906 var cache = runtime.helpers.tplcache
3907 ,deleted = false;
3908
3909 if( typeof path === 'string' ){
3910 return delete cache[path];
3911 } else {
3912 Object.keys(cache).forEach(function(key){
3913 if( cache[key] === path ){ deleted = delete cache[key]; }
3914 })
3915 return deleted;
3916 }
3917};
3918
3919},{"./lib/error":3,"./package.json":34}]},{},[1])(1)
3920});
\No newline at end of file