UNPKG

92.3 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":31,"./runtime":32,"debug":28}],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":28}],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":32}],6:[function(require,module,exports){
611require('./trim');
612require('./highlight');
613require('./layout');
614module.exports = require('../../runtime');
615},{"../../runtime":32,"./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":32,"../util/copyrtl":26,"fs":"fs","path":"path"}],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":32}],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":28}],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":28}],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":28}],28:[function(require,module,exports){
2601
2602/**
2603 * This is the web browser implementation of `debug()`.
2604 *
2605 * Expose `debug()` as the module.
2606 */
2607
2608exports = module.exports = require('./debug');
2609exports.log = log;
2610exports.formatArgs = formatArgs;
2611exports.save = save;
2612exports.load = load;
2613exports.useColors = useColors;
2614exports.storage = 'undefined' != typeof chrome
2615 && 'undefined' != typeof chrome.storage
2616 ? chrome.storage.local
2617 : localstorage();
2618
2619/**
2620 * Colors.
2621 */
2622
2623exports.colors = [
2624 'lightseagreen',
2625 'forestgreen',
2626 'goldenrod',
2627 'dodgerblue',
2628 'darkorchid',
2629 'crimson'
2630];
2631
2632/**
2633 * Currently only WebKit-based Web Inspectors, Firefox >= v31,
2634 * and the Firebug extension (any Firefox version) are known
2635 * to support "%c" CSS customizations.
2636 *
2637 * TODO: add a `localStorage` variable to explicitly enable/disable colors
2638 */
2639
2640function useColors() {
2641 // is webkit? http://stackoverflow.com/a/16459606/376773
2642 return ('WebkitAppearance' in document.documentElement.style) ||
2643 // is firebug? http://stackoverflow.com/a/398120/376773
2644 (window.console && (console.firebug || (console.exception && console.table))) ||
2645 // is firefox >= v31?
2646 // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
2647 (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
2648}
2649
2650/**
2651 * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
2652 */
2653
2654exports.formatters.j = function(v) {
2655 return JSON.stringify(v);
2656};
2657
2658
2659/**
2660 * Colorize log arguments if enabled.
2661 *
2662 * @api public
2663 */
2664
2665function formatArgs() {
2666 var args = arguments;
2667 var useColors = this.useColors;
2668
2669 args[0] = (useColors ? '%c' : '')
2670 + this.namespace
2671 + (useColors ? ' %c' : ' ')
2672 + args[0]
2673 + (useColors ? '%c ' : ' ')
2674 + '+' + exports.humanize(this.diff);
2675
2676 if (!useColors) return args;
2677
2678 var c = 'color: ' + this.color;
2679 args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
2680
2681 // the final "%c" is somewhat tricky, because there could be other
2682 // arguments passed either before or after the %c, so we need to
2683 // figure out the correct index to insert the CSS into
2684 var index = 0;
2685 var lastC = 0;
2686 args[0].replace(/%[a-z%]/g, function(match) {
2687 if ('%%' === match) return;
2688 index++;
2689 if ('%c' === match) {
2690 // we only are interested in the *last* %c
2691 // (the user may have provided their own)
2692 lastC = index;
2693 }
2694 });
2695
2696 args.splice(lastC, 0, c);
2697 return args;
2698}
2699
2700/**
2701 * Invokes `console.log()` when available.
2702 * No-op when `console.log` is not a "function".
2703 *
2704 * @api public
2705 */
2706
2707function log() {
2708 // this hackery is required for IE8/9, where
2709 // the `console.log` function doesn't have 'apply'
2710 return 'object' === typeof console
2711 && console.log
2712 && Function.prototype.apply.call(console.log, console, arguments);
2713}
2714
2715/**
2716 * Save `namespaces`.
2717 *
2718 * @param {String} namespaces
2719 * @api private
2720 */
2721
2722function save(namespaces) {
2723 try {
2724 if (null == namespaces) {
2725 exports.storage.removeItem('debug');
2726 } else {
2727 exports.storage.debug = namespaces;
2728 }
2729 } catch(e) {}
2730}
2731
2732/**
2733 * Load `namespaces`.
2734 *
2735 * @return {String} returns the previously persisted debug modes
2736 * @api private
2737 */
2738
2739function load() {
2740 var r;
2741 try {
2742 r = exports.storage.debug;
2743 } catch(e) {}
2744 return r;
2745}
2746
2747/**
2748 * Enable namespaces listed in `localStorage.debug` initially.
2749 */
2750
2751exports.enable(load());
2752
2753/**
2754 * Localstorage attempts to return the localstorage.
2755 *
2756 * This is necessary because safari throws
2757 * when a user disables cookies/localstorage
2758 * and you attempt to access it.
2759 *
2760 * @return {LocalStorage}
2761 * @api private
2762 */
2763
2764function localstorage(){
2765 try {
2766 return window.localStorage;
2767 } catch (e) {}
2768}
2769
2770},{"./debug":29}],29:[function(require,module,exports){
2771
2772/**
2773 * This is the common logic for both the Node.js and web browser
2774 * implementations of `debug()`.
2775 *
2776 * Expose `debug()` as the module.
2777 */
2778
2779exports = module.exports = debug;
2780exports.coerce = coerce;
2781exports.disable = disable;
2782exports.enable = enable;
2783exports.enabled = enabled;
2784exports.humanize = require('ms');
2785
2786/**
2787 * The currently active debug mode names, and names to skip.
2788 */
2789
2790exports.names = [];
2791exports.skips = [];
2792
2793/**
2794 * Map of special "%n" handling functions, for the debug "format" argument.
2795 *
2796 * Valid key names are a single, lowercased letter, i.e. "n".
2797 */
2798
2799exports.formatters = {};
2800
2801/**
2802 * Previously assigned color.
2803 */
2804
2805var prevColor = 0;
2806
2807/**
2808 * Previous log timestamp.
2809 */
2810
2811var prevTime;
2812
2813/**
2814 * Select a color.
2815 *
2816 * @return {Number}
2817 * @api private
2818 */
2819
2820function selectColor() {
2821 return exports.colors[prevColor++ % exports.colors.length];
2822}
2823
2824/**
2825 * Create a debugger with the given `namespace`.
2826 *
2827 * @param {String} namespace
2828 * @return {Function}
2829 * @api public
2830 */
2831
2832function debug(namespace) {
2833
2834 // define the `disabled` version
2835 function disabled() {
2836 }
2837 disabled.enabled = false;
2838
2839 // define the `enabled` version
2840 function enabled() {
2841
2842 var self = enabled;
2843
2844 // set `diff` timestamp
2845 var curr = +new Date();
2846 var ms = curr - (prevTime || curr);
2847 self.diff = ms;
2848 self.prev = prevTime;
2849 self.curr = curr;
2850 prevTime = curr;
2851
2852 // add the `color` if not set
2853 if (null == self.useColors) self.useColors = exports.useColors();
2854 if (null == self.color && self.useColors) self.color = selectColor();
2855
2856 var args = Array.prototype.slice.call(arguments);
2857
2858 args[0] = exports.coerce(args[0]);
2859
2860 if ('string' !== typeof args[0]) {
2861 // anything else let's inspect with %o
2862 args = ['%o'].concat(args);
2863 }
2864
2865 // apply any `formatters` transformations
2866 var index = 0;
2867 args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
2868 // if we encounter an escaped % then don't increase the array index
2869 if (match === '%%') return match;
2870 index++;
2871 var formatter = exports.formatters[format];
2872 if ('function' === typeof formatter) {
2873 var val = args[index];
2874 match = formatter.call(self, val);
2875
2876 // now we need to remove `args[index]` since it's inlined in the `format`
2877 args.splice(index, 1);
2878 index--;
2879 }
2880 return match;
2881 });
2882
2883 if ('function' === typeof exports.formatArgs) {
2884 args = exports.formatArgs.apply(self, args);
2885 }
2886 var logFn = enabled.log || exports.log || console.log.bind(console);
2887 logFn.apply(self, args);
2888 }
2889 enabled.enabled = true;
2890
2891 var fn = exports.enabled(namespace) ? enabled : disabled;
2892
2893 fn.namespace = namespace;
2894
2895 return fn;
2896}
2897
2898/**
2899 * Enables a debug mode by namespaces. This can include modes
2900 * separated by a colon and wildcards.
2901 *
2902 * @param {String} namespaces
2903 * @api public
2904 */
2905
2906function enable(namespaces) {
2907 exports.save(namespaces);
2908
2909 var split = (namespaces || '').split(/[\s,]+/);
2910 var len = split.length;
2911
2912 for (var i = 0; i < len; i++) {
2913 if (!split[i]) continue; // ignore empty strings
2914 namespaces = split[i].replace(/\*/g, '.*?');
2915 if (namespaces[0] === '-') {
2916 exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
2917 } else {
2918 exports.names.push(new RegExp('^' + namespaces + '$'));
2919 }
2920 }
2921}
2922
2923/**
2924 * Disable debug output.
2925 *
2926 * @api public
2927 */
2928
2929function disable() {
2930 exports.enable('');
2931}
2932
2933/**
2934 * Returns true if the given mode name is enabled, false otherwise.
2935 *
2936 * @param {String} name
2937 * @return {Boolean}
2938 * @api public
2939 */
2940
2941function enabled(name) {
2942 var i, len;
2943 for (i = 0, len = exports.skips.length; i < len; i++) {
2944 if (exports.skips[i].test(name)) {
2945 return false;
2946 }
2947 }
2948 for (i = 0, len = exports.names.length; i < len; i++) {
2949 if (exports.names[i].test(name)) {
2950 return true;
2951 }
2952 }
2953 return false;
2954}
2955
2956/**
2957 * Coerce `val`.
2958 *
2959 * @param {Mixed} val
2960 * @return {Mixed}
2961 * @api private
2962 */
2963
2964function coerce(val) {
2965 if (val instanceof Error) return val.stack || val.message;
2966 return val;
2967}
2968
2969},{"ms":30}],30:[function(require,module,exports){
2970/**
2971 * Helpers.
2972 */
2973
2974var s = 1000;
2975var m = s * 60;
2976var h = m * 60;
2977var d = h * 24;
2978var y = d * 365.25;
2979
2980/**
2981 * Parse or format the given `val`.
2982 *
2983 * Options:
2984 *
2985 * - `long` verbose formatting [false]
2986 *
2987 * @param {String|Number} val
2988 * @param {Object} options
2989 * @return {String|Number}
2990 * @api public
2991 */
2992
2993module.exports = function(val, options){
2994 options = options || {};
2995 if ('string' == typeof val) return parse(val);
2996 return options.long
2997 ? long(val)
2998 : short(val);
2999};
3000
3001/**
3002 * Parse the given `str` and return milliseconds.
3003 *
3004 * @param {String} str
3005 * @return {Number}
3006 * @api private
3007 */
3008
3009function parse(str) {
3010 str = '' + str;
3011 if (str.length > 10000) return;
3012 var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);
3013 if (!match) return;
3014 var n = parseFloat(match[1]);
3015 var type = (match[2] || 'ms').toLowerCase();
3016 switch (type) {
3017 case 'years':
3018 case 'year':
3019 case 'yrs':
3020 case 'yr':
3021 case 'y':
3022 return n * y;
3023 case 'days':
3024 case 'day':
3025 case 'd':
3026 return n * d;
3027 case 'hours':
3028 case 'hour':
3029 case 'hrs':
3030 case 'hr':
3031 case 'h':
3032 return n * h;
3033 case 'minutes':
3034 case 'minute':
3035 case 'mins':
3036 case 'min':
3037 case 'm':
3038 return n * m;
3039 case 'seconds':
3040 case 'second':
3041 case 'secs':
3042 case 'sec':
3043 case 's':
3044 return n * s;
3045 case 'milliseconds':
3046 case 'millisecond':
3047 case 'msecs':
3048 case 'msec':
3049 case 'ms':
3050 return n;
3051 }
3052}
3053
3054/**
3055 * Short format for `ms`.
3056 *
3057 * @param {Number} ms
3058 * @return {String}
3059 * @api private
3060 */
3061
3062function short(ms) {
3063 if (ms >= d) return Math.round(ms / d) + 'd';
3064 if (ms >= h) return Math.round(ms / h) + 'h';
3065 if (ms >= m) return Math.round(ms / m) + 'm';
3066 if (ms >= s) return Math.round(ms / s) + 's';
3067 return ms + 'ms';
3068}
3069
3070/**
3071 * Long format for `ms`.
3072 *
3073 * @param {Number} ms
3074 * @return {String}
3075 * @api private
3076 */
3077
3078function long(ms) {
3079 return plural(ms, d, 'day')
3080 || plural(ms, h, 'hour')
3081 || plural(ms, m, 'minute')
3082 || plural(ms, s, 'second')
3083 || ms + ' ms';
3084}
3085
3086/**
3087 * Pluralization helper.
3088 */
3089
3090function plural(ms, n, name) {
3091 if (ms < n) return;
3092 if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
3093 return Math.ceil(ms / n) + ' ' + name + 's';
3094}
3095
3096},{}],31:[function(require,module,exports){
3097module.exports={
3098 "name": "vash",
3099 "description": "Razor syntax for JS templating",
3100 "version": "0.12.1",
3101 "author": "Andrew Petersen <senofpeter@gmail.com>",
3102 "homepage": "https://github.com/kirbysayshi/vash",
3103 "bin": {
3104 "vash": "./bin/vash"
3105 },
3106 "keywords": [
3107 "razor",
3108 "parser",
3109 "template",
3110 "express"
3111 ],
3112 "repository": {
3113 "type": "git",
3114 "url": "git://github.com/kirbysayshi/vash"
3115 },
3116 "main": "index.js",
3117 "engines": {
3118 "node": ">= 0.10"
3119 },
3120 "scripts": {
3121 "prepublish": "npm run test && npm run build",
3122 "coverage": "VASHPATH=../../index.js VASHRUNTIMEPATH=../../runtime.js browserify -t envify -t coverify test/vows/vash.test.js | node | coverify",
3123 "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",
3124 "test": "VASHPATH=../../index.js VASHRUNTIMEPATH=../../runtime.js vows test/vows/vash.*.js --spec",
3125 "docs": "scripts/docs.sh",
3126 "docs-dev": "scripts/docs-dev.sh"
3127 },
3128 "dependencies": {
3129 "commander": "~1.1.1",
3130 "debug": "^2.2.0",
3131 "uglify-js": "^2.6.2"
3132 },
3133 "devDependencies": {
3134 "browserify": "^13.0.0",
3135 "coverify": "^1.4.1",
3136 "envify": "^3.4.0",
3137 "jshint": "0.8.0",
3138 "marked": "~0.2.8",
3139 "semver": "~1",
3140 "vows": "^0.8.1"
3141 }
3142}
3143
3144},{}],32:[function(require,module,exports){
3145
3146var error = require('./lib/error');
3147var runtime = {
3148 version: require('./package.json').version
3149};
3150
3151var helpers = runtime['helpers'];
3152
3153module.exports = runtime;
3154
3155function Helpers( model ) {
3156 this.buffer = new Buffer();
3157 this.model = model;
3158 this.options = null; // added at render time
3159
3160 this.vl = 0;
3161 this.vc = 0;
3162};
3163
3164runtime['helpers']
3165 = helpers
3166 = Helpers.prototype
3167 = { constructor: Helpers, config: {}, tplcache: {} };
3168
3169// this allows a template to return the context, and coercion
3170// will handle it
3171helpers.toString = helpers.toHtmlString = function(){
3172 // not calling buffer.toString() results in 2x speedup
3173 return this.buffer._vo.join('');//.toString();
3174}
3175
3176///////////////////////////////////////////////////////////////////////////
3177// HTML ESCAPING
3178
3179var HTML_REGEX = /[&<>"'`]/g
3180 ,HTML_REPLACER = function(match) { return HTML_CHARS[match]; }
3181 ,HTML_CHARS = {
3182 "&": "&amp;"
3183 ,"<": "&lt;"
3184 ,">": "&gt;"
3185 ,'"': "&quot;"
3186 ,"'": "&#x27;"
3187 ,"`": "&#x60;"
3188 };
3189
3190helpers['raw'] = function( val ) {
3191 var func = function() { return val; };
3192
3193 val = val != null ? val : "";
3194
3195 return {
3196 toHtmlString: func
3197 ,toString: func
3198 };
3199};
3200
3201helpers['escape'] = function( val ) {
3202 var func = function() { return val; };
3203
3204 val = val != null ? val : "";
3205
3206 if ( typeof val.toHtmlString !== "function" ) {
3207
3208 val = val.toString().replace( HTML_REGEX, HTML_REPLACER );
3209
3210 return {
3211 toHtmlString: func
3212 ,toString: func
3213 };
3214 }
3215
3216 return val;
3217};
3218
3219// HTML ESCAPING
3220///////////////////////////////////////////////////////////////////////////
3221
3222
3223///////////////////////////////////////////////////////////////////////////
3224// BUFFER MANIPULATION
3225//
3226// These are to be used from within helpers, to allow for manipulation of
3227// output in a sane manner.
3228
3229var Buffer = function() {
3230 this._vo = [];
3231}
3232
3233Buffer.prototype.mark = function( debugName ) {
3234 var mark = new Mark( this, debugName );
3235 mark.markedIndex = this._vo.length;
3236 this._vo.push( mark.uid );
3237 return mark;
3238};
3239
3240Buffer.prototype.fromMark = function( mark ) {
3241 var found = mark.findInBuffer();
3242
3243 if( found > -1 ){
3244 // automatically destroy the mark from the buffer
3245 mark.destroy();
3246 // `found` will still be valid for a manual splice
3247 return this._vo.splice( found, this._vo.length );
3248 }
3249
3250 return [];
3251};
3252
3253Buffer.prototype.spliceMark = function( mark, numToRemove, add ){
3254 var found = mark.findInBuffer();
3255
3256 if( found > -1 ){
3257 mark.destroy();
3258 arguments[0] = found;
3259 return this._vo.splice.apply( this._vo, arguments );
3260 }
3261
3262 return [];
3263};
3264
3265Buffer.prototype.empty = function() {
3266 return this._vo.splice( 0, this._vo.length );
3267};
3268
3269Buffer.prototype.push = function( buffer ) {
3270 return this._vo.push( buffer );
3271};
3272
3273Buffer.prototype.pushConcat = function( buffer ){
3274 var buffers;
3275 if (Array.isArray(buffer)) {
3276 buffers = buffer;
3277 } else if ( arguments.length > 1 ) {
3278 buffers = Array.prototype.slice.call( arguments );
3279 } else {
3280 buffers = [buffer];
3281 }
3282
3283 for (var i = 0; i < buffers.length; i++) {
3284 this._vo.push( buffers[i] );
3285 }
3286
3287 return this.__vo;
3288}
3289
3290Buffer.prototype.indexOf = function( str ){
3291
3292 for( var i = 0; i < this._vo.length; i++ ){
3293 if(
3294 ( str.test && this._vo[i] && this._vo[i].search(str) > -1 )
3295 || this._vo[i] == str
3296 ){
3297 return i;
3298 }
3299 }
3300
3301 return -1;
3302}
3303
3304Buffer.prototype.lastIndexOf = function( str ){
3305 var i = this._vo.length;
3306
3307 while( --i >= 0 ){
3308 if(
3309 ( str.test && this._vo[i] && this._vo[i].search(str) > -1 )
3310 || this._vo[i] == str
3311 ){
3312 return i;
3313 }
3314 }
3315
3316 return -1;
3317}
3318
3319Buffer.prototype.splice = function(){
3320 return this._vo.splice.apply( this._vo, arguments );
3321}
3322
3323Buffer.prototype.index = function( idx ){
3324 return this._vo[ idx ];
3325}
3326
3327Buffer.prototype.flush = function() {
3328 return this.empty().join( "" );
3329};
3330
3331Buffer.prototype.toString = Buffer.prototype.toHtmlString = function(){
3332 // not using flush because then console.log( tpl() ) would artificially
3333 // affect the output
3334 return this._vo.join( "" );
3335}
3336
3337// BUFFER MANIPULATION
3338///////////////////////////////////////////////////////////////////////////
3339
3340///////////////////////////////////////////////////////////////////////////
3341// MARKS
3342// These can be used to manipulate the existing entries in the rendering
3343// context. For an example, see the highlight helper.
3344
3345var Mark = runtime['Mark'] = function( buffer, debugName ){
3346 this.uid = '[VASHMARK-'
3347 + ~~( Math.random() * 10000000 )
3348 + (debugName ? ':' + debugName : '')
3349 + ']';
3350 this.markedIndex = 0;
3351 this.buffer = buffer;
3352 this.destroyed = false;
3353}
3354
3355var reMark = Mark.re = /\[VASHMARK\-\d{1,8}(?::[\s\S]+?)?]/g
3356
3357// tests if a string has a mark-like uid within it
3358Mark.uidLike = function( str ){
3359 return (str || '').search( reMark ) > -1;
3360}
3361
3362Mark.prototype.destroy = function(){
3363
3364 var found = this.findInBuffer();
3365
3366 if( found > -1 ){
3367 this.buffer.splice( found, 1 );
3368 this.markedIndex = -1;
3369 }
3370
3371 this.destroyed = true;
3372}
3373
3374Mark.prototype.findInBuffer = function(){
3375
3376 if( this.destroyed ){
3377 return -1;
3378 }
3379
3380 if( this.markedIndex && this.buffer.index( this.markedIndex ) === this.uid ){
3381 return this.markedIndex;
3382 }
3383
3384 // The mark may be within a string due to block manipulation shenanigans.
3385 var escaped = this.uid.replace(/(\[|\])/g, '\\$1');
3386 var re = new RegExp(escaped);
3387 return this.markedIndex = this.buffer.indexOf( re );
3388}
3389
3390// MARKS
3391///////////////////////////////////////////////////////////////////////////
3392
3393///////////////////////////////////////////////////////////////////////////
3394// ERROR REPORTING
3395
3396// Liberally modified from https://github.com/visionmedia/jade/blob/master/jade.js
3397helpers.constructor.reportError = function(e, lineno, chr, orig, lb, atRenderTime){
3398
3399 lb = lb || '!LB!';
3400
3401 var contextStr = error.context(orig, lineno, chr, lb);
3402
3403 e.vashlineno = lineno;
3404 e.vashcharno = chr;
3405 e.message = 'Problem while '
3406 + (atRenderTime ? 'rendering' : 'compiling')
3407 + ' template at line '
3408 + lineno + ', character ' + chr
3409 + '.\nOriginal message: ' + e.message + '.'
3410 + '\nContext: \n\n' + contextStr + '\n\n';
3411
3412 throw e;
3413};
3414
3415helpers['reportError'] = function() {
3416 this.constructor.reportError.apply( this, arguments );
3417};
3418
3419// ERROR REPORTING
3420///////////////////////////////////////////////////////////////////////////
3421
3422///////////////////////////////////////////////////////////////////////////
3423// VASH.LINK
3424// Take a compiled string or function and "link" it to the current vash
3425// runtime. This is necessary to allow instantiation of `Helpers` and
3426// proper decompilation via `toClientString`.
3427//
3428// If `options.asHelper` and `options.args` are defined, the `cmpFunc` is
3429// interpreted as a compiled helper, and is attached to `runtime.helpers` at
3430// a property name equal to `options.asHelper`.
3431
3432runtime['link'] = function( cmpFunc, options ){
3433
3434 // TODO: allow options.filename to be used as sourceUrl?
3435
3436 var originalFunc
3437 ,cmpOpts;
3438
3439 if( !options.args ){
3440 // every template has these arguments
3441 options.args = [options.modelName, options.helpersName, '__vopts', 'runtime'];
3442 }
3443
3444 if( typeof cmpFunc === 'string' ){
3445 originalFunc = cmpFunc;
3446
3447 try {
3448 // do not pollute the args array for later attachment to the compiled
3449 // function for later decompilation/linking
3450 cmpOpts = options.args.slice();
3451 cmpOpts.push(cmpFunc);
3452 cmpFunc = Function.apply(null, cmpOpts);
3453 } catch(e) {
3454 // TODO: add flag to reportError to know if it's at compile time or runtime
3455 helpers.reportError(e, 0, 0, originalFunc, /\n/, false);
3456 }
3457 }
3458
3459 // need this to enable decompilation / relinking
3460 cmpFunc.options = {
3461 simple: options.simple
3462 ,modelName: options.modelName
3463 ,helpersName: options.helpersName
3464 }
3465
3466 var linked;
3467
3468 if( options.asHelper ){
3469
3470 cmpFunc.options.args = options.args;
3471 cmpFunc.options.asHelper = options.asHelper;
3472
3473 linked = function(){
3474 return cmpFunc.apply(this, slice.call(arguments));
3475 }
3476
3477 helpers[options.asHelper] = linked;
3478
3479 } else {
3480
3481 linked = function( model, opts ){
3482 if( options.simple ){
3483 var ctx = {
3484 buffer: []
3485 ,escape: Helpers.prototype.escape
3486 ,raw: Helpers.prototype.raw
3487 }
3488 return cmpFunc( model, ctx, opts, runtime );
3489 }
3490
3491 opts = divineRuntimeTplOptions( model, opts );
3492 return cmpFunc( model, (opts && opts.context) || new Helpers( model ), opts, runtime );
3493 }
3494 }
3495
3496 // show the template-specific code, instead of the generic linked function
3497 linked['toString'] = function(){ return cmpFunc.toString(); }
3498
3499 // shortcut to show the actual linked function
3500 linked['_toString'] = function(){ return Function.prototype.toString.call(linked) }
3501
3502 // This assumes a vash global, and should be deprecated.
3503 // TODO: @deprecate
3504 linked['toClientString'] = function(){
3505 return 'vash.link( '
3506 + cmpFunc.toString() + ', '
3507 + JSON.stringify( cmpFunc.options ) + ' )';
3508 }
3509
3510 return linked;
3511}
3512
3513// given a model and options, allow for various tpl signatures and options:
3514// ( model, {} )
3515// ( model, function onRenderEnd(){} )
3516// ( model )
3517// and model.onRenderEnd
3518function divineRuntimeTplOptions( model, opts ){
3519
3520 // allow for signature: model, callback
3521 if( typeof opts === 'function' ) {
3522 opts = { onRenderEnd: opts };
3523 }
3524
3525 // allow for passing in onRenderEnd via model
3526 if( model && model.onRenderEnd ){
3527 opts = opts || {};
3528
3529 if( !opts.onRenderEnd ){
3530 opts.onRenderEnd = model.onRenderEnd;
3531 }
3532
3533 delete model.onRenderEnd;
3534 }
3535
3536 // ensure options can be referenced
3537 if( !opts ){
3538 opts = {};
3539 }
3540
3541 return opts;
3542}
3543
3544// shortcut for compiled helpers
3545var slice = Array.prototype.slice;
3546
3547// VASH.LINK
3548///////////////////////////////////////////////////////////////////////////
3549
3550///////////////////////////////////////////////////////////////////////////
3551// TPL CACHE
3552
3553runtime['lookup'] = function( path, model ){
3554 var tpl = runtime.helpers.tplcache[path];
3555 if( !tpl ){ throw new Error('Could not find template: ' + path); }
3556 if( model ){ return tpl(model); }
3557 else return tpl;
3558};
3559
3560runtime['install'] = function( path, tpl ){
3561 var cache = runtime.helpers.tplcache;
3562 if( typeof tpl === 'string' ){
3563 // Super hacky: if the calling context has a `compile` function,
3564 // then `this` is likely full vash. This is simply for backwards
3565 // compatibility.
3566 // TODO: @deprecate
3567 if ( typeof this.compile === 'function') {
3568 tpl = this.compile(tpl);
3569 } else {
3570 throw new Error('.install(path, [string]) is not available in the standalone runtime.');
3571 }
3572 } else if( typeof path === 'object' ){
3573 tpl = path;
3574 Object.keys(tpl).forEach(function(path){
3575 cache[path] = tpl[path];
3576 });
3577 return cache;
3578 }
3579 return cache[path] = tpl;
3580};
3581
3582runtime['uninstall'] = function( path ){
3583 var cache = runtime.helpers.tplcache
3584 ,deleted = false;
3585
3586 if( typeof path === 'string' ){
3587 return delete cache[path];
3588 } else {
3589 Object.keys(cache).forEach(function(key){
3590 if( cache[key] === path ){ deleted = delete cache[key]; }
3591 })
3592 return deleted;
3593 }
3594};
3595
3596},{"./lib/error":3,"./package.json":31}]},{},[6])(6)
3597});
\No newline at end of file