<|start_filename|>node_modules/vash/lib/nodes/markupattribute.js<|end_filename|>
var Node = module.exports = function MarkupAttributeNode() {
  this.type = 'VashMarkupAttribute';
  this.left = [];
  this.right = [];
  this.rightIsQuoted = false;
  this.startloc = null;
  this.endloc = null;

  this._finishedLeft = false;
  this._expectRight = false;
}

Node.prototype.endOk = function() {
  // TODO: this should include expecting right + found quotes or not.
  return this._finishedLeft
    ? true
    : false;
}

<|start_filename|>node_modules/vash/lib/helpers/trim.js<|end_filename|>
var helpers = require('../../runtime').helpers;

// Trim whitespace from the start and end of a string
helpers.trim = function(val){
  return val.replace(/^\s*|\s*$/g, '');
}

<|start_filename|>node_modules/vash/lib/nodes/location.js<|end_filename|>
module.exports = function LocationNode() {
  this.line = 1;
  this.column = 0;
}

<|start_filename|>node_modules/vash/lib/tokens.js<|end_filename|>
// The order of these is important, as it is the order in which
// they are run against the input string.
// They are separated out here to allow for better minification
// with the least amount of effort from me. :)

// Any function instead of regex is called with the lexer as the
// context.

// NOTE: this is an array, not an object literal! The () around
// the regexps are for the sake of the syntax highlighter in my
// editor... sublimetext2

var TESTS = [

  // A real email address is considerably more complex, and unfortunately
  // this complexity makes it impossible to differentiate between an address
  // and an AT expression.
  //
  // Instead, this regex assumes the only valid characters for the user portion
  // of the address are alphanumeric, period, and %. This means that a complex email like
  // <EMAIL>-<EMAIL> will be interpreted as an email, but incompletely. `who-`
  // will be content, while `<EMAIL>` will be the email address.
  //
  // However, this is "Good Enough"© :).
    'EMAIL', (/^([a-zA-Z0-9.%]+@[a-zA-Z0-9.\-]+\.(?:[a-z]{2}|co\.uk|com|edu|net|org))\b/)

  , 'AT_STAR_OPEN', (/^(@\*)/)
  , 'AT_STAR_CLOSE', (/^(\*@)/)


  , 'AT_COLON', (/^(@\:)/)
  , 'AT', (/^(@)/)


  , 'PAREN_OPEN', (/^(\()/)
  , 'PAREN_CLOSE', (/^(\))/)


  , 'HARD_PAREN_OPEN', (/^(\[)/)
  , 'HARD_PAREN_CLOSE', (/^(\])/)


  , 'BRACE_OPEN', (/^(\{)/)
  , 'BRACE_CLOSE', (/^(\})/)


  , 'HTML_TAG_VOID_CLOSE', (/^(\/>)/)
  , 'HTML_TAG_CLOSE', (/^(<\/)/)
  , 'HTML_COMMENT_OPEN', (/^(<!--+)/)
  , 'HTML_COMMENT_CLOSE', (/^(--+>)/)
  , 'LT_SIGN', (/^(<)/)
  , 'GT_SIGN', (/^(>)/)

  , 'ASSIGNMENT_OPERATOR', (/^(\|=|\^=|&=|>>>=|>>=|<<=|-=|\+=|%=|\/=|\*=)\b/) // Also =
  , 'EQUALITY_OPERATOR', (/^(===|==|!==|!=)\b/)
  , 'BITWISE_SHIFT_OPERATOR', (/^(<<|>>>|>>)/)
  , 'UNARY_OPERATOR', (/^(delete\b|typeof\b|void|\+\+|--|\+|-|~|!)/)
  , 'RELATIONAL_OPERATOR', (/^(<=|>=|instanceof|in)\b/) // Also <, >
  , 'BINARY_LOGICAL_OPERATOR', (/^(&&|\|\|)\b/)
  , 'BINARY_BITWISE_OPERATOR', (/^(&|\^|\|)\b/)
  , 'NEW_OPERATOR', (/^(new)\b/)
  , 'COMMA_OPERATOR', (/^(,)/)

  , 'EQUAL_SIGN', (/^(=)/)
  , 'COLON', (/^(:)/)
  , 'PERIOD', (/^(\.)/)
  , 'NEWLINE', function(){
    var token = this.scan(/^(\n)/, exports.NEWLINE);
    if(token){
      this.lineno++;
      this.charno = 0;
    }
    return token;
  }
  , 'WHITESPACE', (/^([^\S\n]+)/) // http://stackoverflow.com/a/3469155
  , 'FUNCTION', (/^(function)(?![\d\w])/)
  , 'BLOCK_KEYWORD', (/^(catch|do|else if|else|finally|for|function|goto|if|switch|try|while|with)(?![\d\w])/)
  , 'KEYWORD', (/^(break|case|continue|instanceof|return|var)(?![\d\w])/)
  , 'IDENTIFIER', (/^([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)/)

  , 'DOUBLE_FORWARD_SLASH', (/^(\/\/)/)

  , 'FORWARD_SLASH', (/^(\/)/)

  , 'BACKSLASH', (/^(\\)/)
  , 'EXCLAMATION_POINT', (/^(!)/)
  , 'DOUBLE_QUOTE', (/^(\")/)
  , 'SINGLE_QUOTE', (/^(\')/)

  , 'NUMERAL', (/^([0-9])/)
  , 'CONTENT', (/^([^\s])/)

];

exports.tests = TESTS;

// Export all the tokens as constants.
for(var i = 0; i < TESTS.length; i += 2) {
  exports[TESTS[i]] = TESTS[i];
}

<|start_filename|>node_modules/vash/lib/util/fn-namer.js<|end_filename|>
var lg = require('debug')('vash:fn-namer');
var reName = /^function\s+([A-Za-z0-9_]+)\s*\(/;

module.exports = function(fn) {
  if (fn.name) {
    lg('bailing, found .name %s', fn.name);
    return fn;
  }
  var fnstr = fn.toString();
  var match = reName.exec(fnstr);
  if (!match) {
    lg('bailing, could not match within %s', fnstr);
    return fn;
  }
  fn.name = match[1];
  lg('set .name as %s', fn.name);
  return fn;
}

<|start_filename|>node_modules/vash/lib/util/copyrtl.js<|end_filename|>
module.exports = function(obj) {
  // extend works from right to left, using first arg as target
  var next, i, p;

  for(i = 1; i < arguments.length; i++){
    next = arguments[i];

    for(p in next){
      obj[p] = next[p];
    }
  }

  return obj;
}

<|start_filename|>node_modules/vash/lib/helpers/highlight.js<|end_filename|>
var helpers = require('../../runtime').helpers;

///////////////////////////////////////////////////////////////////////////
// EXAMPLE HELPER: syntax highlighting

helpers.config.highlighter = null;

helpers.highlight = function(lang, cb){

  // context (this) is and instance of Helpers, aka a rendering context

  // mark() returns an internal `Mark` object
  // Use it to easily capture output...
  var startMark = this.buffer.mark();

  // cb() is simply a user-defined function. It could (and should) contain
  // buffer additions, so we call it...
  cb( this.model );

  // ... and then use fromMark() to grab the output added by cb().
  var cbOutLines = this.buffer.fromMark(startMark);

  // The internal buffer should now be back to where it was before this
  // helper started, and the output is completely contained within cbOutLines.

  this.buffer.push( '<pre><code>' );

  if( helpers.config.highlighter ){
    this.buffer.push( helpers.config.highlighter(lang, cbOutLines.join('')).value );
  } else {
    this.buffer.push( cbOutLines );
  }

  this.buffer.push( '</code></pre>' );

  // returning is allowed, but could cause surprising effects. A return
  // value will be directly added to the output directly following the above.
}


<|start_filename|>node_modules/vash/lib/nodes/markupcontent.js<|end_filename|>
var Node = module.exports = function MarkupContentNode() {
  this.type = 'VashMarkupContent';
  this.values = [];
  this.startloc = null;
  this.endloc = null;

  this._waitingForNewline = null;
}

Node.prototype.endOk = function() {
  return this._waitingForNewline
    ? false
    : true;
}

<|start_filename|>node_modules/vash/lib/nodes/block.js<|end_filename|>
var Node = module.exports = function BlockNode() {
  this.type = 'VashBlock';
  this.keyword = null;
  this.head = [];
  this.values = [];
  this.tail = [];
  this.hasBraces = null;
  this.startloc = null;
  this.endloc = null;

  this._reachedOpenBrace = false;
  this._reachedCloseBrace = false;
  this._withinCommentLine = false;
  this._waitingForEndQuote = null;
}

Node.prototype.endOk = function() {
  var gradeSchool = this.hasBraces
    && (!this._reachedOpenBrace || !this._reachedCloseBrace);

  return (gradeSchool || this._withinCommentLine || this._waitingForEndQuote)
    ? false
    : true;
}

<|start_filename|>node_modules/vash/lib/traverse.js<|end_filename|>

var visitorKeys = {
  VashProgram: ['body'],
  VashBlock: ['head', 'values', 'tail'],
  VashExplicitExpression: ['values'],
  VashExpression: ['values'],
  VashIndexExpression: ['values'],
  VashMarkup: ['attributes', 'values'],
  VashMarkupAttribute: ['left', 'right'],
  VashText: []
}

module.exports = function traverse(node, callbacks) {

  (function walk(node, parent) {

    if (!node.parent) {
      node.parent = parent;
    }

    var ret = callbacks.enter(node);

    var candidateKeys = visitorKeys[node.type];
    if (candidateKeys) {
      candidateKeys.forEach(function(key) {
        var valueNodes = node[key];
        if (valueNodes && valueNodes.length) {
          valueNodes.forEach(function(child) {
            walk(child, node);
          })
        }
      })
    }

    ret = callbacks.leave(node);
  }(node, null));
}

<|start_filename|>node_modules/vash/lib/error.js<|end_filename|>

exports.context = function(input, lineno, columnno, linebreak) {
  linebreak = linebreak || '!LB!';

  var lines = input.split(linebreak)
    , contextSize = lineno === 0 && columnno === 0 ? lines.length - 1 : 3
    , start = Math.max(0, lineno - contextSize)
    , end = Math.min(lines.length, lineno + contextSize);

  return lines
    .slice(start, end)
    .map(function(line, i, all){
      var curr = i + start + 1;

      return (curr === lineno ? '  > ' : '    ')
        + (curr < 10 ? ' ' : '')
        + curr
        + ' | '
        + line;
    }).join('\n');
}

<|start_filename|>node_modules/vash/lib/nodes/text.js<|end_filename|>
module.exports = function TextNode() {
  this.type = 'VashText';
  this.value = '';
  this.startloc = null;
  this.endloc = null;
}

<|start_filename|>node_modules/vash/lib/lexer.js<|end_filename|>
var debug = require('debug');
var tokens = require('./tokens');

// This pattern and basic lexer code were originally from the
// Jade lexer, but have been modified:
// https://github.com/visionmedia/jade/blob/master/lib/lexer.js

function VLexer(){
  this.lg = debug('vash:lexer');
  this.input = '';
  this.originalInput = '';
  this.lineno = 1;
  this.charno = 0;
}

module.exports = VLexer;

VLexer.prototype = {

  write: function(input) {
    var normalized = input.replace(/\r\n|\r/g, '\n');

    // Kill BOM if this is the first chunk.
    if (this.originalInput.length == 0) {
      normalized = normalized.replace(/^\uFEFF/, '');
    }

    this.input += normalized;
    this.originalInput += normalized;
    return true;
  },

  read: function() {
    var out = []
      , result;
    while(this.input.length) {
      result = this.advance();
      if (result) {
        out.push(result);
        this.lg('Read %s at line %d, column %d with content %s',
          result.type, result.line, result.chr, result.val.replace(/(\n)/, '\\n'));
      }
    }
    return out;
  },

  scan: function(regexp, type){
    var captures, token;
    if (captures = regexp.exec(this.input)) {
      this.input = this.input.substr((captures[1].length));

      token = {
        type: type
        ,line: this.lineno
        ,chr: this.charno
        ,val: captures[1] || ''
        ,toString: function(){
          return '[' + this.type
            + ' (' + this.line + ',' + this.chr + '): '
            + this.val.replace(/(\n)/, '\\n') + ']';
        }
      };

      this.charno += captures[0].length;
      return token;
    }
  }

  ,advance: function() {

    var i, name, test, result;

    for(i = 0; i < tokens.tests.length; i += 2){
      test = tokens.tests[i+1];
      test.displayName = tokens.tests[i];

      if(typeof test === 'function'){
        // assume complex callback
        result = test.call(this);
      }

      if(typeof test.exec === 'function'){
        // assume regex
        result = this.scan(test, tokens.tests[i]);
      }

      if( result ){
        return result;
      }
    }
  }
}


<|start_filename|>node_modules/vash/test/vows/vash.test.runtime.js<|end_filename|>
var vows = require('vows')
	,assert = require('assert')
	,util = require('util')
	,path = require('path')
	,vm = require('vm')

	,vash = require( process.env.VASHPATH )
	,vruntime = require( process.env.VASHRUNTIMEPATH )

vash.config.useWith = false;
vash.config.debug = false;

require(path.join(path.dirname(process.env.VASHRUNTIMEPATH), 'lib', 'helpers'));

vows.describe('vash templating library runtime').addBatch({

	// TODO:
	/*'requiring': {
		'does not expose global': function() {
			var ctx = {};
			vm.runInNewContext( 'var vash = require("vash/runtime")', ctx );
			assert.equal(ctx.vash, undefined);
		}

		,'does not have full vash': function() {
			assert.equal(vruntime.compile, undefined);
		}
	}

	,*/'default runtime helpers': {

		'highlight': {
			topic: "@html.highlight('javascript', function(){<text>I am code</text>})"
			,'wraps with <pre><code>': function( topic ){
				var tpl = vash.compile( topic );
				assert.equal( tpl(), '<pre><code>I am code</code></pre>' );
			}
		}
	}

	,'decompilation': {

		topic: function(){
			var str = '<p></p>';
			return vash.compile( str, { debug: false, useWith: false } );
		}

		,'of a linked function returns the generated function': function( tpl ){

			var  test = 'function anonymous(model,html,__vopts'
				,result = tpl.toString().indexOf(test);
			assert.strictEqual( result, 0, test )
		}

		,'using .toClientString returns vash.link': function( tpl ){

			var result = tpl.toClientString().match(/^vash\.link\(/g);
			assert.equal( ( result || [''] )[0], 'vash.link(' )
		}

		,'followed by relinking renders': function( tpl ){

			var  client = tpl.toClientString() + '()'
				,actual = vm.runInNewContext( client, { vash: vruntime } );

			assert.equal( actual.toString(), tpl().toString() );
		}

		,'.toClientString, vash.link loop disobeys thermodynamics': function( tpl ){
			var  client = tpl.toClientString() + '.toClientString()'
				,actual = vm.runInNewContext( client, { vash: vruntime } );

			assert.equal( actual, tpl.toClientString() );
		}
	}

	,'installation': {

		topic: ''

		,'uninstalling removes': function(){
			var  str = '<p></p>'
				,tpl = vash.compile(str);

			vash.install('testpath', tpl);

			assert.equal( vash.lookup('testpath')(), str );
			vash.uninstall('testpath');
			assert.throws( function(){
				vash.lookup('testpath');
			}, (/Could not find template: testpath/))
		}

		,'installing with string auto-compiles': function(){

			// Trick the runtime into thinking it's the full thing.
			vruntime.compile = vash.compile;
			vruntime.compileBatch = vash.compileBatch;
			vruntime.compileHelper = vash.compileHelper;

			var  str = '<p></p>'
				,tpl = vash.install('testpath', str);

			delete vruntime.compile;
			delete vruntime.compileBatch;
			delete vruntime.compileHelper;

			assert.equal( tpl(), str );
		}

		,'installing with object installs each separately': function(){
			var  p = '<p></p>'
				,li = '<li></li>'
				,obj = {
					 p: vash.compile(p)
					,li: vash.compile(li)
				}
				,tpl = vash.install(obj);

			assert.equal( vash.lookup('p')(), p );
			assert.equal( vash.lookup('li')(), li );

			vash.uninstall('p');
			vash.uninstall('li');
		}

		,'installing with string throws if only runtime': function(){
			var str = 'vash.install("testpath", "<p></p>")'

			assert.throws(function(){
				vm.runInNewContext( str, { vash: vruntime } );
			}, (/not available/g));
		}

		,'lookup can execute with model': function(){
			var  str = '<p>@model.what</p>'
				,tpl = vash.compile(str);

			vash.install('testpath', tpl);
			assert.equal( vash.lookup('testpath', { what: 'how' }), '<p>how</p>' );
			vash.uninstall('testpath');
		}
	}

	,'compiled helpers': {

		topic: 'vash.helpers.fn = function(id, ctn){'
			+ 'this.fnCounter = this.fnCounter || 0;'
			+ 'this.fnIds = this.fnIds || {};'
			+ 'this.fnCtn = this.fnCtn || {};'
			+ '  if(ctn){'
			+ '    <li id="fn:@id">'
			+ '      @ctn()'
			+ '      <a rev="footnote" href="#fnref:@id">↩</a>'
			+ '  	</li>'
			+ '  } else {'
			+ '    this.fnIds[id] = ++this.fnCounter;'
			+ '    <sup id="fnref:@id">'
			+ '    <a rel="footnote" href="#fn:@id">@html.raw(this.fnCounter)</a>'
			+ '    </sup>'
			+ '  }'
			+ '}'

		,'can be decompiled and relinked': function(topic){

			vash.compileHelper(topic);
			assert.ok( vash.helpers.fn );

			var  str = vash.compile('@html.fn("one") @html.fn("two")').toClientString() + '()'
				,client = vash.helpers.fn.toClientString() + '; \n' + str
				,ctx = { vash: vruntime }

			var actual = vm.runInNewContext( client, ctx );

			assert.ok( actual.indexOf('fnref:one') > -1, 'expect indexOf fnref:one to be > -1' );
			assert.ok( actual.indexOf('fnref:two') > -1, 'expect indexOf fnref:two to be > -1' );
			assert.equal( ctx.fnCtn, undefined, 'expect `this` within helper to be instance' );
		}
	}

	,'runtime error reporting': {

		// "marked line" == line with the >
		// "reported line" == line with code as reported
		// "actual line" == the true/actual source code index

		// NOTE: lineIndexOf only works assuming that the entire test tpl can
		// fit in the "context" of the error message. That means the error
		// must exist within the first 3 lines of the template

		topic: function(){

			// Trick the runtime into thinking it's the full thing.
			vruntime.compile = vash.compile;
			vruntime.compileBatch = vash.compileBatch;
			vruntime.compileHelper = vash.compileHelper;
			vruntime.config = vash.config;

			return {

				getError: function(str, model){
					var tpl = vash.compile(str, { debug: true });
					model = model || {};
					model.settings = model.settings || {};
					model.settings.views = __dirname + '/../fixtures/views/';
					model.settings['view engine'] = 'vash';
					try {
						tpl(model);
					} catch(e) {
						return e;
					}
				}

				,reMarked: /^\s+>\s+[0-9]+\s\|.*?$/gm

				,lineIndexOf: function(message, text){
					// slice(4) because first four lines are
					// err + line + char
					// original message
					// context:
					//  (newline)
					return message.split('\n').slice(4).reduce(function(prev, curr, i){
						var result = curr.match(text)
						//console.log(prev, curr, result)
						if(result) return i + 1; // 1-based index for "lines"
						else return prev;
					}, -1);
				}

				,assert: function(text, topic, badtext, actualLine){
					var  e = topic.getError(text)
						,marked = topic.lineIndexOf(e.message, topic.reMarked)
						,reported = topic.lineIndexOf(e.message, badtext)

					// when debugging, dumping this is useful to clearly see the mismatch
					// console.log(e.message);

					assert.equal( e.vashlineno, actualLine, 'expected error line ' + actualLine + ', got ' + e.vashlineno );
					assert.equal( marked, e.vashlineno, 'expected marked line ' + e.vashlineno + ', got ' + marked );
					assert.equal( reported, e.vashlineno, 'expected reported line ' + e.vashlineno + ' got ' + reported );
				}
			}
		}

		,'in layout in windows': {
			'reports proper line number': function(topic){
				var  str = "@html.extend('layout', function(model){\r\n\r\n<br/>@model.child.xxx\r\n\r\n})"
				topic.assert(str, topic, '@model', 3);
			}
		}

		,'in layout in unix': {
			'reports proper line number': function(topic){
				var  str = "@html.extend('layout', function(model){\n\n<br/>@model.child.xxx\n\n})"
				topic.assert(str, topic, '@model', 3);
			}
		}

		,'on windows': {
			'reports proper line number': function(topic){
				var str = '\r\n\r\n @who'
				topic.assert(str, topic, '@who', 3);
			}
		}

		,'on unix': {
			'reports proper line number': function(topic){
				var str = '\n\n @who'
				topic.assert(str, topic, '@who', 3);
			}
		}
	}

}).export(module)

<|start_filename|>node_modules/vash/lib/helperbatch.js<|end_filename|>

var slice = Array.prototype.slice

  , reHelperFuncHead = /vash\.helpers\.([^= ]+?)\s*=\s*function([^(]*?)\(([^)]*?)\)\s*{/
  , reHelperFuncTail = /\}$/

  , reBatchSeparator = /^\/\/\s*@\s*batch\s*=\s*(.*?)$/

// The logic for compiling a giant batch of templates or several
// helpers is nearly exactly the same. The only difference is the
// actual compilation method called, and the regular expression that
// determines how the giant string is split into named, uncompiled
// template strings.
module.exports = function compile(type, compile, str, options){

  var separator = type === 'helper'
    ? reHelperFuncHead
    : reBatchSeparator;

  var tpls = splitByNamedTpl(separator, str, function(ma, name){
    return name.replace(/^\s+|\s+$/, '');
  }, type === 'helper' ? true : false);

  if(tpls){
    Object.keys(tpls).forEach(function(path){
      tpls[path] = type === 'helper'
        ? compileSingleHelper(compile, tpls[path], options)
        : compile('@{' + tpls[path] + '}', options);
    });

    tpls.toClientString = function(){
      return Object.keys(tpls).reduce(function(prev, curr){
        if(curr === 'toClientString'){
          return prev;
        }
        return prev + tpls[curr].toClientString() + '\n';
      }, '')
    }
  }

  return tpls;
}

// Given a separator regex and a function to transform the regex result
// into a name, take a string, split it, and group the rejoined strings
// into an object.
// This is useful for taking a string, such as
//
//    // tpl1
//    what what
//    and more
//
//    // tpl2
//    what what again
//
// and returning:
//
//    {
//      tpl1: 'what what\nand more\n',
//      tpl2: 'what what again'
//    }
var splitByNamedTpl = function(reSeparator, markup, resultHandler, keepSeparator){

  var  lines = markup.split(/[\n\r]/g)
    ,tpls = {}
    ,paths = []
    ,currentPath = ''

  lines.forEach(function(line, i){

    var  pathResult = reSeparator.exec(line)
      ,handlerResult = pathResult ? resultHandler.apply(pathResult, pathResult) : null

    if(handlerResult){
      currentPath = handlerResult;
      tpls[currentPath] = [];
    }

    if((!handlerResult || keepSeparator) && line){
      tpls[currentPath].push(line);
    }
  });

  Object.keys(tpls).forEach(function(key){
    tpls[key] = tpls[key].join('\n');
  })

  return tpls;
}

var compileSingleHelper = function(compile, str, options){

  options = options || {};

    // replace leading/trailing spaces, and parse the function head
  var  def = str.replace(/^[\s\n\r]+|[\s\n\r]+$/, '').match(reHelperFuncHead)
    // split the function arguments, kill all whitespace
    ,args = def[3].split(',').map(function(arg){ return arg.replace(' ', '') })
    ,name = def[1]
    ,body = str
      .replace( reHelperFuncHead, '' )
      .replace( reHelperFuncTail, '' )

  // Wrap body in @{} to simulate it actually being inside a function
  // definition, since we manually stripped it. Without this, statements
  // such as `this.what = "what";` that are at the beginning of the body
  // will be interpreted as markup.
  body = '@{' + body + '}';

  // `args` and `asHelper` inform `vash.compile/link` that this is a helper
  options.args = args;
  options.asHelper = name;
  return compile(body, options);
}


<|start_filename|>node_modules/vash/test/vows/vash.test.layout.js<|end_filename|>
var vows = require('vows')
	,assert = require('assert')
	,util = require('util')
	,path = require('path')
	,vm = require('vm')

	,vash = require( process.env.VASHPATH )
	,vruntime = require( process.env.VASHRUNTIMEPATH )

var copyrtl = require(path.join(path.dirname(process.env.VASHPATH), 'lib', 'util', 'copyrtl'));
require(path.join(path.dirname(process.env.VASHRUNTIMEPATH), 'lib', 'helpers', 'layout'));

vash.config.useWith = false;
vash.config.debug = false;

vows.describe('vash templating library layout').addBatch({

	'express support': {

		'is present via __express': function() {
			assert.ok(vash.__express);
		}
	}

	,'layout helpers':{

		topic: function(){

			this.opts = function(model){
				return copyrtl( model || {}, {
					// mock up express settings
					settings: {
						views: __dirname + '/../fixtures/views',
						'view engine': 'vash'
					}
					,onRenderEnd: function(err, ctx){
						ctx.finishLayout();
					}
				});
			}

			// and this is how you mock the template cache...
			this.installTplAt = function( filename, str ){
				var opts = this.opts( { cache: true } )
					,base = opts.settings.views
					,p = path.join( base, filename )

				vash.helpers.tplcache[ p ] = vash.compile( str );
				return p;
			}

			this.uninstallTplAt = function( filename ){
				return delete vash.helpers.tplcache[ filename ];
			}

			return this.opts;
		}

		,'p': {
			topic: function(opts){
				vash.loadFile( 'p', opts(), this.callback );
			}

			,'renders': function( err, tpl ){
				assert.equal( tpl( this.opts({ a: 'a' }) ), '<p>a</p>' )
			}
		}

		,'includes': {

			topic: function(opts){
				vash.loadFile( 'list', opts(), this.callback );
			}

			,'renders': function( err, tpl ){
				var actual = tpl( this.opts({ count: 2 }) );

				assert.equal( actual, '<ul><li>a</li><li>a</li></ul>' )
			}

			,'containing redefining block redefines': function( err, tpl ){

				var  parentPath = this.installTplAt( 'parent.vash', '@html.block("c", function(){<p></p>})@html.include("included")' )
					,includedPath = this.installTplAt( 'included.vash', '@html.block("c", function(){<span></span>})' )

					,parent = vash.helpers.tplcache[ parentPath ]

				var actual = parent( this.opts({ cache: true }) );

				assert.equal( actual, '<span></span>' )

				this.uninstallTplAt( parentPath )
				this.uninstallTplAt( includedPath )
			}

			,'given unique model renders': function( err, tpl ){

				var  t = this.installTplAt( 'something/t.vash', '@html.include("something/i.vash", { u: "u" })' )
					,i = this.installTplAt( 'something/i.vash', '<p>@model.u</p>' )

					,opts = this.opts({ cache: true })

				assert.equal( vash.helpers.tplcache[ t ]( opts ), '<p>u</p>' )
			}
		}

		,'block': {

			topic: function(opts){
				return function(before, inner, after){
					before = before || '';
					after = after || '';
					return vash.compile(
						before
						+ '@html.block("main"' + ( inner ? ', function(model){' + inner + '}' : '' ) + ')'
						+ after
					)
				}
			}

			,'renders blank': function( maker ){
				assert.equal( maker()( this.opts() ), '' );
			}

			,'renders replace': function( maker ){
				var ctn = '<p></p>'
					,before = '@html.block("main", function(){' + ctn + '})'

					,actual = maker(before, '', '')( this.opts());

				assert.equal( actual, ctn );
			}

			,'subsequent blocks redefine': function( maker ){
				var ctnA = '<p></p>'
					,ctnB = '<a></a>'
					,before = '@html.block("main", function(){' + ctnA + '})'
						+ '@html.block("main", function(){' + ctnB + '})'

					,actual = maker(before, '', '')( this.opts() );

				assert.equal( actual, ctnB );
			}

			,'renders default content': function( maker ){
				var ctn = '<p></p>'

					,actual = maker('', ctn, '')( this.opts() );

				assert.equal( actual, ctn );
			}
		}

		,'extend': {

			topic: function(opts){
				return function(inner){
					return vash.compile('@html.extend("layout", function(){' + inner + '})');
				}
			}

			,'renders blank': function( maker ){
				assert.equal( maker('')( this.opts() ), '' )
			}

			,'renders expression': function( maker ){
				var actual = maker('')( this.opts({ title: 'is title' }) )
				//console.log('actual', actual)
				assert.equal( actual, 'is title' )
			}

			,'renders content block': function( maker ){
				var block = '@html.block("content", function(model){<p>@model.a</p>})'
				assert.equal( maker(block)( this.opts({ a: 'a' }) ), '<p>a</p>' )
			}

			,'renders content block with include': function( maker ){
				var block = '@html.block("content", function(model){@html.include("p")<p>@model.a</p>})'
				assert.equal( maker(block)( this.opts({ a: 'a' }) ), '<p>a</p><p>a</p>' )
			}

			,'renders content block with multiple include': function( maker ){
				var  incp = '@html.include("p")'
					,mb = '<p>@model.b</p>'
					,block = '@html.block("content", function(model){' + incp + mb + incp + mb + '})'

					,actual = maker(block)( this.opts({ a: 'a', b: 'b' }) )

				//console.log( 'actual', actual )
				assert.equal( actual , '<p>a</p><p>b</p><p>a</p><p>b</p>' );
			}

			,'renders include that appends to content block': function( maker ){
				var footer = '@html.block("footer", function(){ <footer></footer> })'
					,include = '@html.include("footerappend")'
					,block = '@html.block("content", function(){ ' + include + ' })'

					,actual = maker( footer + block )( this.opts() );

				//console.log('UNCOMPILED', footer + block + 'END');
				//console.log('FN', maker( footer + block ))
				//console.log('actual', 'START', actual, 'END');
				assert.equal( actual, '<footer></footer><footer2></footer2>' )
			}

			,'renders appended content block': function( maker ){
				var  incp = '@html.include("p")'
					,appf = '@html.append("footer", function(){<footer></footer>})'
					,block = '@html.block("content", function(model){' + incp + appf + incp + '})'

					,outp = '<p>a</p>'

					,opts = this.opts({ a: 'a' })


				var actual = maker(block)( opts )

				//console.log( 'actual', actual )
				assert.equal( actual, outp + outp + '<footer></footer>' );
			}

			,'renders prepended/appended content block': function( maker ){
				var  incp = '@html.include("p")'
					,appf = '@html.append("footer", function(){<app></app>})'
					,pref = '@html.prepend("footer", function(){<pre></pre>})'
					,block = '@html.block("content", function(model){' + incp + appf + pref + incp + '})'

					,outp = '<p>a</p>'

					,actual = maker(block)( this.opts({ a: 'a' }) )

				//console.log( 'actual', actual )
				assert.equal( actual, outp + outp + '<pre></pre><app></app>' );
			}

			,'subsequent blocks redefine': function( maker ){
				var  ctnA = '<p></p>'
					,ctnB = '<a></a>'
					,block = '@html.block("content", function(){' + ctnA + '})'
						+ '@html.block("content", function(){' + ctnB + '})'

					,actual = maker(block)( this.opts() )

				assert.equal( actual, ctnB );
			}

			,'can handle really long buffers': function( maker ) {

				// Note: this is not as much to handle a long list of items as to
				// ensure that the runtime functions do not exceed the maximum call
				// stack size, which varies per JS engine / platform.
				// See: https://bugzilla.mozilla.org/show_bug.cgi?id=607371
				// and: https://github.com/kirbysayshi/vash/issues/29

				var block = '@html.block("content", function(){'
						+ '@html.include("listitems")'
						+ '})'

				var count = 100000;

				var expected = '';
				for(var i = 0; i < count; i++) {
					expected += '<li>a</li>';
				}

				var actual = maker(block)( this.opts({ count: count }) )

				assert.equal( actual, expected );
			}

			,'deep': {

				topic: function(maker){
					var self = this;

					this.layoutPath = this.installTplAt( 'l.vash',
						"<article>@html.block('content')</article>"
						+ "@html.block('footer', function(){<footer></footer>})" )

					this.extendingPath = this.installTplAt( 'extending.vash',
						"@html.extend('l', function(){"
							+ "<div>"
								+ "@html.block('content', function(){"
									+ "<block></block>"
								+ "})"
							+ "</div>"
						+ "})" )

					return function(before, inner, after){
						before = before || '';
						after = after || '';
						inner = inner || '';
						return vash.compile(
							before
							+ '@html.extend("extending", function(model){' + inner + '})'
							+ after
						)
					}
				}

				,teardown: function(){
					delete vash.helpers.tplcache[ this.layoutPath ];
					delete vash.helpers.tplcache[ this.extendingPath ];
				}

				,'only works on highest block': function( maker ){

					var actual = maker()( this.opts({ cache: true }) );

					assert.equal( actual, '<article><block></block></article><footer></footer>' )
				}

				,'inner wrapped by article': function( maker ){
					var inner = '<inner></inner>'
						,block = '@html.block("content", function(){' + inner + '})'
						,opts = this.opts({ cache: true })
						,actual

					actual = maker( '', block)( opts )

					assert.equal( actual, '<article>' + inner + '</article><footer></footer>' )
				}

				,'blocks only defined in child are ignored': function( maker ){

					var inner = '<inner></inner>'
						,block = '@html.block("inner", function(){' + inner + '})'
						,opts = this.opts({ cache: true })
						,actual

					actual = maker( '', block)( opts )

					assert.equal( actual, '<article><block></block></article><footer></footer>' )
				}

				,'includes can define new blocks if called within a block': function( maker ){

					var name = this.installTplAt( 'i.vash',
						"@html.block('included', function(){<inc></inc>})" );

					var inner = '@html.block("content", function(){@html.include("i")})'
						,tpl = maker( '', inner )

						,actual = tpl( this.opts({cache: true}) )

					assert.equal( actual, '<article><inc></inc></article><footer></footer>')

					delete vash.helpers.tplcache[name];
				}

				,'includes can redefine a block': function( maker ){

					var includedPath = this.installTplAt( 'i.vash', '@html.block("content", function(){ <p></p> })')
						,tpl = vash.compile( '@html.extend("layout",function(){ @html.append("footer", function(){ @html.include("i") }) })' )

					var actual = tpl( this.opts({ cache: true }) )
					assert.equal( actual, '<p></p>' );

					this.uninstallTplAt( includedPath )
				}
			}
		}

		,'extended include': function( opts ){

			// Scenario:
			// A -> index <- included <- B
			//
			//   A         B
			//   |         |
			//   v         |
			//   index     v
			//        included

			var layout = '@html.block(\'bob\')';
			var index = ''
				+ '@html.extend(\'layout\', function(model) {'
					+ '@html.block(\'bob\', function(model) {'
						+ '<h1>Hello from [index.bob]</h1>'
						+ '@html.include(\'templateToInclude\', model)'
						+ '<p></p>'
					+ '})'
				+ '})'
			var include = ''
				+ '@html.extend(\'extendableTemplate\', function(model) {'
					+ '@html.block(\'mary\', function(model) {'
						+ '<h1>Hello from [templateToInclude.mary]</h1>'
					+ '})'
				+ '})'
			var extendable = ''
				+ '<h1>Hello from [extendableTemplate]</h1>'
				+ '@html.block(\'mary\')'

			var a = this.installTplAt('layout.vash', layout);
			var b = this.installTplAt('templateToInclude.vash', include);
			var c = this.installTplAt('extendableTemplate.vash', extendable);

			var opts = this.opts( { cache: true, debug: true })
			var tpl = vash.compile(index, opts);
			var actual = tpl(opts);
			var expected = ''
				+ '<h1>Hello from [index.bob]</h1>'
				+ '<h1>Hello from [extendableTemplate]</h1>'
				+ '<h1>Hello from [templateToInclude.mary]</h1>'
				+ '<p></p>'

			assert.equal(actual, expected);

			this.uninstallTplAt(a);
			this.uninstallTplAt(b);
			this.uninstallTplAt(c);
		}

		,'empty include throws': function() {
			var str = '@html.include("empty.include.vash")';
			var opts = this.opts();
			var tpl = vash.compile(str);
			assert.throws(function() { tpl(opts) }, /Empty or non\-string/g);
		}

		,'empty layout throws': function() {
			var str = '@html.extend("empty.layout.vash")';
			var opts = this.opts();
			var tpl = vash.compile(str);
			assert.throws(function() { tpl(opts) }, /Empty or non\-string/g);
		}

	}

	,'view lookups': {

		topic: function(){

			this.opts = function( viewpath ){
				return {
					settings: {
						views: viewpath,
						'view engine': 'vash'
					}
				}
			}

			return true; // to avoid weird vows async stuff
		}

		,'with extension and engine': function(){
			var path = __dirname + '/../fixtures/views/'
				,opts = this.opts( path )

			vash.loadFile( 'p.vash', opts, function(err, tpl){
				assert.ifError( err );
				assert.equal( tpl(), '<p></p>' )
			})
		}

		,'with extension and not engine': function(){
			var path = __dirname + '/../fixtures/views/'
				,opts = this.opts( path )

			delete opts.settings['view engine'];

			vash.loadFile( 'p.vash', opts, function(err, tpl){
				assert.ifError( err );
				assert.equal( tpl(), '<p></p>' )
			})
		}

		,'without extension and with engine': function(){
			var path = __dirname + '/../fixtures/views/'
				,opts = this.opts( path )

			vash.loadFile( 'p', opts, function(err, tpl){
				assert.ifError( err );
				assert.equal( tpl(), '<p></p>' )
			})
		}

		,'with forward slashes': function( ){
			var path = __dirname + '/../fixtures/views/'

			vash.loadFile( 'p', this.opts( path ), function(err, tpl){
				assert.ifError( err );
				assert.equal( tpl(), '<p></p>' )
			})
		}

		,'with back slashes': function( ){

			// the `path` module normalizes separators based on platform
			// so it's basically impossible to test on non-windows
			if( process.platform === 'win32' ){
				var path = __dirname + '\\..\\fixtures\\views\\'

				vash.loadFile( 'p', this.opts( path ), function(err, tpl){
					assert.ifError( err );
					assert.equal( tpl(), '<p></p>' )
				})
			}
		}
	}

}).export(module)

<|start_filename|>node_modules/vash/lib/nodes/program.js<|end_filename|>
module.exports = function ProgramNode() {
  this.type = 'VashProgram';
  this.body = [];
}

<|start_filename|>node_modules/vash/index.js<|end_filename|>
var debug = require('debug')
var lg = debug('vash:main');

var Lexer = require('./lib/lexer');
var Parser = require('./lib/parser');
var codegen = require('./lib/codegen');
var runtime = require('./runtime');
var helperbatch = require('./lib/helperbatch');
var copyrtl = require('./lib/util/copyrtl');

// Attach all runtime exports to enable backwards compatible behavior,
// like `vash.install` to still be accessible in a full build.
require('./lib/helpers');
copyrtl(exports, runtime);

exports.config = {

  // Parser Options
  favorText: false,
  // TODO: are these even needed with proper codegen?
  saveAT: false,
  saveTextTag: false,

  // Compiler Options
  useWith: false,
  htmlEscape: true,
  helpersName: 'html',
  modelName: 'model',
  debug: true,
  source: null,
  simple: false,

  // Runtime options
  asHelper: false,
  args: null // Internal, for compiled helpers
}

exports.version = require('./package.json').version;

exports.compileStream = function() {
  // This could eventually handle waiting until a `null`
  // is pushed into the lexer, etc.
  throw new Error('NotImplemented');
}

exports.compile = function(markup, options) {

  if(markup === '' || typeof markup !== 'string') {
    throw new Error('Empty or non-string cannot be compiled');
  }

  var opts = copyrtl({}, exports.config, options || {});

  var l = new Lexer();

  l.write(markup);
  var tokens = l.read();

  var p = new Parser(opts);
  p.write(tokens);
  var more = true;
  while(more !== null) more = p.read();

  p.checkStack();

  // Stash the original input (new lines normalized by the lexer).
  opts.source = l.originalInput;

  p.lg(p.dumpAST());

  var compiled = codegen(p.stack[0], opts);
  lg(compiled);
  var tpl = runtime.link(compiled, opts);

  return tpl;
}

///////////////////////////////////////////////////////////////////////////
// VASH.COMPILEHELPER
//
// Allow multiple helpers to be compiled as templates, for helpers that
// do a lot of markup output.
//
// Takes a template such as:
//
//    vash.helpers.p = function(text){
//      <p>@text</p>
//    }
//
// And compiles it. The template is then added to `vash.helpers`.
//
// Returns the compiled templates as named properties of an object.
//
// This is string manipulation at its... something. It grabs the arguments
// and function name using a regex, not actual parsing. Definitely error-
// prone, but good enough. This is meant to facilitate helpers with complex
// markup, but if something more advanced needs to happen, a plain helper
// can be defined and markup added using the manual Buffer API.
exports['compileHelper'] = helperbatch.bind(null, 'helper', exports.compile);

///////////////////////////////////////////////////////////////////////////
// VASH.COMPILEBATCH
//
// Allow multiple templates to be contained within the same string.
// Templates are separated via a sourceURL-esque string:
//
// //@batch = tplname/or/path
//
// The separator is forgiving in terms of whitespace:
//
// // @      batch=tplname/or/path
//
// Is just as valid.
//
// Returns the compiled templates as named properties of an object.
exports['compileBatch'] = exports['batch'] = helperbatch.bind(null, 'batch', exports.compile);

<|start_filename|>node_modules/vash/lib/nodes/comment.js<|end_filename|>
var Node = module.exports = function CommentNode() {
  this.type = 'VashComment';
  this.values = [];
  this.startloc = null;
  this.endloc = null;

  this._waitingForClose = null;
}

Node.prototype.endOk = function() {
  return this._waitingForClose
    ? false
    : true;
}

<|start_filename|>node_modules/vash/lib/helpers/layout.js<|end_filename|>
var helpers = require('../../runtime').helpers;
var copyrtl = require('../util/copyrtl');

// For now, using the layout helpers requires a full build. For now.
var vash = require('../../index');
module.exports = vash;

///////////////////////////////////////////////////////////////////////////
// LAYOUT HELPERS

// semi hacky guard to prevent non-nodejs erroring
// switched from window not existing to global existing
// to avoid conflict with Jest where window is always defined(!)
if( typeof global !== 'undefined' ){
  var  fs = require('fs')
    ,path = require('path')
}

// TRUE implies that all TPLS are loaded and waiting in cache
helpers.config.browser = false;

vash.loadFile = function(filepath, options, cb){

  // options are passed in via Express
  // {
  //   settings:
  //   {
  //      env: 'development',
  //    'jsonp callback name': 'callback',
  //    'json spaces': 2,
  //    views: '/Users/drew/Dropbox/js/vash/test/fixtures/views',
  //    'view engine': 'vash'
  //   },
  //   _locals: [Function: locals],
  //   cache: false
  // }

  // The only required options are:
  //
  // settings: {
  //     views: ''
  // }

  options = copyrtl({}, vash.config, options || {});

  var browser = helpers.config.browser
    ,tpl

  if( !browser && options.settings && options.settings.views ){
    // this will really only have an effect on windows
    filepath = path.normalize( filepath );

    if( filepath.indexOf( path.normalize( options.settings.views ) ) === -1 ){
      // not an absolute path
      filepath = path.join( options.settings.views, filepath );
    }

    if( !path.extname( filepath ) ){
      filepath += '.' + ( options.settings['view engine'] || 'vash' )
    }
  }

  // TODO: auto insert 'model' into arguments
  try {
    // if browser, tpl must exist in tpl cache
    tpl = options.cache || browser
      ? helpers.tplcache[filepath] || ( helpers.tplcache[filepath] = vash.compile(fs.readFileSync(filepath, 'utf8')) )
      : vash.compile( fs.readFileSync(filepath, 'utf8') )

    cb && cb(null, tpl);
  } catch(e) {
    cb && cb(e, null);
  }
}

vash.renderFile = vash.__express = function(filepath, options, cb){

  vash.loadFile(filepath, options, function(err, tpl){
    // auto setup an `onRenderEnd` callback to seal the layout
    var prevORE = options.onRenderEnd;

    cb( err, !err && tpl(options, function(err, ctx){
      ctx.finishLayout()
      if( prevORE ) prevORE(err, ctx);
    }) );
  })
}

helpers._ensureLayoutProps = function(){
  this.appends = this.appends || {};
  this.prepends = this.prepends || {};
  this.blocks = this.blocks || {};

  this.blockMarks = this.blockMarks || {};
}

helpers.finishLayout = function(){
  this._ensureLayoutProps();

  var self = this, name, marks, blocks, prepends, appends, injectMark, m, content, block

  // each time `.block` is called, a mark is added to the buffer and
  // the `blockMarks` stack. Find the newest/"highest" mark on the stack
  // for each named block, and insert the rendered content (prepends, block, appends)
  // in place of that mark

  for( name in this.blockMarks ){

    marks = this.blockMarks[name];

    prepends = this.prepends[name];
    blocks = this.blocks[name];
    appends = this.appends[name];

    injectMark = marks.pop();

    // mark current point in buffer in prep to grab rendered content
    m = this.buffer.mark();

    prepends && prepends.forEach(function(p){ self.buffer.pushConcat( p ); });

    // a block might never have a callback defined, e.g. is optional
    // with no default content
    block = blocks.pop();
    block && this.buffer.pushConcat( block );

    appends && appends.forEach(function(a){ self.buffer.pushConcat( a ); });

    // grab rendered content
    content = this.buffer.fromMark( m )

    // Join, but split out the VASHMARKS so further buffer operations are still
    // sane. Join is required to prevent max argument errors when large templates
    // are being used.
    content = compactContent(content);

    // Prep for apply, ensure the right location (mark) is used for injection.
    content.unshift( injectMark, 0 );
    this.buffer.spliceMark.apply( this.buffer, content );
  }

  for( name in this.blockMarks ){

    // kill all other marks registered as blocks
    this.blockMarks[name].forEach(function(m){ m.destroy(); });
  }

  // this should only be able to happen once
  delete this.blockMarks;
  delete this.prepends;
  delete this.blocks;
  delete this.appends;

  // and return the whole thing
  return this.toString();
}

// Given an array, condense all the strings to as few array elements
// as possible, while preserving `Mark`s as individual elements.
function compactContent(content) {
  var re = vash.Mark.re;
  var parts = [];
  var str = '';

  content.forEach(function(part) {
    if (re.exec(part)) {
      parts.push(str, part);
      str = '';
    } else {
      // Ensure `undefined`s are not `toString`ed
      str += (part || '');
    }
  });

  // And don't forget the rest.
  parts.push(str);

  return parts;
}

helpers.extend = function(path, ctn){
  var  self = this
    ,buffer = this.buffer
    ,origModel = this.model
    ,layoutCtx;

  this._ensureLayoutProps();

  // this is a synchronous callback
  vash.loadFile(path, this.model, function(err, tpl){

    if (err) throw err;

    // any content that is outside of a block but within an "extend"
    // callback is completely thrown away, as the destination for such
    // content is undefined
    var start = self.buffer.mark();

    ctn(self.model);

    // ... and just throw it away
    var  content = self.buffer.fromMark( start )
      // TODO: unless it's a mark id? Removing everything means a block
      // MUST NOT be defined in an extend callback
      //,filtered = content.filter( vash.Mark.uidLike )

    //self.buffer.push( filtered );

    // `isExtending` is necessary because named blocks in the layout
    // will be interpreted after named blocks in the content. Since
    // layout named blocks should only be used as placeholders in the
    // event that their content is redefined, `block` must know to add
    // the defined content at the head or tail or the block stack.
    self.isExtending = true;
    tpl( self.model, { context: self } );
    self.isExtending = false;
  });

  this.model = origModel;
}

helpers.include = function(name, model){

  var  self = this
    ,buffer = this.buffer
    ,origModel = this.model;

  // TODO: should this be in a new context? Jade looks like an include
  // is not shared with parent context

  // this is a synchronous callback
  vash.loadFile(name, this.model, function(err, tpl){
    if (err) throw err;
    tpl( model || self.model, { context: self } );
  });

  this.model = origModel;
}

helpers.block = function(name, ctn){
  this._ensureLayoutProps();

  var  self = this
    // ensure that we have a list of marks for this name
    ,marks = this.blockMarks[name] || ( this.blockMarks[name] = [] )
    // ensure a list of blocks for this name
    ,blocks = this.blocks[name] || ( this.blocks[name] = [] )
    ,start
    ,content;

  // render out the content immediately, if defined, to attempt to grab
  // "dependencies" like other includes, blocks, etc
  if( ctn ){
    start = this.buffer.mark();
    ctn( this.model );
    content = this.buffer.fromMark( start );

    // add rendered content to named list of blocks
    if( content.length && !this.isExtending ){
      blocks.push( content );
    }

    // if extending the rendered content must be allowed to be redefined
    if( content.length && this.isExtending ){
      blocks.unshift( content );
    }
  }

  // mark the current location as "where this block will end up"
  marks.push( this.buffer.mark( 'block-' + name ) );
}

helpers._handlePrependAppend = function( type, name, ctn ){
  this._ensureLayoutProps();

  var start = this.buffer.mark()
    ,content
    ,stack = this[type]
    ,namedStack = stack[name] || ( stack[name] = [] )

  ctn( this.model );
  content = this.buffer.fromMark( start );

  namedStack.push( content );
}

helpers.append = function(name, ctn){
  this._handlePrependAppend( 'appends', name, ctn );
}

helpers.prepend = function(name, ctn){
  this._handlePrependAppend( 'prepends', name, ctn );
}


<|start_filename|>node_modules/vash/test/vows/vash.test.js<|end_filename|>
var vows = require('vows')
	,assert = require('assert')
	,util = require('util')
	,path = require('path')
	,vm = require('vm')

	,vash = require( process.env.VASHPATH )

vash.config.useWith = false;
vash.config.debug = true;

var tryCompile = function(str){
	vash.config.useWith = true;
	vash.compile(str); // because we want to see the stupid error
	assert.doesNotThrow( function(){ vash.compile(str) }, Error );

	var ret;

	try {
		ret = vash.compile(str);
	} catch(e){
		ret = function(){};
	}
	vash.config.useWith = false;
	return ret;
};

// SUPER HACK:
var aeToStringVows = assert.AssertionError.prototype.toString;
assert.AssertionError.prototype.toString = function wrapper() {
	// ...Put the default back to get around vows#278 caused by recursively
	// calling itself.
	assert.AssertionError.prototype.toString = Error.prototype.toString;
	var out = aeToStringVows.call(this);
	assert.AssertionError.prototype.toString = wrapper;
	return out;
}

vows.describe('vash templating library').addBatch({

	'a plain text template': {
		topic: function(){
			var tpl = vash.compile('<a href="">this is a <br /> simple template</a>');
			return tpl;
		}
		,'sends back the same plain text': function(topic){
			assert.equal( topic(), '<a href="">this is a <br /> simple template</a>');
		}
	}
	,'during "why are you using a template?"-style idiotic edge-cased interpolation': {
		topic: function(){
			return vash.compile('@i', { htmlEscape: false, useWith: true });
		}
		,'we get 2 from just @i': function(topic){
			assert.equal( topic({ i: 2 }), 2 );
		}
		,'we get <li class="what"></li> from just @i': function(topic){
			assert.equal( topic({ i: '<li class="what"></li>' }), '<li class="what"></li>' );
		}
	}
	,'during simple interpolation': {
		topic: function(){
			var str = '<li class="@className">@itemName</li>';
			//console.log( vash._parse(str) )
			return vash.compile(str, { useWith: true });
		}
		,'we get <li class="blue">the blue item</li>': function(topic){
			//console.log(topic);
			assert.equal(
				topic( {
					className: 'blue'
					,itemName: 'the blue item' } )
				,'<li class="blue">the blue item</li>' );
		}
	}
	,'during simple interpolation with self-closing tags': {
		topic: function(){
			var str = '<img src="@src" alt="github" />';
			//console.log( vash._parse(str) )
			return vash.compile(str, { useWith: true });
		}
		,'we get the full self-closed tag': function(topic){
			//console.log(topic);
			assert.equal(
				topic( { src: 'https://github.com' } )
				,'<img src="https://github.com" alt="github" />' );
		}
	}
	,'property references': {
		topic: function(){
			var str = '<li>@model.name</li>'
			return vash.compile(str, { useWith: true });
		}
		,'are interpolated': function(topic){
			assert.equal( topic({ model: {name: 'what'}}), "<li>what</li>" );
		}
	}
	,'deep property references': {
		topic: function(){
			return '@model.repository.url/tree/@model.payload.ref'
		}
		,'are fine': function(topic){
			var tpl = vash.compile(topic, { useWith: false });
			assert.equal( tpl({ repository: { url: 'URL' }, payload: { ref: 'REF' } }), 'URL/tree/REF' );
		}
	}
	,'property references with whitespace': {
		topic: function(){
			return '@model.actor created a @model.payload'
		}
		,'are not contiguous': function(topic){
			var tpl = vash.compile(topic, { useWith: false });
			assert.equal( tpl({ actor: 'act', payload: 'pay' }), 'act created a pay' );
		}
	}
	,'ellipses': {
		topic: function(){
			return "@(model.payload.desc.substring(0, 4) + '...')";
		}
		,'are not infinite': function(topic){
			var tpl = vash.compile(topic, { useWith: false });
			assert.equal( tpl({ payload: { desc: 'description!!!' } }), 'desc...' );
		}
	}
	,'.. (double dot notation)': {
		topic: function(){
			return "@( false || 1..toString() )";
		}
		,'is valid in expression': function(topic){
			var tpl = vash.compile(topic);
			assert.equal( tpl(), '1' );
		}
	}
	,'for blocks': {
		topic: function(){
			var str = '@for(var i = 0; i < 10; i++){ \n }';
			return vash.compile(str);
		}
		,'output nothing': function(topic){
			assert.equal( topic(), '' );
		}
	}
	,'for blocks and markup': {
		topic: function(){
			var str = "@for(var i = 0; i < 1; i++){ <li class=\"\">list item</li> \n }";
			return vash.compile(str);
		}
		,'output markup': function(topic){
			assert.equal(topic(), '<li class="">list item</li>');
		}
	}
	,'for blocks and markup with interpolation/expression': {
		topic: function(){
			var str = "@for(var i = 0; i < 1; i++){ <li class=\"@i\">list item</li> \n }";
			return vash.compile(str, { useWith: true });
		}
		,'output markup': function(topic){
			assert.equal(topic(), '<li class="0">list item</li>');
		}
	}
	,'for blocks and markup with complex interpolation/expression': {
		topic: function(){
			var str = "@for(var i = 0; i < 1; i++){ <li class=\"@(i % 2 == 0 ? \"blue\" : \"red\")\">list item</li> \n }";
			return vash.compile(str, { useWith: true });
		}
		,'output markup': function(topic){
			assert.equal(topic(), '<li class="blue">list item</li>');
		}
	}
	,'nested for blocks and markup with complex interpolation/expression': {
		topic: function(){
			var str = "@for(var i = 0; i < 1; i++){ for(var j = 0; j < 2; j++) { <li class=\"@(i % 2 == 0 ? \"blue\" : \"red\")\">list item</li> \n } }";
			return vash.compile(str, { useWith: true });
		}
		,'output markup': function(topic){
			assert.equal(topic(), '<li class="blue">list item</li><li class="blue">list item</li>');
		}
	}
	,'nested for blocks on new lines with markup and even more complex interpolation/expressions': {
		topic: function(){
			var str = "@for(var i = 0; i < somearr.length; i++){ \n"
				+ "	<li class=\"@(i % 2 === 0 ? 'even' : 'odd')\">Some element, number @i, value @somearr[i]</li> \n"
				+ "	@for(var j = 0; j < anotherarr.length; j++){"
				+ "		<li class=\"@j-what\">some text, @( (j+2) % 2 === 0 ? 'even' : 'odd' ), value @anotherarr[j]</li> \n"
				+ "	}"
			+ "}";
			return vash.compile(str, { useWith: true });
		}
		,'output markup': function(topic){
			var model = {
				somearr: ['a', 'b']
				,anotherarr: ['z', 'y']
			};

			assert.equal(topic(model), '<li class="even">Some element, number 0, value a</li><li class="0-what">some text, even, value z</li><li class="1-what">some text, odd, value y</li><li class="odd">Some element, number 1, value b</li><li class="0-what">some text, even, value z</li><li class="1-what">some text, odd, value y</li>');
		}
	}
	,'forEach': {
		'and markup with complex interpolation/expression': {
			topic: function(){
				var str = '@model.forEach( function(p){ <li class="@(p.x % 2 == 0 ? \'blue\' : \'red\')">list item</li> })';
				return vash.compile(str);
			}
			,'output markup': function(topic){
				var model = [{ x: 0, y: 1 }];
				assert.equal(topic(model), '<li class="blue">list item</li>');
			}
		}
		,'wrapped in tags': {
			topic: function(){
				var str = '<ul>@model.forEach( function(p){ <li>@p</li> })</ul>';
				return str;
			}
			,'output markup': function(topic){
				var topic = vash.compile(topic);
				var model = ['a', 'b'];
				assert.equal(topic(model), '<ul><li>a</li><li>b</li></ul>');
			}
		}
		,'no whitespace': {
			topic: function(){
				var str = '<ul class="friends">@friends.forEach(function(friend){<li></li>})</ul>';
				return str;
			}
			,'output markup': function(topic){
				var topic = vash.compile(topic, {useWith: true})
					,model = { friends: [ 'a' ] };

				assert.equal( topic(model), '<ul class="friends"><li></li></ul>' );
			}
		}
	}
	,'empty try/catch block': {
		topic: function(){
			var str = "@try { var i = 0; } catch(e){  }";
			return vash.compile(str);
		}
		,'outputs nothing': function(topic){
			assert.equal(topic(), '')
		}
	}
	,'when try/catch block throws exception': {
		topic: function(){
			var str = "@try { throw new Error('error') } catch(e){ <li>list item</li> \n }";
			return vash.compile(str);
		}
		,'catch block outputs markup': function(topic){
			assert.equal(topic(), '<li>list item</li>')
		}
	}
	,'when try/catch block does not throw exception': {
		topic: function(){
			var str = "@try { <li>list item</li> \n } catch(e){  }";
			return vash.compile(str);
		}
		,'try block outputs markup': function(topic){
			assert.equal(topic(), '<li>list item</li>')
		}
	}
	,'when try/catch/finally block does not throw exception': {
		topic: function(){
			var str = "@try { <li>list item</li> \n } catch(e){  } finally{ <li>list item 2</li> \n }";
			return vash.compile(str);
		}
		,'try block outputs markup': function(topic){
			assert.equal(topic(), '<li>list item</li><li>list item 2</li>')
		}
	}
	,'simple expression': {
		topic: function(){
			var str = '<a href="@(true)"></a>';
			return vash.compile(str);
		}
		,'outputs true': function(topic){
			assert.equal(topic(), '<a href="true"></a>');
		}
	}
	,'simple expression with valid identifier following': {
		topic: function(){
			var str = '<a href="@(true)that"></a>';
			return str;
		}
		,'outputs true': function(topic){
			var tpl = tryCompile(topic);
			assert.equal(tpl(), '<a href="truethat"></a>');
		}
	}
	,'expression with nested parenthesis': {
		topic: function(){
			var str = '<a href="@( true == (Math.random() + 1 >= 1 ? true : false) ? "red" : "blue" )"></a>';
			return vash.compile(str);
		}
		,'outputs red': function(topic){
			assert.equal(topic(), '<a href="red"></a>');
		}
	}
	,'expression with indexed properties': {
		topic: function(){
			var str = '<a href="@what.how[0]"></a>';
			return vash.compile(str, { useWith: true });
		}
		,'outputs G': function(topic){
			assert.equal( topic({ what: { how: 'G' }}), '<a href="G"></a>');
		}
	}
	,'expression with indexed properties and method call': {
		topic: function(){
			var str = '<a href="@what.how()[0]"></a>';
			return vash.compile(str, { useWith: true });
		}
		,'outputs G': function(topic){
			assert.equal( topic({ what: { how: function() { return 'G'; } }}), '<a href="G"></a>');
		}
	}
	,'expression with indexed properties and method call with additional property': {
		topic: function(){
			var str = '<a href="@what.how()[0].length"></a>';
			return vash.compile(str, { useWith: true });
		}
		,'outputs 1': function(topic){
			assert.equal( topic({ what: { how: function() { return 'G'; } }}), '<a href="1"></a>');
		}
	}
	,'expression with indexed property followed by valid identifer': {
		topic: function(){
			var str = '<a href="@what[0]yeah"></a>';
			return vash.compile(str, { useWith: true });
		}
		,'outputs 1yeah': function(topic){
			assert.equal( topic({ what: '1'}), '<a href="1yeah"></a>');
		}
	}
	,'explicit expression followed by bracket, @escape': {
		topic: function(){
			var str = '<a href="somename_@(what.how)@[0]"></a>';
			return vash.compile(str, { useWith: true });
		}
		,'uses brackets as markup': function(topic){
			assert.equal( topic({ what: { how: 'yes' }}), '<a href="somename_yes[0]"></a>');
		}
	}
	,'explicit expression followed by bracket': {
		topic: function(){
			var str = '<a href="somename_@(what.how)[0]"></a>';
			return vash.compile(str, { useWith: true });
		}
		,'uses brackets as markup': function(topic){
			assert.equal( topic({ what: { how: 'yes' }}), '<a href="somename_yes[0]"></a>');
		}
	}
	,'expression followed by empty bracket': {
		topic: function(){
			var str = '<a href="somename_@what.how[]"></a>';
			return vash.compile(str, { useWith: true });
		}
		,'uses brackets as markup': function(topic){
			assert.equal( topic({ what: { how: 'yes' }}), '<a href="somename_yes[]"></a>');
		}
	}
	,'explicit expression with parens as immediate child': {
		topic: function(){
			var str = '@((1) > 0)';
			return vash.compile(str);
		}
		,'create a separate explicit expression': function(topic){
			assert.equal(topic(), 'true');
		}
	}

	,'explicit expressions containing quoted characters': {

		topic: function() {

			// For now just include ASCII.
			var UNICODE_MAX = 127; // 1114111;

			// 0-31 are control characters.
			for(var i = 32, chars = []; i <= UNICODE_MAX; i++) {
				chars.push(String.fromCharCode(i));
			}

			return chars;
		}

		,'do not need to be escaped': function(chars){

			var str = chars.map(wrapCharWithExpression).join('');
			var tpl = vash.compile(str, { htmlEscape: false });
			var expected = chars.map(wrapCharWithP);
			assert.equal( tpl(), expected.join('') );

			function wrapCharWithExpression(chr) {
				return '<p>@("' + escapeIfNeeded(chr) + '")</p>\n';
			}

			function wrapCharWithP(chr) {
				return '<p>' + chr + '</p>\n';
			}

			function escapeIfNeeded(chr) {
				if (chr === '"' || chr === '\n' || chr === '\\') return '\\' + chr;
				else return chr;
			}
		}
	}

	,'a parent expression': {

		topic: '@(function() { <p>)</p> }())'

		,'should not consume content PAREN_CLOSE': function(topic) {
			var tpl = vash.compile(topic);
			assert.equal( tpl(), '<p>)</p>' );
		}
	}

	,'a parent block': {

		topic: '@{ function what() { <p>}</p> } }'

		,'should not consume content BRACE_CLOSE': function(topic) {
			var tpl = vash.compile(topic);
			assert.equal( tpl(), '' );
		}
	}

	,'anonymous blocks': {

		'empty,': {
			topic: function(){
				var str = "@{ }";
				return vash.compile(str);
			}
			,'outputs nothing': function(topic){
				assert.equal(topic(), '');
			}
		}
		,'empty, with same-line markup': {
			topic: function(){
				var str = "@{ <li>list item</li> }";
				return vash.compile(str);
			}
			,'outputs markup': function(topic){
				assert.equal(topic(), '<li>list item</li>');
			}
		}
		,'and markup with quotes': {
			topic: function(){
				var str = "@{ <li class=\"1\">list item</li> \n }";
				return vash.compile(str);
			}
			,'outputs markup': function(topic){
				assert.equal(topic(), '<li class="1">list item</li>');
			}
		}
		,'and nested markup': {
			topic: function(){
				var str = "@{ <li class=\"1\">list item</li> @{ <li class=\"2\">list item</li> } }";
				return vash.compile(str);
			}
			,'outputs markup': function(topic){
				assert.equal(topic(), '<li class=\"1\">list item</li><li class=\"2\">list item</li>');
			}
		}
		,'and nested for loop': {
			topic: function(){
				var str = "@{ <li class=\"1\">list item</li> @for(var i = 0; i < 1; i++){ <li class=\"2\">list item</li> } }";
				return vash.compile(str);
			}
			,'outputs markup': function(topic){
				assert.equal( topic(), '<li class=\"1\">list item</li><li class=\"2\">list item</li>' );
			}
		}
		,'and named function defined': {
			topic: function(){
				var str = "@{ <li class=\"1\">list item</li> @function testFunc(param1, param2){ <li class=\"2\">list item</li> } }";
				return vash.compile(str);
			}
			,'outputs non-function defined markup': function(topic){
				assert.equal( topic(), '<li class=\"1\">list item</li>' );
			}
		}
		,'and named function defined and called': {
			topic: function(){
				var str = "@{ <li class=\"1\">list item</li> @function testFunc(param1, param2){ <li class=\"2\">list item</li> \n } testFunc(); \n }";
				return vash.compile(str);
			}
			,'outputs non-function defined markup': function(topic){
				assert.equal( topic(), '<li class=\"1\">list item</li><li class=\"2\">list item</li>' );
			}
		}
		,'and while loop with manual increment': {
			topic: function(){
				var str = "@{ var countNum = 0; while(countNum < 1){ \n countNum += 1; \n <p>Line #@countNum</p> \n } }";
				return vash.compile(str);
			}
			,'outputs 1 line': function(topic){
				assert.equal( topic(), '<p>Line #1</p>');
			}
		}
		,'and while loop with manual increment post': {
			topic: function(){
				var str = "@{ var countNum = 0; while(countNum < 2){ \n countNum += 1; \n <p>Line #@countNum</p> \n countNum += 1; \n } }";
				return vash.compile(str);
			}
			,'outputs 1 line': function(topic){
				assert.equal( topic(), '<p>Line #1</p>');
			}
		}
	}
	,'immediate function invocation within expression': {
		topic: function(){
			var str = '<a>@(false || (function(){ <b>YES</b> })())</a>';
			return vash.compile(str, { debugCompiler: false });
		}
		,'returns properly': function(topic){
			assert.equal( topic(), '<a><b>YES</b></a>' )
		}
	}

	,'array literal within markup': {
		topic: '<a>@["a", "b", "c"].join("")</a>'
		,'outputs': function(topic){
			var tpl = vash.compile(topic);
			assert.equal( tpl(), '<a>abc</a>' );
		}
	}

	,'function invocation within expression buffers': {
		topic: function(){
			var str = '<a>@model.map(function(l){ return "__" + l + "__";  }).forEach(function(l){ <b>@l</b> })</a>';
			return vash.compile(str, { debugCompiler: false });
		}
		,'returns properly': function(topic){
			assert.equal( topic(["a", "b", "c"]), '<a><b>__a__</b><b>__b__</b><b>__c__</b></a>' )
		}
	}
	,'<text> escape': {

		'single line': {

			topic: function(){
				var str = '@if (true) { \n'
					+ '<text>Plain Text</text>\n'
					+ '}';
				return vash.compile(str);
			}
			,'outputs plain text': function(topic){
				assert.equal( topic(), 'Plain Text' );
			}

		}

		,'multiple lines': {

			topic: function(){
				var str = '@if (true) { \n'
					+ '<text>Plain Text \n Plain Text \n</text>\n'
					+ '}';
				return vash.compile(str);
			}
			,'outputs plain text': function(topic){
				assert.equal( topic(), 'Plain Text \n Plain Text \n' );
			}

		}

		,'can be escaped': {
			topic: '@("<text>")More@("</text>")'
			,'with html escaping': function(topic){
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '&lt;text&gt;More&ls;/text&gt;' );
			}
		}

		,'can be escaped': {
			topic: '@html.raw("<text>")More@html.raw("</text>")'
			,'without html escaping': function(topic){
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '<text>More</text>' );
			}
		}
	}

	,'@: escape': {
		'single line': {
			topic: function(){
				var str = '@if (true) { \n'
					+ '@:Plain Text\n'
					+ '}';
				return str;
			}
			,'outputs plain text': function(topic){
				var tpl = vash.compile(topic);
				assert.equal( tpl(), 'Plain Text\n' );
			}
		}
		,'multiple lines': {
			topic: function(){
				var str = '@if (true) { \n'
					+ '@:Plain Text\n'
					+ '@:Plain Text\n'
					+ '}';
				return str;
			}
			,'outputs plain text': function(topic){
				var tpl = vash.compile(topic)
				assert.equal( tpl(), 'Plain Text\nPlain Text\n' );
			}
		}
		,'single line with content on next line': {
			topic: function(){
				var str = '@if (true) { \n'
					+ '@:Plain Text\n'
					+ 'var a = "what";'
					+ '}';
				return str;
			}
			,'throws error': function(topic){
				var tpl = vash.compile(topic);
				assert.equal( tpl(), 'Plain Text\n' );
			}
		}

		,'can be escaped': {
			topic: '<p>@@:</p>'

			,'and is': function(topic){
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '<p>@:</p>' );
			}
		}
	}

	,'markup within a code block': {
		topic: function(){
			var str = '@if(true){ \n'
				+ '<span>this is text \n'
				+ 'that spans multiple lines</span> \n'
				+ '}';
			return str;
		}
		,'disregards newline re-entry into BLK mode': function(topic){
			var tpl = tryCompile(topic);
			assert.equal(tpl(), '<span>this is text \nthat spans multiple lines</span>');
		}
	}
	,'markup within a code block followed by else': {
		topic: function(){
			var str = '@if(true){ \n'
				+ '<span>this is text \n'
				+ 'that spans multiple lines</span> \n'
				+ '} else { \n'
				+ '<span>different text</span> \n'
				+ '}';
			return str;
		}
		,'disregards newline re-entry into BLK mode': function(topic){
			var tpl = tryCompile(topic);
			assert.equal(tpl(), '<span>this is text \nthat spans multiple lines</span>');
		}
	}
	,'markup within a code block with if/else on new lines': {
		topic: '@if(true)\n{\n<strong>Hello</strong>\n}\n    else\n{\n    <strong>World!</strong>\n    }'
		,'outputs': function(topic) {
			var tpl = vash.compile(topic);
			assert.equal(tpl(), '<strong>Hello</strong>');
		}
	}
	,'markup within a code block followed by else with markup': {
		topic: function(){
			var str = '@if(false){ \n'
				+ '<span>this is text \n'
				+ 'that spans multiple lines</span> \n'
				+ '} else { \n'
				+ '<span>different text</span> \n'
				+ '}';
			return str;
		}
		,'disregards newline re-entry into BLK mode': function(topic){
			var tpl = tryCompile(topic);
			assert.equal(tpl(), '<span>different text</span>');
		}
	}
	,'markup within a code block with an expression in the tag name': {
		topic: function(){
			var str = '@if(true){ \n'
				+ '<span-@name>this is text \n'
				+ 'that spans multiple lines</span-@name> \n'
				+ '}';
			return str;
		}
		,'parses': function(topic){

			var tpl = tryCompile(topic);
			assert.equal(tpl({ name: 'what' }), '<span-what>this is text \nthat spans multiple lines</span-what>');
		}
	}
	,'markup within a code block with an expression after the tag name': {
		topic: function(){
			var str = '@if(true){ \n'
				+ '<span-@name>this is text \n'
				+ 'that spans multiple lines</span-@name> \n'
				+ '<span class="@name">this is text \n'
				+ 'that spans multiple lines</span> \n'
				+ '}';
			return str;
		}
		,'parses': function(topic){
			var tpl = tryCompile(topic);
			assert.equal(tpl({ name: 'what' }), '<span-what>this is text \nthat spans multiple lines</span-what><span class="what">this is text \nthat spans multiple lines</span>');
		}
	}
	,'markup within a code block within markup within a code block': {
		topic: function(){
			var str = '@if(true){ \n'
				+ '<span>this is text \n'
				+ '@if(true){ <b>important</b> \n } '
				+ 'that spans multiple lines</span> \n'
				+ '}';
			return str;
		}
		,'nests properly': function(topic){
			var tpl = tryCompile(topic);
			assert.equal(tpl(), '<span>this is text \n<b>important</b> that spans multiple lines</span>');
		}
	}
	,'markup within a code block within markup within a code block with keyword': {
		topic: function(){
			var str = '@if(true){ \n'
				+ '<span>this is text \n'
				+ '@if(true){ <b>delete</b> \n } '
				+ 'that spans multiple lines</span> \n'
				+ '}';
			return str;
		}
		,'nests properly': function(topic){
			var tpl = tryCompile(topic);
			assert.equal(tpl(), '<span>this is text \n<b>delete</b> that spans multiple lines</span>');
		}
	}
	,'markup within markup within a block': {
		topic: function(){
			var str = '@if(true){ <p>This is content that is <strong>important</strong> but outside.</p> }'
			return str;
		}
		,'is consumed by markup, not block': function(topic){
			assert.equal(vash.compile(topic)(), '<p>This is content that is <strong>important</strong> but outside.</p>');
		}
	}
	,'self-closing tag within tag within BLK': {

		topic: '@if(true){ <p>Hello<br />world</p> }'

		,'does not prematurely exit MKP': function(topic){
			var  tpl = vash.compile(topic)
				,expected = '<p>Hello<br />world</p>';

			assert.equal(tpl(), expected);
		}

		,'does not prematurely exit MKP, even without closing /': function(topic){
			var  tpl = vash.compile(topic.replace('<br />', '<br>'))
				,expected = '<p>Hello<br>world</p>';

			assert.equal(tpl(), expected);
		}
	}
	,'self-closing tag within BLK': {

		topic: '@if(true){ <br /> true; }'

		,'exits MKP': function(topic){
			var  tpl = vash.compile(topic)
				,expected = '<br />';

			assert.equal(tpl(), expected);
		}

		,'exits MKP with expression': function(topic){
			var str = topic.replace('/>', '@(true)/>')
			var  tpl = vash.compile(str)
				,expected = '<br true />';

			assert.equal(tpl(), expected);
		}

		/*
		,'exits MKP without closing /': function(topic){
			var  tpl = vash.compile(topic.replace('/>', '>'))
				,expected = '<br >';

			assert.equal(tpl(), expected);
		}*/
	}
	,'if statement is not confused': {

		topic: '@{ if(-1 <= 0){ <br /> } }'

		,'with self-closing tag': function(topic){
			var tpl = vash.compile(topic)
				,expected = '<br />';

			assert.equal(tpl(), expected);
		}
	}
	,'self-closing tag containing >': {

		topic: '<button data-bind="enable: @model.length > 0" />'

		,'compiles and renders': function(topic) {
			var tpl = vash.compile(topic)
				,expected = '<button data-bind="enable: 0 > 0" />';

			assert.equal(tpl([]), expected);
		}
	}

	,'self-closing tag containing > with no whitespace': {

		topic: '<button data-bind="enable:@model.length>0"/>'

		,'compiles and renders': function(topic) {
			var tpl = vash.compile(topic)
				,expected = '<button data-bind="enable:0>0" />';

			assert.equal(tpl([]), expected);
		}
	}

	,'self-closing tag does not grab too much': {

		topic: '@{{<b ></b>}<img />}'

		,'when preceeded by tag with whitespace': function(topic) {
			var tpl = vash.compile(topic)
				, expected = '<b></b><img />'

			assert.equal(tpl(), expected);
		}
	}

	,'markup with numbers': {
		topic: function(){
			var str = "<div>"
				+ "	<h1 class='header'>@header</h1>"
				+ "	<h2 class='header2'>@header2</h2>"
				+ "</div>"
			return str;
		}
		,'is named properly': function(topic){
			assert.equal( vash.compile(topic, { useWith: true })( { header: 'a', header2: 'b' } ),
				'<div>'
				+ '	<h1 class=\'header\'>a</h1>'
				+ '	<h2 class=\'header2\'>b</h2>'
				+ '</div>' );
		}
	}
	,'simple expression as tagname': {
		topic: function(){
			return '<@name>This is content</@name>';
		}
		,'is allowed': function(topic){
			assert.equal( vash.compile(topic, { useWith: true })({name: 'what'}), '<what>This is content</what>' );
		}
	}
	,'simple expression as tagname within block': {
		topic: function(){
			return '@if(true){ <@name>This is content</@name> }';
		}
		,'is allowed': function(topic){
			assert.equal( vash.compile(topic, { useWith: true })({name: 'what'}), '<what>This is content</what>' );
		}
	}
	,'complex expression as tagname': {
		topic: function(){
			return '<@name[0].length>This is content</@name[0].length>';
		}
		,'is allowed': function(topic){
			assert.equal( vash.compile(topic, { useWith: true })({name: 'what'}), '<1>This is content</1>' );
		}
	}
	,'email address': {

		topic: function(){
			return 'some.gr-at%email_address-indeed@complex-domain.<EMAIL>dom.<EMAIL>'
		}

		,'included as content': {
			topic: function(address){
				return 'Hi ' + address;
			}
			,'does not leave markup mode': function(topic){
				var tpl = vash.compile( topic );
				assert.equal( tpl(), topic );
			}
		}

		,'included within a href': {
			topic: function(address){
				return '<a href="mailto:' + address + '">' + address + '</a>'
			}
			,'is still just an email': function(topic){
				var tpl = vash.compile( topic );
				assert.equal( tpl(), topic );
			}
		}

		,'are not confused with': {
			topic: '@model.title@console.log("")'
			,'concatenated expressions': function( topic ){
				var tpl = vash.compile( topic, { useWith: false } );
				assert.equal( tpl({ title: 'who' }), 'who' );
			}
		}

		,'can have any two-letter tld-ish': {
			topic: '<EMAIL>'
			,'and still register': function(topic) {
				var tpl = vash.compile(topic);
				assert.equal(tpl(), topic);
			}
		}
	}
	,'explicit expression': {
		topic: function(){
			var str = '<span>ISBN@(isbnNumber)</span>';
			return vash.compile(str, { useWith: true });
		}
		,'does not trip e-mail escape': function(topic){
			assert.equal( topic({isbnNumber: 10101}), '<span>ISBN10101</span>' )
		}
	}
	,'explicit expression with unmatched parenthesis': {
		topic: function(){
			var str = '<span>ISBN@(isbnNumber</span>';
			return str;
		}
		,'throws syntax error': function(topic){
			assert.throws( function(){ vash.compile(topic) }, Error );
		}
	}
	,'expression with spaces in func call': {
		topic: function(){
			var str = '<span>@a.replace("x", "o")</span>';
			return str;
		}
		,'renders': function(topic){
			assert.equal(vash.compile(topic, { useWith: true })({ a: 'xxx' }), '<span>oxx</span>');
		}
	}

	,'regex': {

		'simple expression': {
			topic: '<span>@a.replace(/\\)a"\'/gi, "o")</span>'
			,'replaces': function( topic ){
				var tpl = vash.compile( topic, { useWith: true } );
				assert.equal( tpl({ a: ')a"\'' }), '<span>o</span>');
			}
		}

		,'period meta character': {
			topic: '<span>@a.replace(/\\)."\'/gi, "o")</span>'
			,'replaces': function( topic ){
				var tpl = vash.compile( topic, { useWith: true } );
				assert.equal( tpl({ a: ')a"\'' }), '<span>o</span>')
			}
		}

		,'within BLK': {
			topic: '@{ var re = /[@}\'"]/gi; }<span>@a.replace(re, "o")</span>'
			,'replaces': function( topic ){
				var tpl = vash.compile( topic, { useWith: true } );
				assert.equal( tpl({ a: '@' }), '<span>o</span>');
			}
		}

		,'within an expression': {
			topic: '@(/a/.exec(\'abc\')[0])'
			,outputs: function( topic ){
				var tpl = vash.compile(topic);
				assert.equal( tpl(), 'a' );
			}
		}

		,'literal': {

			'within markup': {
				topic: '<span>@/a/.test(\'abc\')</span>'
				,outputs: function ( topic ) {
					var tpl = vash.compile(topic);
					assert.equal( tpl(), '<span>true</span>' );
				}
			}

			,'within markup attribute': {
				topic: '<span b="@/a/.exec(\'abc\')[0]"></span>'
				,outputs: function ( topic ) {
					var tpl = vash.compile(topic);
					assert.equal( tpl(), '<span b="a"></span>' );
				}
			}
		}

		,'following conditional': {
			topic: '@if (true) /a/.test(\'abc\')'
			,outputs: function ( topic ) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '' );
			}
		}

		,'are not mistaken for': {

			'division expression': {
				topic: '@{ var test = 100/2; }<span>@test</span>'
				,'is not mistaken for regex': function (topic) {
					var tpl = vash.compile(topic);
					assert.equal( tpl({}), '<span>50</span>' );
				}
			}

			,'division within condition': {
				topic: '@{ if(100/2) <span></span> }'
				,'is not mistaken for regex': function (topic) {
					var tpl = vash.compile(topic);
					assert.equal( tpl({}), '<span></span>' );
				}
			}

			,'division after expression': {
				topic: '@(Math.round(20.22) / 100)'
				,'is not mistaken for regex': function (topic) {
					var tpl = vash.compile(topic);
					assert.equal( tpl({}), '0.2' );
				}
			}

			,'division within block': {
				topic: '@{ Math.round(2 * 1) / Number.MAX_VALUE }'
				,'is not mistaken for regex': function (topic) {
					var tpl = vash.compile(topic);
					assert.equal( tpl({}), '' );
				}
			}

		}
	}

	,'escaping the @ symbol': {

		'within content': {
			topic: function(){
				var str = '<span>In vash, you use the @@foo to display the value of foo</span>';
				return vash.compile(str);
			}
			,'leaves just a single @': function(topic){
				assert.equal( topic(), '<span>In vash, you use the @foo to display the value of foo</span>' )
			}
		}

		,'within quoted string in BLK': {
			topic: '@{ var a = "Twitter: @KirbySaysHi"; }<text>@a</text>'

			,'is not required': function(topic){
				var tpl = vash.compile(topic);
				assert.equal( tpl(), 'Twitter: @KirbySaysHi' );
			}
		}

		,'within ProgramNode (root) in front of keywords': {
			topic: ''
			  + '@@if(model.type){\n'
			  + '  <p>I\'m a @@model.type!</p>\n'
			  + '} else if(model.name){\n'
			  + '  <p>My name is @@model.name.</p>\n'
			  + '} else {\n'
			  + '  <p>I DON\'T KNOW WHO OR WHAT I AM...</p>\n'
			  + '}\n'
			,'outputs': function(topic) {
				var tpl = vash.compile( topic );
				assert.equal( tpl(), ''
				  + '@if(model.type){\n'
					+ '  <p>I\'m a @model.type!</p>\n'
					+ '} else if(model.name){\n'
					+ '  <p>My name is @model.name.</p>\n'
					+ '} else {\n'
					+ '  <p>I DON\'T KNOW WHO OR WHAT I AM...</p>\n'
					+ '}\n');
			}
		}

		,'within markup attribute': {
			topic: '<figure id="fig-@@(figcount++)"></figure>'
			,outputs: function(topic) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '<figure id="fig-@(figcount++)"></figure>' );
			}
		}

		,'within markup node': {
			topic: '<f@@e></f@@e>'
			,outputs: function(topic) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '<f@e></f@e>' );
			}
		}

		,'<ul class="@@(model.active ? \'highlight\' : \'\')"></ul>': {
			topic: '<ul class="@@(model.active ? \'highlight\' : \'\')"></ul>'
			,outputs: function(topic) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '<ul class="@(model.active ? \'highlight\' : \'\')"></ul>' );
			}
		}

		,'@@@@' : {
			topic: '`@@@@`'
			,outputs: function(topic) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '`@@`' );
			}
		}

		,'@@{  }': {
			topic: '@@{  }'
			,outputs: function(topic) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '@{  }' );
			}
		}
	}

	,'PHP-like tags are not confused for attributes': {
		topic: '<? $what = \'what\' ?>'
		,outputs: function(topic) {
			var tpl = vash.compile(topic);
			assert.equal( tpl(), '<? $what = \'what\' ?>' );
		}
	}

	,'attribute parsing allows for quoted = (equal) signs': function() {
		var topic = '<meta name="viewport" content="width=device-width, initial-scale=1">';
		var tpl = vash.compile(topic);
		assert.equal( tpl(), topic );
	}

	,'"server-side" comments': {

		'multiline': {
			topic: function(){
				var str = '@* \n'
					+ 'This is a server side \n'
					+ 'multiline comment \n'
					+ '*@ and this content should be';
				return vash.compile(str);
			}
			,'output nothing': function(topic){
				assert.equal( topic(), ' and this content should be' )
			}
		}
		,'singleline': {
			topic: function(){
				var str = '@*'
					+ 'This is a server side '
					+ 'comment'
					+ '*@ and this content should be';
				return vash.compile(str);
			}
			,'output nothing': function(topic){
				assert.equal( topic(), ' and this content should be' )
			}
		}
		,'within a block': {
			topic: function(){
				var str = '@if(true){ @*'
					+ 'This is a server side '
					+ 'comment'
					+ '*@ } and this content should be';
				return str;
			}
			,'output nothing': function(topic){
				topic = tryCompile(topic)
				assert.equal( topic(), ' and this content should be' )
			}

		}
		,'unclosed': {
			topic: function(){
				var str = '@* \n'
					+ 'This is a server side \n'
					+ 'multiline comment \n';
				return str;
			}
			,'throws exception': function(topic){
				assert.throws( function(){ vash.compile(topic) }, Error );
			}
		}
		,'can be escaped': {
			topic: 'with `@@*` and `*@@`'
			,'successfully': function(topic) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), 'with `@*` and `*@`' );
			}
		}
	}
	,'mixing expressions and text': {
		topic: function(){
			var str = 'Hello @title. @name.';
			return vash.compile(str, { useWith: true });
		}
		,'outputs text': function(topic){
			assert.equal( topic({ title: 'Mr', name: 'Doob' }), 'Hello Mr. Doob.');
		}
	}
	,'excluding "with"': {
		topic: function(){
			var str = '<li>@model.name</li>'
				,tpl;

			tpl = vash.compile(str, { useWith: false });
			return tpl;
		}
		,'ensures it is not there': function(topic){
			assert.equal( topic({name: 'what'}), "<li>what</li>" );
		}
	}
	,'including "with"': {
		topic: function(){
			var str = '<li>@name</li>'
				,tpl;

			tpl = vash.compile(str, { useWith: true });
			return tpl;
		}
		,'ensures it is there': function(topic){
			assert.equal( topic({name: 'what'}), "<li>what</li>" );
		}
	}
	,'model name': {
		topic: function(){
			var str = '<li>@it.name</li>'
				,tpl;

			vash.config.modelName = 'it';
			tpl = vash.compile(str, { useWith: false });
			vash.config.modelName = 'model';
			return tpl;
		}
		,'is configurable': function(topic){
			assert.equal( topic({name: 'what'}), "<li>what</li>" );
		}
	}
	,'same line } in block after markup': {
		topic: function(){
			var str = '@{ var a = 0; a += 1; <span>text</span> } <span>text</span>';
			return str;
		}
		,'closes block without neccessity of newline': function(topic){
			var tpl = tryCompile(topic);
			assert.equal( tpl(), '<span>text</span> <span>text</span>' );
		}
	}
	,'misnested html tags in block': {
		topic: function(){
			var str = '@if(true) { <li><p></li></p> }';
			return str;
		}
		,'does not throw "UMATCHED" exception': function(topic){
			//vash.compile(topic);
			assert.doesNotThrow( function(){ vash.compile(topic) }, Error );
		}
	}
	,'self closing html tag inside block': {
		topic: function(){
			var str = '@if(true) { <img src="" /> \n}';
			return str;
		}
		,'does not bork the block stack': function(topic){
			//assert.doesNotThrow( function(){ vash.compile(topic); }, vash._err.MALFORMEDHTML );
			assert.equal( vash.compile(topic)(), '<img src="" />' );
		}
	}
	,'self closing html tag with expression': {
		topic: '<img src="@model.a" />'
		,'allows expression': function(topic){
			assert.equal( vash.compile(topic)({ a: 'a' }), '<img src="a" />' );
		}
	}
	,'nested self closing html tag inside block': {
		topic: function(){
			var str = '@if(true) { <li><img src="" /></li> \n}';
			return str;
		}
		,'does not bork the block stack': function(topic){
			//assert.doesNotThrow( function(){ vash.compile(topic); }, vash._err.MALFORMEDHTML );
			assert.equal( vash.compile(topic)(), '<li><img src="" /></li>' );
		}
	}

	,'content following parens preserves whitespace': {
		topic: '<(bin/vash <docs/helpers/* --helper) > README2.md'
		,outputs: function(topic) {
			var tpl = vash.compile(topic);
			assert.equal( tpl(), '<(bin/vash <docs/helpers/* --helper) > README2.md' );
		}
	}

	,'content } in closed markup': {
		topic: function(){
			var str = '@if(true) { <li> } </li> }';
			return str;
		}
		,'does not need to be escaped': function(topic){
			assert.doesNotThrow( function(){ vash.compile(topic) }, Error );
			assert.equal( vash.compile(topic)(), '<li> } </li>');
		}
	}
	,'content } in expression': {
		topic: function(){
			var str = '@( false || "}" )';
			return str;
		}
		,'does not need to be escaped': function(topic){
			//assert.doesNotThrow( function(){ vash.compile(topic) }, Error);
			assert.doesNotThrow( function(){ vash.compile(topic) }, Error );
			assert.equal( vash.compile(topic)(), '}');
		}
	}
	,'markup followed by for loop': {
		topic: function(){
			var str = '<div class="how"> @for(var i = 0; i < 1; i++){ <div class="item-@i">I be an item!</div> } </div>';
			return vash.compile(str);
		}
		,'renders': function(topic){
			assert.equal( topic(), '<div class="how"> <div class="item-0">I be an item!</div> </div>' );
		}
	}
	,'unclosed block': {
		// throws UNMATCHED exception
		topic: function(){
			var str = '<div class="yeah"> @for(var i = 0; i < 1; i++){ </div>';
			return str;
		}
		,'throws UNMATCHED': function(topic){
			assert.throws( function(){ vash.compile(topic) }, Error );
		}
	}
	,'HTML5:': {
		/*'unclosed tags': {
			topic: function(){
				var str = '<div class="how what">This is content @for(var i = 0; i < 1; i++){ <p>@i }';
				return str;
			}
			,'do not bork': function(topic){
				assert.equal( vash.compile(topic)(), '<div class="how what">This is content <p>0 ' );
			}
		}
		,*/'unclosed tag followed by previous closing tag': {
			topic: function(){
				var str = '<div class="how what">This is content @for(var i = 0; i < 1; i++){ <p>@i </div> }';
				return str;
			}
			,'throws': function(topic){
				assert.throws( function(){ vash.compile(topic)() }, Error );
			}
		}
		,'self-closing tags WITHOUT /': {
			topic: function(){
				var str = '<div class="how what">This is content @for(var i = 0; i < 1; i++){ <text><br>@i</text> } </div>'
				return str;
			}
			,'does not throw UNMATCHED': function(topic){
				assert.doesNotThrow( function(){ vash.compile(topic)() }, Error );
			}
		}
		,'explicitly unclosed, non-void tags': {
			topic: '<a><b><c>'
			,'throw': function(topic) {
				assert.throws(function() {
					var tpl = vash.compile(topic);
				}, Error);
			}
		}
		,'void tag with closing tag': {
			topic: '<img></img>'
			,'throws': function(topic) {
				assert.throws(function() {
					var tpl = vash.compile(topic);
				}, Error)
			}
		}
		,'void tag with closing tag surrounded by BLK': {
			topic: '@{ <img></img> }'
			,'throws': function(topic) {
				assert.throws(function() {
					var tpl = vash.compile(topic);
				}, Error)
			}
		}

		/*,'closing tag within block': {
			topic: function(){
				var str = '<div>This is content @if(true){ </div> } else { </div> }';
				return str;
			}
			,'closes parent': function(topic){
				assert.doesNotThrow( function(){ vash.compile(topic)() }, Error );
				assert.equal( vash.compile(topic)(), '<div>This is content </div>' );
			}
		}*/

		,'operators': {

			topic: function(){
				return '@for( var i = 0; i <= 0; i++ ){<p></p>}';
			}

			,'are not mistaken for tags': function(topic){
				var tpl = vash.compile( topic );
				assert.equal( tpl(), '<p></p>' );
			}
		}

		,'newlines within nested markup': {
			topic: '<span>@model<span\n></span></span>'

			,'does not prevent HTML matching': function(topic) {
				var tpl = vash.compile(topic, {useWith: false})
					, actual = tpl('1');

				assert.equal( '<span>1<span></span></span>', actual )
			}
		}

		,'dashes within tag names': {
			topic: '<accordion-group>hey</accordion-group>'

			,'are included as the tag': function(topic) {
				var tpl = vash.compile(topic, {useWith: false})
					, actual = tpl();

				assert.equal( actual, topic )
			}
		}

	}

	,'xml': {

		'tag namespaces parse as tags': function () {
			var str = '<core:AnotherElement>Hello</core:AnotherElement>';
			var tpl = vash.compile(str);
			assert.equal( tpl(), str );
		}

		,'AT within namespaces are ok': function () {
			var str = '<c:@model.a>Hello</c:@model.a>';
			var tpl = vash.compile(str);
			assert.equal( tpl({ a: 'a' }), '<c:a>Hello</c:a>' );
		}

		,'directives are ok': function () {
			var str = '<?xml version="1.0" encoding="UTF-8"?><p></p>';
			var tpl = vash.compile(str);
			assert.equal( tpl(), str );
		}

		,'attribute namespaces are ok': function () {
			var str = ''
				+ '<ADI \n'
				+ '  xmlns:core="blah" \n'
				+ '  xmlns:ext="URN:NNDS:CMS:ADI3:01"\n'
				+ '  xmlns="http://www.cablelabs.com/namespaces/metadata/xsd/vod30/1">\n'
				+ '  @(model.what)\n'
				+ '</ADI>';
			var tpl = vash.compile(str);
			var expected = ''
				+ '<ADI '
				+ 'xmlns:core="blah" '
				+ 'xmlns:ext="URN:NNDS:CMS:ADI3:01" '
				+ 'xmlns="http://www.cablelabs.com/namespaces/metadata/xsd/vod30/1">\n'
				+ '  what\n'
				+ '</ADI>'
			var actual = tpl({ what: 'what' });
			console.log('actual', actual)
			console.log('expected', expected)
			assert.equal( actual, expected );
		}
	}

	,'unbalanced characters are ok': {
		// https://github.com/kirbysayshi/vash/issues/26

		'open paren': {
			topic: '@("(")'
			,'outputs': function( topic ) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '(' );
			}
		}

		,'open paren within markup within block': {
			topic: '@(function(model) { <p>(</p> }())'
			,'outputs': function( topic ) {
				var tpl = vash.compile(topic);
				assert.equal( tpl(), '<p>(</p>' );
			}
		}

	}

	,'simple expression followed by @()': {

		topic: function(){
			return '<li data-score="@model.Score" class="user-panel-track @(model.i % 2 === 0 ? \'even\' : \'odd\')"></li>';
		}
		,'renders': function(topic){
			assert.equal( vash.compile(topic)({ Score: '1', i: 0 })
				, '<li data-score="1" class="user-panel-track even"></li>');
		}
	}
	,'empty string': {
		topic: function(){ return "" }
		,'returns empty string': function(topic){
			assert.throws( function(){ vash.compile(topic)() }, Error );
		}
	}
	,'non-string parameter': {
		topic: function(){ return {} }
		,'throws exception': function(topic){
			assert.throws( function(){ vash.compile(topic)() }, Error );
		}
	}
	,'@function': {
		// the space before @ counts as markup
		topic: function(){ return '@function doWhat(){ return "what"; } @doWhat()' }
		,compiles: function(topic){
			assert.doesNotThrow( function(){ vash.compile(topic) }, Error );
		}
		,'can be called': function(topic){
			assert.equal( vash.compile(topic)(), ' what' );
		}
	}
	,'@function with markup': {
		topic: function(){ return '@function doWhat(input){ <li>@input.name</li> } @doWhat(model)' }
		,compiles: function(topic){
			assert.doesNotThrow( function(){ vash.compile(topic) }, Error );
		}
		,'can be called': function(topic){
			assert.equal( vash.compile(topic)({ name: 'what' }), ' <li>what</li>' );
		}
	}

	,'this was an infinite parser bug #68 at some point': {
		topic: '@:Some Text\n@html.block(\'test\')\n'
		,'does not hang': function(topic) {
			var tpl = vash.compile(topic);
			assert.ok( tpl );
		}
	}

	/*,'fat arrow': {

		'with single parameter': {
			topic: function(){
				return vash.compile( '<ul>@arr.forEach( i => <li>@i</li> )</ul>' );
			}

			,'succeeds': function(topic){
				assert.equal( topic( { arr: ['a','b'] } ), '<ul><li>a</li> <li>b</li> </ul>' )
			}
		}
		// this is technically incorrect, but pretty cool that you can do it
		,'with multiple unparenthetized parameters': {
			topic: function(){
				return vash.compile( '<ul>@arr.forEach( i, k => <li>@i</li> )</ul>' );
			}

			,'succeeds': function(topic){
				assert.equal( topic( { arr: ['a','b'] } ), '<ul><li>a</li> <li>b</li> </ul>' )
			}
		}
		,'with multiple parameters': {
			topic: function(){
				return vash.compile( '<ul>@arr.forEach( (i,k) => <li>@i</li> )</ul>' );
			}

			,'succeeds': function(topic){
				assert.equal( topic( { arr: ['a','b'] } ), '<ul><li>a</li> <li>b</li> </ul>' )
			}
		}
		,'with multiple parameters with function block': {
			topic: function(){
				return vash.compile( '<ul>@arr.forEach( (i,k) => { <li>@i</li> } )</ul>' );
			}

			,'succeeds': function(topic){
				assert.equal( topic( { arr: ['a','b'] } ), '<ul><li>a</li> <li>b</li> </ul>' )
			}
		}
	}*/

	,'html escaping:': {

		'basic': {
			topic: function(){
				return vash.compile( '<span>@it</span>', { useWith: true } );
			}
			,'is escaped': function(topic){
				assert.equal( topic({ it: '<b>texted</b>' }), '<span>&lt;b&gt;texted&lt;/b&gt;</span>' );
			}
		}

		,'force no escaping': {
			topic: function(){
				return vash.compile( '<span>@it</span>', { htmlEscape: false, useWith: true } );
			}
			,'is escaped': function(topic){
				assert.equal( topic({ it: '<b>texted</b>' }), '<span><b>texted</b></span>' );
			}
		}

		,'force no escaping per call (html.raw)': {
			topic: function(){
				return vash.compile( '<span>@html.raw(it)</span>', { useWith: true } );
			}
			,'is escaped': function(topic){
				assert.equal( topic({ it: '<b>texted</b>' }), '<span><b>texted</b></span>' );
			}
		}

		,'multiple function calls': {
			topic: function(){
				return vash.compile( '@function f(i){ <b>@i</b> }<span>@f(it)</span>@f(it)', { useWith: true } );
			}
			,'are escaped': function(topic){
				assert.equal( topic({ it: '<b>texted</b>' }),
					'<span><b>&lt;b&gt;texted&lt;/b&gt;</b></span><b>&lt;b&gt;texted&lt;/b&gt;</b>' );
			}
		}

		,'multiple function calls are not double escaped': {
			topic: function(){
				return vash.compile( '@function f(i){ <b>@i</b> }<span>@f(model.it)</span>@f(model.it)', { useWith: false } );
			}
			,'are escaped': function(topic){
				//console.log( topic.toString() );
				assert.equal( topic({ it: '<b>texted</b>' }),
					'<span><b>&lt;b&gt;texted&lt;/b&gt;</b></span><b>&lt;b&gt;texted&lt;/b&gt;</b>' );
			}
		}

		,'multiple nested function calls': {
			topic: function(){
				return vash.compile( '@function f(i){ <b>@i</b> function d(i){ <b>@i</b> } d(model.it) }<span>@f(model.it)</span>@f(model.it)' );
			}
			,'are escaped': function(topic){
				assert.equal( topic({ it: '<b>texted</b>' }),
					'<span><b>&lt;b&gt;texted&lt;/b&gt;</b><b>&lt;b&gt;texted&lt;/b&gt;</b></span><b>&lt;b&gt;texted&lt;/b&gt;</b><b>&lt;b&gt;texted&lt;/b&gt;</b>' );
			}
		}
	}

	,"markup quotation marks:": {

		"single quotes come out": {
			topic: function(){
				return "<text>It's followed by primary content.</text>"
			}
			,"as single quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), "It's followed by primary content.")
			}
		}

		,"double quotes come out": {
			topic: function(){
				return '<text>It is "followed" by primary content.</text>'
			}
			,"as double quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), 'It is "followed" by primary content.')
			}
		}

		,"escaped single quotes come out": {
			topic: function(){
				return "<text>'It\\'s followed by primary content.'</text>"
			}
			,"as single quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), "'It\\'s followed by primary content.'")
			}
		}

		,"escaped double quotes come out": {
			topic: function(){
				return "<text>It is \"followed\" by primary content.</text>"
			}
			,"as double quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), 'It is "followed" by primary content.')
			}
		}
	}

	,"block quotation marks:": {

		"single quotes come out": {
			topic: function(){
				return "@{ var a = \"It's followed by primary content.\"; } @html.raw(a)"
			}
			,"as single quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), " It's followed by primary content.")
			}
		}

		,"double quotes come out": {
			topic: function(){
				return '@{ var a = \'It is "followed" by primary content.\'; } @html.raw(a)'
			}
			,"as double quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), ' It is "followed" by primary content.')
			}
		}

		,"escaped single quotes come out": {
			topic: function(){
				return "@{ var a = 'It\\'s followed by primary content.'; } @html.raw(a)"
			}
			,"as single quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), " It's followed by primary content.")
			}
		}

		,"escaped double quotes come out": {
			topic: function(){
				return '@{ var a = \'It is \"followed\" by primary content.\'; } @html.raw(a)'
			}
			,"as double quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), ' It is "followed" by primary content.')
			}
		}
	}

	,"expression quotation marks:": {

		"single quotes come out": {
			topic: function(){
				return "@html.raw(\"It's followed by primary content.\")"
			}
			,"as single quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), "It's followed by primary content.")
			}
		}

		,"double quotes come out": {
			topic: function(){
				return '@html.raw(\'It is "followed" by primary content.\')'
			}
			,"as double quotes": function(topic){
				var tpl = tryCompile(topic);
				assert.equal(tpl(), 'It is "followed" by primary content.')
			}
		}

		,"escaped single quotes come out": {
			topic: function(){
				return "@html.raw('It\\'s followed by primary content.')"
			}
			,"as single quotes": function(topic){
				vash.compile(topic, { useWith: false, debug: false });
				var tpl = tryCompile(topic);
				assert.equal(tpl(), "It's followed by primary content.")
			}
		}

		,"escaped double quotes come out": {
			topic: function(){
				return '@html.raw("It is \\"followed\\" by primary content.")'
			}
			,"as double quotes": function(topic){
				var tpl = vash.compile(topic);
				//var tpl = vash.compile(topic, { useWith: false, debug: false });
				assert.equal(tpl(), 'It is "followed" by primary content.')
			}
		}
	}

	,"inline styles": {

		"style tag with one id rule": {
			topic: function(){
				return '<style type="text/css">#header{ border-bottom: 0; }</style>'
			}
			,"is unchanged": function(topic){
				var tpl = vash.compile(topic);
				assert.equal(tpl(), topic)
			}
		}
		,"style tag with two id rule": {
			topic: function(){
				return '<style type="text/css">#parallax_field #parallax_bg { position: absolute; top: -20px; left: -20px; width: 110%; height: 425px; z-index: 1; }</style>'
			}
			,"is unchanged": function(topic){
				var tpl = vash.compile(topic);
				assert.equal(tpl(), topic)
			}
		}
	}

	,'favorText': {

		'with expression': {
			topic: function(){
				return '@if(true){ @model.text }'
			}
			,'requires @': function(topic){
				var tpl = vash.compile(topic, { favorText: true })
				assert.equal( tpl({ text: 'yes' }), ' yes ' );
			}
		}

		,'with expression inside markup block': {
			topic: function(){
				return '@if(true){ <b>@model.text</b> }'
			}
			,'requires @': function(topic){
				var tpl = vash.compile(topic, { favorText: true })
				assert.equal( tpl({ text: 'yes' }), ' <b>yes</b> ' );
			}
		}

		,'with explicit expression inside markup block': {
			topic: function(){
				return '@if(true){ <b>@(model.text)</b> }'
			}
			,'requires @': function(topic){
				var tpl = vash.compile(topic, { favorText: true })
				assert.equal( tpl({ text: 'yes' }), ' <b>yes</b> ' );
			}
		}

		,'with text': {
			topic: function(){
				return '@if(true){ model.text }'
			}
			,'outputs content': function(topic){
				var tpl = vash.compile(topic, { favorText: true })
				assert.equal( tpl(), ' model.text ' );
			}
		}

		,'with html': {
			topic: function(){
				return '@if(true){ <b>model.text</b> }'
			}
			,'outputs content': function(topic){
				var tpl = vash.compile(topic, { favorText: true })
				assert.equal( tpl(), ' <b>model.text</b> ' );
			}
		}

	}

	,'keywords': {

		topic: '@if( model instanceof Object ){ <p></p> }'

		,'do not open extraneous blocks': function( topic ){
			var tpl = vash.compile( topic );
			assert.equal( tpl(), '<p></p>' );
		}
	}

	,'else if': {

		topic: '@if (!model.w){<p>A</p>} else if (model.w) {<p>B</p>}<p>C</p>'

		,'codegen': function( topic ){
			var tpl = vash.compile(topic);
			assert.equal( tpl({w: false}), '<p>A</p><p>C</p>' );
		}
	}

	,'else followed by another if': {
		topic: '@if (true) { <span>1</span> } else if (false) { <span>no</span> } else { <span>no</span> }\n\n@if (true) { <span>2</span> }',
		'outputs': function( topic ) {
			var tpl = vash.compile(topic);
			console.log(tpl())
			assert.equal( tpl(), '<span>1</span><span>2</span>' );
		}
	}

	,'switch statement': {

		'without braced cases': {
			topic: '@switch(model){ case 1: <p></p>break; case 2: <b></b>break; }'

			,'work': function( topic ){
				var tpl = vash.compile( topic );
				assert.equal( tpl(1), '<p></p>' );
				assert.equal( tpl(2), '<b></b>' );
			}
		}

		,'with braced cases': {
			topic: '@switch(model){ case 1: { <p></p>break; } case 2: { <b></b>break; } }'
			,work: function( topic ) {
				var tpl = vash.compile( topic );
				assert.equal( tpl(1), '<p></p>' );
				assert.equal( tpl(2), '<b></b>' );
			}
		}
	}

	,'backslashes': {

		'in markup': {
			topic: '<p>Literal \\</p>'

			,'are literal': function(topic){
				var tpl = vash.compile(topic)
					,expected = topic
					,actual = tpl();

				assert.equal( actual, expected );
			}

			,'within content regex': {
				topic: '/^([a-zA-Z0-9.%]+@@[a-zA-Z0-9.\\-]+\\.(?:ca|co\\.uk|com|edu|net|org))\\b/'
				,outputs: function(topic) {
					var tpl = vash.compile(topic);
					assert.equal( tpl(), '/^([a-zA-Z0-9.%]+@[a-zA-Z0-9.\\-]+\\.(?:ca|co\\.uk|com|edu|net|org))\\b/' );
				}
			}
		}

		,'in expression': {
			topic: '@( false || /\\n/.exec("\\n") && "\\ " + "\\n" )'

			,'are literal': function(topic){
				var tpl = vash.compile(topic)
					,expected = ' \n'
					,actual = tpl();

				assert.equal( actual, expected );
			}
		}

		,'in block': {
			topic: '@{ var a = "\\\\ "; var b = /\\n/.exec("\\n"); }@a@b[0]'

			,'are literal': function(topic){
				var tpl = vash.compile(topic)
					,expected = '\\ \n'
					,actual = tpl();

				assert.equal( actual, expected );
			}
		}

		,'in a compiled helper': {
			topic: ''
				+ 'vash.helpers.bslash = function(assert, str){ \n'
				+ 'var re = /\\n/ \n'
				+ 'assert.equal(re.source, "\\\\n"); \n'
				+ 'assert.ok(re.exec(str), "`str` does not contain newline"); \n'
				+ 'return re; \n'
				+ '}'

			,'are not double escaped': function(topic){
				vash.compileHelper(topic);
				var re = vash.helpers.bslash(assert, 'this\nhas');
				assert.equal(re.source, '\\n');
			}
		}
	}

	,'markup within explicit expressions': {

		topic: '@(<p>@model.what @{ var a = "who"; } @a</p>)'

		,'is allowed': function (topic) {
			var tpl = vash.compile(topic);
			console.log('tpl', tpl.toClientString());
			assert.equal(tpl({ what: 'hey' }), '<p>hey  who</p>');
		}

	}

	,'render options': {

		topic: function(){ return vash.compile('<p></p>') }

		,'asContext': {
			topic: function( tpl ){
				return tpl({}, { asContext: true });
			}

			,'returns a context': function( ctx ){
				assert( ctx instanceof vash.helpers.constructor, 'ctx should be instance of Helpers')
			}
		}

		,'onRenderEnd as parameter': {
			topic: function( tpl ){
				tpl( {}, this.callback );
			}

			,'is called': function( err, ctx ){
				assert.ifError( err );
				assert( ctx instanceof vash.helpers.constructor, 'ctx should be instance of Helpers')
			}
		}

		,'onRenderEnd as option': {
			topic: function( tpl ){
				tpl( {}, { onRenderEnd: this.callback } );
			}

			,'is called': function( err, ctx ){
				assert.ifError( err );
				assert( ctx instanceof vash.helpers.constructor, 'ctx should be instance of Helpers')
			}
		}

		,'injecting a context': {
			topic: function( tpl ){
				var ctx = new vash.helpers.constructor({ injected: true });
				ctx.buffer.push('test');
				return tpl({ injected: false }, { context: ctx, asContext: true })
			}

			,'overrides the model': function( ctx ){
				assert.equal( ctx.model.injected, true );
			}

			,'retains buffer': function( ctx ){
				assert.equal( ctx.toString(), 'test<p></p>' )
			}
		}

		,'from within a helper': {
			topic: function(){
				vash.helpers.opts1 = function(){ return this.options.something; }
				return vash.compile('<p>@html.opts1()</p>')
			}

			,'are accessible': function( tpl ){
				assert.equal( tpl({}, { something: 'what' }), '<p>what</p>' );
			}

			,teardown: function(){
				delete vash.helpers.opts1;
			}
		}
	}

	,'simple': {

		topic: function(){
			return '<p>@model</p>'
		}

		,'renders': function( topic ){
			var tpl = vash.compile(topic, { simple: true });
			assert.equal( tpl('YES'), '<p>YES</p>' );
		}

		,'html escaping': {
			topic: ''

			,'is ignored via `raw`': function(){
				var str = '<p>@html.raw(model)</p>'
					,tpl = vash.compile( str, { simple: true, htmlEscape: true } )

				assert.equal( tpl('<br />'), '<p><br /></p>');
			}

			,'works': function(){
				var str = '<p>@model</p>'
					,tpl = vash.compile( str, { simple: true, htmlEscape: true } )

				assert.equal( tpl('<br />'), '<p>&lt;br /&gt;</p>');
			}
		}
	}

	,'batched': {

		topic: '//@batch = div \n<div>@model</div>\n'
			+'// @batch = a \n<a>@model</a>'

		,'installs each tpl': function(topic){
			var  tpls = vash.compileBatch(topic)
				,model = 'm';

			vash.install(tpls);

			assert.equal( vash.lookup('div')(model), '<div>m</div>' );
			assert.equal( vash.lookup('a')(model), '<a>m</a>' );

			vash.uninstall('div');
			vash.uninstall('a');
		}
	}

	,'compiled helpers': {

		'can be defined': {

			topic: 'vash.helpers.fn = function(id, ctn){'
				+ 'this.fnCounter = this.fnCounter || 0;'
				+ 'this.fnIds = this.fnIds || {};'
				+ 'this.fnCtn = this.fnCtn || {};'
				+ '  if(ctn){'
				+ '    <li id="fn:@id">'
				+ '      @ctn()'
				+ '      <a rev="footnote" href="#fnref:@id">↩</a>'
				+ '  	</li>'
				+ '  } else {'
				+ '    this.fnIds[id] = ++this.fnCounter;'
				+ '    <sup id="fnref:@id">'
				+ '    <a rel="footnote" href="#fn:@id">@html.raw(this.fnCounter)</a>'
				+ '    </sup>'
				+ '  }'
				+ '}'

			,'and execute': function(topic){

				vash.compileHelper(topic);
				assert.ok( vash.helpers.fn );

				var str = '@html.fn("one") @html.fn("two")'
					,tpl = vash.compile(str);

				var  ctx = tpl({}, { asContext: true })
					,rendered = ctx.toString();

				assert.equal( ctx.fnCounter, 2 );
				assert.equal( ctx.fnIds.one, 1 );
				assert.equal( ctx.fnIds.two, 2 );
				assert.ok( rendered.indexOf('fnref:one') > -1, 'expect indexOf fnref:one to be > -1' );
				assert.ok( rendered.indexOf('fnref:two') > -1, 'expect indexOf fnref:two to be > -1' );
				delete vash.helpers.fn;
			}
		}

		,'when compiling': {

			topic: '\n'
				+ 'vash.helpers.fn0 = function(){}\n'
				+ '           vash.helpers.fn1 = function(id){ return id + "1"; }\n'
				+ '\t \t\n  vash.helpers.fn2 = function(id){ return id + "2"; }'

			,'can batch compile': function(topic){
				vash.compileHelper(topic);
				assert.ok( vash.helpers.fn1, 'expect fn1 to be defined' );
				assert.ok( vash.helpers.fn2, 'expect fn2 to be defined' );

				var  str = '@html.fn1("a") @html.fn2("b")'
					,tpl = vash.compile(str);

				var rendered = tpl();

				assert.equal( rendered, 'a1 b2' );
				delete vash.helpers.fn0;
				delete vash.helpers.fn1;
				delete vash.helpers.fn2;
			}

			,'can batch .toClientString': function(topic){

				var tpls = vash.compileHelper(topic)
					,output = tpls.toClientString()

				assert.ok( output.match(/vash.link/gi).length === 3, 'expect three vash.link' );
			}
		}

	}

	,'html comments': {
		topic: '<!--li><a href="#features">Features</a></li-->'
		,'do not break parsing': function(topic) {
			var tpl = vash.compile(topic);
			assert.equal( tpl(), topic );
		}
	}

	,'html comments with unclosed tags within': {
		topic: '<!--<a><b>c</b>-->'
		,'are not interpreted as tags': function(topic) {
			var tpl = vash.compile(topic);
			assert.equal( tpl(), '<!--<a><b>c</b>-->' );
		}
	}

	,'single line comments': {
		'near markup': {
			topic: ' // this is a comment\n'
				+ '<p></p>'
			,'are output as content': function(topic){
				var  tpl = vash.compile(topic)
					,actual = tpl()
					,expected = ' // this is a comment\n<p></p>'
				assert.equal( actual, expected );
			}
		}
		,'within code': {
			topic: '@{ // this is a comment\n }'
			,'are not output': function(topic){
				var  tpl = vash.compile(topic)
					,actual = tpl()
					,expected = ''
				assert.equal( actual, expected );
			}
		}

		,'and urls': {
			topic: '@{ var href = "http://google.com"; <p>@href</p> }'
			,'are not confused': function(topic){
				var  tpl = vash.compile(topic)
					,actual = tpl()
					,expected = '<p>http://google.com</p>'
				assert.equal( actual, expected );
			}
		}

		,'containing @': {
			topic: '@{ // this is a @comment\n}'

			,"are treated as literal": function(topic){
				var tpl = vash.compile(topic)
					,actual = tpl()
					,expected = '';

				assert.equal(actual, expected);
			}
		}

		,'containing a single quote': {
			topic: '@{ // this is a comment y\'all\n}'

			,"does not enter string mode": function(topic){
				var tpl = vash.compile(topic)
					,actual = tpl()
					,expected = '';

				assert.equal(actual, expected);
			}
		}
	}

	,'ast': {

		topic: function(){
			this.asAST = function (input) {
				var Lexer = require('../../lib/lexer');
				var Parser = require('../../lib/parser');
				var l = new Lexer();

				l.write(input);
				var tokens = l.read();

				var p = new Parser();
				p.write(tokens);
				var more = true;
				while(more !== null) more = p.read();

				return p.stack[0];
			}

			return '';
		}

		,'self-closing tag within tag': {

			topic: '@if(true){<a><br /></a>}'

			,'does not close parent tag': function(topic){
				var root = this.asAST(topic);
				assert.equal( root.body[0].type, 'VashMarkup' );
				assert.equal( root.body[0].values[0].type, 'VashMarkupContent' );
				assert.equal( root.body[0].values[0].values[1].type, 'VashBlock' );
				assert.equal( root.body[0].values[0].values[1].values[0].type, 'VashMarkup' );
				assert.equal( root.body[0].values[0].values[1].values[0].values[0].type, 'VashMarkupContent' );
				assert.equal( root.body[0].values[0].values[1].values[0].values[0].values[0].type, 'VashText' );
				assert.equal( root.body[0].values[0].values[1].values[0].values[0].values[1].isVoid, true );
			}
		}

		,'else if else if': {

			topic: '@if (a){<p>A</p>} else if (b) {<p>B</p>} else {<p>C</p>}<p>D</p>'

			,'has proper structure': function( topic ){
				var tpl = vash.compile(topic);
				var ast = this.asAST(topic);

				var ifBlock = ast.body[0].values[0].values[1];
				assert.equal(ifBlock.keyword, 'if');
				assert.equal(ifBlock.tail[1].keyword, 'else if');
				assert.equal(ifBlock.tail[1].tail[1].keyword, 'else');
			}
		}
	}

	,'whitespace': {

		'within block': {
			topic: '@{ html.buffer.push(Array(5).join("  ")) }'

			,'is preserved': function(topic){
				var  tpl = vash.compile(topic)
					,actual = tpl();

				assert.equal( actual.length, 8 );
			}
		}

		,'within expression': {
			topic: '@Array(5).join("  ")'

			,'is preserved': function(topic){
				var  tpl = vash.compile(topic)
					,actual = tpl();

				assert.equal( actual.length, 8 );
			}
		}

		,'within markup': {
			topic: '        '

			,'is preserved': function(topic){
				var  tpl = vash.compile(topic)
					,actual = tpl();

				assert.equal( actual.length, 8 );
			}
		}

		,'containing BOM': {
			topic: '\uFEFF<p></p>'

			,'is stripped': function(topic) {
				var tpl = vash.compile(topic)
					, actual = tpl();

				assert.equal( actual, '<p></p>');
			}
		}

	}

	,'@expressions within a block': {

		topic: '@{ @(model + "b") }'

		,'are content': function(topic) {
			var tpl = vash.compile(topic);
			var actual = tpl('a');
			assert.equal(actual, 'ab');
		}
	}

	/*,'implicit compilation': {

		'is valid': {

			topic: '<text>@model.a</text>'

			,'within an expression': function( topic ){
				var str = '@function help(res){ res(model) } @help(@' + topic + ')'
					,tpl = vash.compile( str )
					,actual = tpl({a : 'a'});

				assert.equal( actual, ' a' );
			}

			,'within a block statement': function( topic ){
				var str = '@{ var b = @' + topic + '; }@b'
					,tpl = vash.compile( str )

				var actual = tpl( {a: 'a'} );
				assert.equal( actual.toString(), 'a' );
			}
		}
	}*/
	//,'putting markup into a property': {
	//	topic: function(){
	//		var str = '@{ var a = { b: <li class="whatwhat"></li> \n } \n }';
	//		return vash.compile(str);
	//	}
	//	,'outputs nothing when rendered': function(topic){
	//		assert.equal( topic(), '' );
	//	}
	//	//,'outputs a returnable function': function(topic){
	//	//	// could calling the tpl return a function?
	//	//}
	//}
}).export(module)


<|start_filename|>node_modules/vash/lib/nodes/expression.js<|end_filename|>
var Node = module.exports = function ExpressionNode() {
  this.type = 'VashExpression';
  this.values = [];
  this.startloc = null;
  this.endloc = null;
}

<|start_filename|>node_modules/vash/lib/helpers/index.js<|end_filename|>
require('./trim');
require('./highlight');
require('./layout');
module.exports = require('../../runtime');

<|start_filename|>node_modules/vash/lib/nodes/indexexpression.js<|end_filename|>
var Node = module.exports = function IndexExpressionNode() {
  this.type = 'VashIndexExpression';
  this.values = [];
  this.startloc = null;
  this.endloc = null;

  this._waitingForEndQuote = null;
  this._waitingForHardParenClose = null;
}

Node.prototype.endOk = function() {
  return (this._waitingForEndQuote || this._waitingForHardParenClose)
    ? false
    : true;
}

<|start_filename|>node_modules/vash/lib/nodestuff.js<|end_filename|>
function clean(node) {
  return Object.keys(node).reduce(function(out, key) {
    var value = node[key];
    if (key[0] !== '_' && typeof value !== 'function') {
      if (Array.isArray(value)) {
        out[key] = value.map(clean);
      } else {
        out[key] = value;
      }
    }
    return out;
  }, {});
}

exports.clean = clean;


<|start_filename|>node_modules/vash/lib/nodes/regex.js<|end_filename|>

// Need to handle:
// if (true) /abc/.test()
// if (/abc/.test('what'))
// @(/abc/.exec('abc'))
// @{ var re = /abc/gi; }
// if (a/b) {}
// @(a/=b) // Previous is IDENTIFIER or WHITESPACE

var Node = module.exports = function RegexNode() {
  this.type = 'VashRegex';
  this.values = [];
  this.startloc = null;
  this.endloc = null;

  this._waitingForForwardSlash = null;
  this._waitingForFlags = null;
}

Node.prototype.endOk = function() {
  return this._waitingForForwardSlash || this._waitingForFlags
    ? false
    : true;
}