Skip to content

Latest commit

 

History

History
3788 lines (3049 loc) · 84.1 KB

bdd-spec.md

File metadata and controls

3788 lines (3049 loc) · 84.1 KB

TOC

Expression

Syntax

parse/exec a simple expression.

var parsed ;
parsed = Expression.parse( '1 + 2' ) ;
doormen.equals( parsed.getFinalValue() , 3 ) ;

parse/exec a simple expression of expression.

var parsed ;

parsed = Expression.parse( '1 + ( 2 + 3 )' ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;

parsed = Expression.parse( '( 2 + 3 ) + 1' ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;

parsed = Expression.parse( '( ( 5 + 1 ) + 6 ) + ( 2 + ( 3 + 4 ) )' ) ;
doormen.equals( parsed.getFinalValue() , 21 ) ;

parse/exec an expression with operator repetition.

var parsed ;

parsed = Expression.parse( '1 + 2 + 3' ) ;
doormen.equals( parsed.args , [ 1 , 2 , 3 ] ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;

parsed = Expression.parse( '1 + 2 + 3 + -4' ) ;
doormen.equals( parsed.args , [ 1 , 2 , 3 , -4 ] ) ;
doormen.equals( parsed.getFinalValue() , 2 ) ;

parse/exec an expression with implicit array creation.

var parsed ;

parsed = Expression.parse( '1 2 3' ) ;
doormen.equals( parsed , [ 1 , 2 , 3 ]  ) ;

parsed = Expression.parse( '1 , 2 , 3' ) ;
doormen.equals( parsed , [ 1 , 2 , 3 ]  ) ;

parse/exec an expression with explicit array creation.

var parsed ;

parsed = Expression.parse( 'array' ) ;
doormen.equals( parsed.args , [] ) ;
doormen.equals( parsed.getFinalValue() , []  ) ;

parsed = Expression.parse( 'array 1' ) ;
doormen.equals( parsed.args , [ 1 ] ) ;
doormen.equals( parsed.getFinalValue() , [ 1 ]  ) ;

parsed = Expression.parse( 'array 1 2 3' ) ;
doormen.equals( parsed.args , [ 1 , 2 , 3 ] ) ;
doormen.equals( parsed.getFinalValue() , [ 1 , 2 , 3 ]  ) ;

parsed = Expression.parse( 'array 1 , 2 , 3' ) ;
doormen.equals( parsed.args , [ 1 , 2 , 3 ] ) ;
doormen.equals( parsed.getFinalValue() , [ 1 , 2 , 3 ]  ) ;

parse/exec an expression featuring the comma separator syntax.

var parsed ;

parsed = Expression.parse( 'add 1 , 2 , 3' ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;

parsed = Expression.parse( 'add 2 * 4 , 3' ) ;
doormen.equals( parsed.getFinalValue() , 11 ) ;

parsed = Expression.parse( 'add 2 , 4 * 2 , 3' ) ;
doormen.equals( parsed.getFinalValue() , 13 ) ;

parsed = Expression.parse( 'add 2 , 4 , 3 * 2' ) ;
doormen.equals( parsed.getFinalValue() , 12 ) ;

parsed = Expression.parse( 'add 2 * 4 , 3 * 5 , 2' ) ;
doormen.equals( parsed.getFinalValue() , 25 ) ;

parsed = Expression.parse( 'add 2 * 4 , 3 * 5' ) ;
doormen.equals( parsed.getFinalValue() , 23 ) ;

parsed = Expression.parse( 'add 1 , 2 * 4 , 3' ) ;
doormen.equals( parsed.getFinalValue() , 12 ) ;

parsed = Expression.parse( 'add 1 + 3 , 2 * 4 , 3 ^ 2' ) ;
doormen.equals( parsed.getFinalValue() , 21 ) ;

Operators

parse/exec has operator.

var parsed ;

parsed = Expression.parse( '( 3 4 5 ) has 4' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '( 3 4 5 ) has 6' ) ;
doormen.equals( parsed.getFinalValue() , false ) ;

parsed = Expression.parse( '( 3 "str" 5 ) has "str"' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '( 3 "str" 5 ) has "str2"' ) ;
doormen.equals( parsed.getFinalValue() , false ) ;

parse/exec concat operator.

var parsed ;

parsed = Expression.parse( 'concat 3 4 5' ) ;
doormen.equals( parsed.getFinalValue() , [ 3 , 4 , 5 ] ) ;

parsed = Expression.parse( 'concat ( 3 4 ) ( 5 6 )' ) ;
doormen.equals( parsed.getFinalValue() , [ 3 , 4 , 5 , 6 ] ) ;

parsed = Expression.parse( 'concat ( array 3 , 4 ) , ( array 5 , 6 ) , ( array 7 , 8 )' ) ;
doormen.equals( parsed.getFinalValue() , [ 3 , 4 , 5 , 6 , 7 , 8 ] ) ;

parse/exec hypot operator.

var parsed ;

parsed = Expression.parse( 'hypot 3 4' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parsed = Expression.parse( 'hypot 3 4 5' ) ;
doormen.equals( parsed.getFinalValue() , 7.0710678118654755 ) ;

parse/exec avg.

var parsed ;

var ctx = {
	array: [ 2 , 3 , 7 ]
} ;

parsed = Expression.parse( 'avg 3 5 7' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parsed = Expression.parse( 'avg -4  10 27 3' ) ;
doormen.equals( parsed.getFinalValue() , 9 ) ;

parsed = Expression.parse( 'avg $array' ) ;
doormen.equals( parsed.getFinalValue( ctx ) , 4 ) ;

parse/exec three-way.

var parsed ;

parsed = Expression.parse( '1 ??? 4 5 6' ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;

parsed = Expression.parse( '-1 ??? 4 5 6' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( '0 ??? 4 5 6' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parse/exec round/floor/ceil operator.

var parsed ;

parsed = Expression.parse( 'round 4.3' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'floor 4.3' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'ceil 4.3' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parsed = Expression.parse( 'round 4.7' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parsed = Expression.parse( 'floor 4.7' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'ceil 4.7' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parse/exec round/floor/ceil operator with precision.

var parsed ;

parsed = Expression.parse( 'round 4.3 1' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'round 4.2 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'round 4.3 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 4.5 ) ;

parsed = Expression.parse( 'round 4.3 2' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'round 5 2' ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;


parsed = Expression.parse( 'floor 4.3 1' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'floor 4.2 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'floor 4.3 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'floor 4.8 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 4.5 ) ;

parsed = Expression.parse( 'floor 4.3 2' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'floor 5 2' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;

parsed = Expression.parse( 'floor 5.5 2' ) ;
doormen.equals( parsed.getFinalValue() , 4 ) ;


parsed = Expression.parse( 'ceil 4.3 1' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parsed = Expression.parse( 'ceil 4.2 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 4.5 ) ;

parsed = Expression.parse( 'ceil 4.3 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 4.5 ) ;

parsed = Expression.parse( 'ceil 4.6 0.5' ) ;
doormen.equals( parsed.getFinalValue() , 5 ) ;

parsed = Expression.parse( 'ceil 4.3 2' ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;

parsed = Expression.parse( 'ceil 5 2' ) ;
doormen.equals( parsed.getFinalValue() , 6 ) ;

parsed = Expression.parse( 'ceil 6.1 2' ) ;
doormen.equals( parsed.getFinalValue() , 8 ) ;

parse/exec is-set? operators.

var parsed ;

parsed = Expression.parse( '$unknown is-set?' ) ;
doormen.equals( parsed.getFinalValue() , false ) ;

parsed = Expression.parse( '0 is-set?' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '1 is-set?' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parse/exec is-real? operators.

var parsed ;

parsed = Expression.parse( '0 is-real?' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '1 is-real?' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '1.5 is-real?' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '-1.5 is-real?' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '-1.5 is-real?' ) ;
doormen.equals( parsed.getFinalValue() , true ) ;

parsed = Expression.parse( '( 1 / 0 ) is-real?' ) ;
doormen.equals( parsed.getFinalValue() , false ) ;

parsed = Expression.parse( 'Infinity is-real?' ) ;
doormen.equals( parsed.getFinalValue() , false ) ;

parse/exec apply operator.

var parsed , ctx , object ;

object = { a: 3 , b: 5 } ;
object.fn = function( v ) { return this.a * v + this.b ; }

ctx = {
	fn: function( v ) { return v * 2 + 1 ; } ,
	object: object
} ;

parsed = Expression.parse( '$fn -> 3' ) ;
doormen.equals( parsed.getFinalValue( ctx ) , 7 ) ;

parsed = Expression.parse( '$object.fn -> 3' ) ;
//deb( parsed ) ;
doormen.equals( parsed.getFinalValue( ctx ) , 14 ) ;

parse/exec custom operator.

var parsed , ctx , operators , object , v ;

object = { a: 3 , b: 5 } ;
object.fn = function( v ) { return this.a * v + this.b ; }

ctx = {
	fn: function( v ) { return v * 2 + 1 ; } ,
	object: object
} ;

operators = {
	D: function( args ) {
		var sum = 0 , n = args[ 0 ] , faces = args[ 1 ] ;
		for ( ; n > 0 ; n -- ) { sum += 1 + Math.floor( Math.random() * faces ) ; }
		return sum ;
	}
} ;

parsed = Expression.parse( '3 D 6' , operators ) ;
//deb( parsed ) ;
v = parsed.getFinalValue( ctx ) ;
//deb( v ) ;
doormen.equals( v >= 1 && v <= 18 , true ) ;

parse/exec apply operator and substitution regexp.

var parsed , ctx , regexp ;

regexp = /hello/ ;
kungFig.parse.builtin.regex.toExtended( regexp ) ;

ctx = {
	str: 'hello world!' ,
	regexp: regexp ,
	array: [
		'hi' ,
		'hello' ,
		'hi there!' ,
		'hello world!'
	]
} ;

parsed = Expression.parse( '$regexp.filter -> $array' ) ;
//deb( parsed ) ;
doormen.equals( parsed.getFinalValue( ctx ) , [ 'hello' , 'hello world!' ] ) ;

kungFig.parse.builtin.regex.toSubstitution( regexp , 'hi' ) ;

parsed = Expression.parse( '$regexp.substitute -> $str' ) ;
//deb( parsed ) ;
doormen.equals( parsed.getFinalValue( ctx ) , 'hi world!' ) ;

KFG stringify

stringify string.

doormen.equals( stringify( "Hello World!" ) , 'Hello World!\n' ) ;
doormen.equals( stringify( "a:1" ) , '> a:1\n' ) ;
doormen.equals( stringify( "123" ) , '> 123\n' ) ;
doormen.equals( stringify( "123.45" ) , '> 123.45\n' ) ;
doormen.equals( stringify( "Hello: World!" ) , '> Hello: World!\n' ) ;
doormen.equals( stringify( "[Hello World!]" ) , '> [Hello World!]\n' ) ;
doormen.equals( stringify( "<hello>" ) , '> <hello>\n' ) ;
doormen.equals( stringify( "(hello)" ) , '> (hello)\n' ) ;
doormen.equals( stringify( "   Hello World!" ) , '>    Hello World!\n' ) ;
doormen.equals( stringify( "Hello World!   " ) , '> Hello World!   \n' ) ;

doormen.equals( stringify( "Hello\nWorld!" ) , '> Hello\n> World!\n' ) ;
doormen.equals( stringify( "One...\nTwo...\n\nThree!" ) , '> One...\n> Two...\n> \n> Three!\n' ) ;
doormen.equals( stringify( "One...\n\tTwo...\n\nThree!" ) , '> One...\n> \tTwo...\n> \n> Three!\n' ) ;

stringify string with option 'preferQuotes'.

doormen.equals( stringify( "Hello World!" , { preferQuotes: true } ) , 'Hello World!\n' ) ;
doormen.equals( stringify( "a:1" , { preferQuotes: true } ) , '"a:1"\n' ) ;
doormen.equals( stringify( "123" , { preferQuotes: true } ) , '"123"\n' ) ;
doormen.equals( stringify( "123.45" , { preferQuotes: true } ) , '"123.45"\n' ) ;
doormen.equals( stringify( "Hello: World!" , { preferQuotes: true } ) , '"Hello: World!"\n' ) ;
doormen.equals( stringify( "[Hello World!]" , { preferQuotes: true } ) , '"[Hello World!]"\n' ) ;
doormen.equals( stringify( "<hello>" , { preferQuotes: true } ) , '"<hello>"\n' ) ;
doormen.equals( stringify( "(hello)" , { preferQuotes: true } ) , '"(hello)"\n' ) ;
doormen.equals( stringify( "   Hello World!" , { preferQuotes: true } ) , '"   Hello World!"\n' ) ;
doormen.equals( stringify( "Hello World!   " , { preferQuotes: true } ) , '"Hello World!   "\n' ) ;

doormen.equals( stringify( "Hello\nWorld!" , { preferQuotes: true } ) , '"Hello\\nWorld!"\n' ) ;
doormen.equals( stringify( "One...\nTwo...\n\nThree!" , { preferQuotes: true } ) , '"One...\\nTwo...\\n\\nThree!"\n' ) ;
doormen.equals( stringify( "One...\n\tTwo...\n\nThree!" , { preferQuotes: true } ) , '"One...\\n\\tTwo...\\n\\nThree!"\n' ) ;

stringify non-string scalar.

doormen.equals( stringify( undefined ) , "null\n" ) ;
doormen.equals( stringify( null ) , "null\n" ) ;
doormen.equals( stringify( true ) , "true\n" ) ;
doormen.equals( stringify( false ) , "false\n" ) ;
doormen.equals( stringify( 123 ) , "123\n" ) ;
doormen.equals( stringify( 123.456 ) , "123.456\n" ) ;

stringify empty object/array.

doormen.equals( stringify( [] ) , '<Array>\n' ) ;
doormen.equals( stringify( {} ) , '<Object>\n' ) ;
doormen.equals( stringify( new TagContainer() ) , '<TagContainer>\n' ) ;

undefined value.

doormen.equals( stringify( {a:{},b:undefined,c:{d:undefined}} ) , 'a: <Object>\nc: <Object>\n' ) ;
doormen.equals( stringify( [{},undefined,{d:undefined}] ) , '- <Object>\n- null\n- <Object>\n' ) ;

stringify a basic object.

var o = {
	a: 1 ,
	b: "bob" ,
	c: null ,
	d: true ,
	e: false ,
	f: NaN ,
	g: Infinity ,
	h: - Infinity ,
	j1: {} ,
	j2: [] ,
	needQuote: {
		a: "1" ,
		c: "null" ,
		d: "true" ,
		e: "false" ,
		f: "NaN" ,
		g: "Infinity" ,
		h: "-Infinity" ,
		sp1: " bob" ,
		sp2: "bob " ,
		sp3: "bob\nbob" ,
		sp4: '"bob\nbob"' ,
	} ,
	"some key": "some value" ,
	"some-key": "some value" ,
	"some_key": "some value" ,
	"1key": "1value" ,
	obj: {
		i: 'ay',
		j: 'djay',
	} ,
	obj2: {
		k: 'K',
		l: 'L',
	} ,
	arr: [ 1,2,3 ] ,
	arr2: [ 4,5,6, [ 7,8,9 ] , { m: 'm' , n: 'n' } ] ,
} ;

var s = stringify( o ) ;
//console.log( s ) ;

// Check that the original object and the stringified/parsed object are equals:
doormen.equals( o , parse( s ) ) ;
//require( 'expect.js' )( o ).to.eql( parse( s ) ) ;

stringify templates.

doormen.equals( stringify( { tpl: Template.create( 'Hello ${name}!' ) } ) , 'tpl: $> Hello ${name}!\n' ) ;
doormen.equals( stringify( { tpl: new Template( 'Hey!\nHello ${name}!' ) } ) , 'tpl: $> Hey!\n\t$> Hello ${name}!\n' ) ;
doormen.equals( stringify( { tpl: Template.create( 'Hello ${name}!' ) } , { preferQuotes: true } ) , 'tpl: $"Hello ${name}!"\n' ) ;
doormen.equals( stringify( { tpl: Template.create( 'Hey!\nHello ${name}!' ) } , { preferQuotes: true } ) , 'tpl: $"Hey!\\nHello ${name}!"\n' ) ;

doormen.equals( stringify( Template.create( 'Hello ${name}!' ) ) , '$> Hello ${name}!\n' ) ;
doormen.equals( stringify( Template.create( 'Hey!\nHello ${name}!' ) ) , '$> Hey!\n$> Hello ${name}!\n' ) ;
doormen.equals( stringify( Template.create( 'Hello ${name}!' ) , { preferQuotes: true } ) , '$"Hello ${name}!"\n' ) ;
doormen.equals( stringify( Template.create( 'Hey!\nHello ${name}!' ) , { preferQuotes: true } ) , '$"Hey!\\nHello ${name}!"\n' ) ;

doormen.equals( stringify( { tpl: Template.create( '' ) } ) , 'tpl: <Template>\n' ) ;
doormen.equals( stringify( Template.create( '' ) ) , '<Template>\n' ) ;

stringify an object with operators.

var o = {
	'+attack': 2,
	'-defense': 1,
	'*time': 0.9,
	'(u-ops)damages': 1.2,
	'()+strange key': 3,
	'()(another strange key)': 5,
	'()-hey': 5,
	'~hey': 5,
	'@@#*>': '/path/to/*/something/',
	'@*>': '/path/to/something/',
	'@': '/path/to/something/',
	'@@': '/path/to/something/',
	list: [ 'one' , 'two' , { "@@": '/path/to/something/' } ] ,
} ;

var s = stringify( o ) ;
//console.log( s ) ;
//console.log( string.escape.control( s ) ) ;
//console.log( parse( s ) ) ;

var expected = 'attack: (+) 2\ndefense: (-) 1\ntime: (*) 0.9\ndamages: (u-ops) 1.2\n+strange key: 3\n"(another strange key)": 5\n"-hey": 5\n~hey: 5\n(#*>) @@/path/to/*/something/\n(*>) @/path/to/something/\n() @/path/to/something/\n() @@/path/to/something/\nlist:\n\t- one\n\t- two\n\t- @@/path/to/something/\n' ;
doormen.equals( s , expected ) ;

// Check that the original object and the stringified/parsed object are equals:
//require( 'expect.js' )( o ).to.eql( parse( s ) ) ;
doormen.equals( o , parse( s ) ) ;

stringify an object with special instances (bin, date, regex).

var o = {
	bin: new Buffer( 'af461e0a' , 'hex' ) ,
	date1: new Date( 123456789 ) ,
	regex1: /abc/ ,
	array: [
		[
			new Date( 123456789 ) ,
			/abc/ig ,
		] ,
		[
			new Date( 123456789 ) ,
			/abc/ig ,
		] ,
	] ,
	object: {
		date2: new Date( 123456789 ) ,
		regex2: /abc/ig ,
	}
} ;

var s = stringify( o ) ;
//console.log( s ) ;
//console.log( string.escape.control( s ) ) ;
//console.log( parse( s ) ) ;

var expected = 'bin: <bin16> af461e0a\ndate1: <date> Fri Jan 02 1970 11:17:36 GMT+0100 (CET)\nregex1: <regex> /abc/\narray:\n\t-\t- <date> Fri Jan 02 1970 11:17:36 GMT+0100 (CET)\n\t\t- <regex> /abc/gi\n\t-\t- <date> Fri Jan 02 1970 11:17:36 GMT+0100 (CET)\n\t\t- <regex> /abc/gi\nobject:\n\tdate2: <date> Fri Jan 02 1970 11:17:36 GMT+0100 (CET)\n\tregex2: <regex> /abc/gi\n' ;
doormen.equals( s , expected ) ;

var o2 = parse( s ) ;

// Check that the original object and the stringified/parsed object are equals:
//expect( o ).to.eql( o2 ) ;

expect( o2.bin ).to.be.a( Buffer ) ;
expect( o2.bin.toString( 'hex' ) ).to.be( o.bin.toString( 'hex' ) ) ;

delete o.bin ;
delete o2.bin ;

doormen.equals( o2 , o ) ;

stringify an object with special custom instances.

function Simple( value )
{
	var self = Object.create( Simple.prototype ) ;
	self.str = value ;
	return self ;
}

function Complex( value )
{
	var self = Object.create( Complex.prototype ) ;
	self.str = value.str ;
	self.int = value.int ;
	return self ;
}

var stringifier = new Map() ;

stringifier.set( Simple.prototype , function Simple( v ) {
	return v.str ;
} ) ;

stringifier.set( Complex.prototype , function Complex( v ) {
	return { str: v.str , int: v.int } ;
} ) ;

var o = {
	simple: Simple( "abc" ) ,	// jshint ignore:line
	complex: Complex( { str: "hello", int: 6 } )	// jshint ignore:line
} ;

//console.log( stringify( o , { classes: stringifier } ) ) ;
doormen.equals(
	stringify( o , { classes: stringifier } ) ,
	"simple: <Simple> abc\ncomplex: <Complex>\n\tstr: hello\n\tint: 6\n"
) ;

stringify an object with tags.

var o = new TagContainer( [
	new Tag( 'if' , 'something > constant' , new TagContainer( [
		new Tag( 'do' , null , 'some tasks' ) ,
		new Tag( 'do' , null , 'some other tasks' )
	] ) ) ,
	new Tag( 'else' , null , new TagContainer( [
		new Tag( 'do' , null , new TagContainer( [
			new Tag( 'do' , null , [ 'one' , 'two' , 'three' ] ) ,
			new Tag( 'do' , null , { a: 1 , b: 2 } )
		] ) )
	] ) )
] ) ;

var s = stringify( o ) ;

//console.log( s ) ;
//console.log( string.escape.control( s ) ) ;
//console.log( parse( s ) ) ;

var expected = '[if something > constant]\n\t[do] some tasks\n\t[do] some other tasks\n[else]\n\t[do]\n\t\t[do]\n\t\t\t- one\n\t\t\t- two\n\t\t\t- three\n\t\t[do]\n\t\t\ta: 1\n\t\t\tb: 2\n' ;
doormen.equals( s , expected ) ;

var o2 = parse( s ) ;

// Check that the original object and the stringified/parsed object are equals:
//expect( o ).to.eql( o2 ) ;

expect( o2 ).to.be.a( TagContainer ) ;
expect( o2.children[ 0 ] ).to.be.a( Tag ) ;

doormen.equals( o2 , o ) ;

stringify an object with tags, featuring custom tags prototype.

function IfTag() {}
IfTag.prototype = Object.create( Tag.prototype ) ;
IfTag.prototype.constructor = IfTag ;

IfTag.create = function createIfTag( tag , attributes , content , shouldParse ) {
	var self = Object.create( IfTag.prototype ) ;
	Tag.call( self , 'if' , attributes , content , shouldParse ) ;
	return self ;
} ;

IfTag.prototype.parseAttributes = function parseAttributes( attributes )
{
	var splitted = attributes.split( / +/ ) ;
	return {
		left: splitted[ 0 ] ,
		operator: splitted[ 1 ] ,
		right: splitted[ 2 ]
	} ;
} ;

IfTag.prototype.stringifyAttributes = function stringifyAttributes() {
	return this.attributes.left + ' ' + this.attributes.operator + ' ' + this.attributes.right ;
} ;

var o = new TagContainer( [
	IfTag.create( 'if' , 'something > constant' , new TagContainer( [
		new Tag( 'do' , null , 'some tasks' ) ,
		new Tag( 'do' , null , 'some other tasks' )
	] ) , true ) ,
	new Tag( 'else' , null , new TagContainer( [
		new Tag( 'do' , null , new TagContainer( [
			new Tag( 'do' , null , [ 'one' , 'two' , 'three' ] ) ,
			new Tag( 'do' , null , { a: 1 , b: 2 } )
		] ) )
	] ) )
] ) ;

//console.log( o ) ;
var s = stringify( o ) ;

//console.log( s ) ;
//console.log( string.escape.control( s ) ) ;
//console.log( parse( s ) ) ;

var expected = '[if something > constant]\n\t[do] some tasks\n\t[do] some other tasks\n[else]\n\t[do]\n\t\t[do]\n\t\t\t- one\n\t\t\t- two\n\t\t\t- three\n\t\t[do]\n\t\t\ta: 1\n\t\t\tb: 2\n' ;
doormen.equals( s , expected ) ;

var o2 = parse( s , { tags: { if: IfTag.create } } ) ;
//console.log( o2 ) ;

// Check that the original object and the stringified/parsed object are equals:
//expect( o ).to.eql( o2 ) ;

expect( o2 ).to.be.a( TagContainer ) ;
expect( o2.children[ 0 ] ).to.be.a( Tag ) ;

doormen.equals( o2 , o ) ;

KFG parse

parse string at top-level.

doormen.equals( parse( '"Hello World!"' ) , "Hello World!" ) ;
doormen.equals( parse( '> Hello World!' ) , "Hello World!" ) ;
doormen.equals( parse( '>   Hello World!' ) , "  Hello World!" ) ;
doormen.equals( parse( '>   Hello World!  ' ) , "  Hello World!  " ) ;
doormen.equals( parse( '> \tHello World!' ) , "\tHello World!" ) ;
doormen.equals( parse( '> \t\t\tHello\t\tWorld!' ) , "\t\t\tHello\t\tWorld!" ) ;
doormen.equals( parse( 'Hello World!' ) , "Hello World!" ) ;
//doormen.equals( parse( '  Hello World!  ' ) , "Hello World!" ) ;	// Do not work with space indentation
doormen.equals( parse( '"123"' ) , "123" ) ;
doormen.equals( parse( '"123.45"' ) , "123.45" ) ;
doormen.equals( parse( '> 123' ) , "123" ) ;
doormen.equals( parse( '> 123.45' ) , "123.45" ) ;
//doormen.equals( parse( 'this is not: an object' ) , "this is not: an object" ) ;

parse multi-line string at top-level.

doormen.equals( parse( '> Hello\n> World!' ) , "Hello\nWorld!" ) ;
//doormen.equals( parse( 'Hello\nWorld!' ) , "Hello\nWorld!" ) ;

parse non-string scalar at top-level.

doormen.equals( parse( 'null' ) , null ) ;
doormen.equals( parse( 'true' ) , true ) ;
doormen.equals( parse( 'false' ) , false ) ;
doormen.equals( parse( '123' ) , 123 ) ;
doormen.equals( parse( '123.456' ) , 123.456 ) ;

parse instance at top-level.

doormen.equals( JSON.stringify( parse( "<Bin16> 22" ) ) , '{"type":"Buffer","data":[34]}' ) ;
doormen.equals( JSON.stringify( parse( "<Object>" ) ) , '{}' ) ;
doormen.equals( JSON.stringify( parse( "<Object>\na: 1" ) ) , '{"a":1}' ) ;
doormen.equals( parse( "<Template> :string" ).toString() , ':string' ) ;

numbers and string ambiguity.

doormen.equals( parse( "v:1" ) , {v:1} ) ;
doormen.equals( parse( "v:1l" ) , {v:"1l"} ) ;
doormen.equals( parse( "v:10e2" ) , {v:1000} ) ;
doormen.equals( parse( "v:123.5" ) , {v:123.5} ) ;
doormen.equals( parse( "v:123.e5" ) , {v:"123.e5"} ) ;

constant and string ambiguity.

doormen.equals( parse( "v:true" ) , {v:true} ) ;
doormen.equals( parse( "v:true or false" ) , {v:"true or false"} ) ;

unquoted key ambiguity.

doormen.equals( parse( "first-name:Joe" ) , {"first-name":"Joe"} ) ;
doormen.equals( parse( "first-name :Joe" ) , {"first-name":"Joe"} ) ;
doormen.equals( parse( "first-name   :Joe" ) , {"first-name":"Joe"} ) ;
doormen.equals( parse( "first-name: Joe" ) , {"first-name":"Joe"} ) ;
doormen.equals( parse( "first-name:   Joe" ) , {"first-name":"Joe"} ) ;
doormen.equals( parse( "first-name : Joe" ) , {"first-name":"Joe"} ) ;
doormen.equals( parse( "first-name   :   Joe" ) , {"first-name":"Joe"} ) ;

doormen.equals( parse( "first name: Joe" ) , {"first name":"Joe"} ) ;
doormen.equals( parse( "first name   :   Joe" ) , {"first name":"Joe"} ) ;

doormen.equals( parse( "null: Joe" ) , {"null":"Joe"} ) ;
doormen.equals( parse( "true: Joe" ) , {"true":"Joe"} ) ;
doormen.equals( parse( "false: Joe" ) , {"false":"Joe"} ) ;

quoted key.

doormen.equals( parse( '"some:\\"bizarre:\\nkey" : value' ) , {"some:\"bizarre:\nkey":"value"} ) ;

unquoted tabs should not be parsed as string but as undefined.

var o ;

o = parse( "object:\t\t\t\n\ta: 1" ) ;
//console.log( o ) ;
doormen.equals( o , { object: { a: 1 } } ) ;

o = parse( "[tag]\t\t\t\n\ta: 1" ) ;
//console.log( o ) ; console.log( JSON.stringify( o ) ) ;
doormen.equals( JSON.stringify( o ) , '{"children":[{"name":"tag","content":{"a":1},"attributes":null}]}' ) ;

comment ambiguity.

doormen.equals( parse( "#comment\nkey: value" ) , { key: "value" } ) ;
doormen.equals( parse( "key: value # comment" ) , { key: "value # comment" } ) ;
doormen.equals( parse( "object:\n\t# comment\n\tkey: value" ) , { object: { key: "value" } } ) ;
doormen.equals( parse( "object:\n# comment\n\tkey: value" ) , { object: { key: "value" } } ) ;
doormen.equals( parse( "object:\n\t\t# comment\n\tkey: value" ) , { object: { key: "value" } } ) ;

parse a basic file.

var o = parse( fs.readFileSync( __dirname + '/sample/kfg/simple.kfg' , 'utf8' ) ) ;

doormen.equals( o , {
	a: 1,
	b: 2,
	c: 3,
	'some key': 'some value',
	d: null,
	e1: true,
	e2: true,
	e3: true,
	f1: false,
	f2: false,
	f3: false,
	g: NaN,
	h: Infinity,
	i: -Infinity,
	j1: {},
	j2: [],
	sub: 
		{ sub: { 'another key': 'another value' },
		k: 1,
		l: 2,
		sub2: { subway: 'no!' } },
	sub2: { no: 'way' },
	sub3: { nooo: 'wai!' },
	text: "A cool story:\n\nIt all started a Friday..." ,
	"inline-string": "This is an inline string!" ,
	list: [ 'one', 'two', 'three' ],
	'list embedded': 
		[ { 'first name': 'Bill', 'last name': 'Baroud' },
		{ 'first name': 'Joe', 'last name': 'Doe' },
		[ [ 'one', 'two', 'three' ],
		[ 'four', 'five' ],
		[ 'six', 'seven' ] ],
		{ 'first name': 'Bill', 'last name': 'Baroud' },
		{ 'first name': 'Joe', 'last name': 'Doe' },
		[ [ 'one', 'two', 'three' ],
		[ 'four', 'five' ],
		[ 'six', 'seven' ] ] ]
} ) ;

//console.log( kungFig.getMeta( o ).getFirstTag( 'meta' ).content ) ;
doormen.equals( kungFig.getMeta( o ).getFirstTag( 'meta' ).content , { content: "test" } ) ;

parse a file using 4-spaces to indent.

var o = parse( fs.readFileSync( __dirname + '/sample/kfg/spaces-indent.kfg' , 'utf8' ) ) ;

doormen.equals( o , {
	a: 1,
	b: 2,
	c: 3,
	'some key': 'some value',
	d: null,
	e1: true,
	e2: true,
	e3: true,
	f1: false,
	f2: false,
	f3: false,
	g: NaN,
	h: Infinity,
	i: -Infinity,
	j1: {},
	j2: [],
	sub: 
		{ sub: { 'another key': 'another value' },
		k: 1,
		l: 2,
		sub2: { subway: 'no!' } },
	sub2: { no: 'way' },
	sub3: { nooo: 'wai!' },
	text: "A cool story:\n\nIt all started a Friday..." ,
	"inline-string": "This is an inline string!" ,
	list: [ 'one', 'two', 'three' ],
	'list embedded': 
		[ { 'first name': 'Bill', 'last name': 'Baroud' },
		{ 'first name': 'Joe', 'last name': 'Doe' },
		[ [ 'one', 'two', 'three' ],
		[ 'four', 'five' ],
		[ 'six', 'seven' ] ],
		{ 'first name': 'Bill', 'last name': 'Baroud' },
		{ 'first name': 'Joe', 'last name': 'Doe' },
		[ [ 'one', 'two', 'three' ],
		[ 'four', 'five' ],
		[ 'six', 'seven' ] ] ]
} ) ;

//console.log( kungFig.getMeta( o ).getFirstTag( 'meta' ).content ) ;
doormen.equals( kungFig.getMeta( o ).getFirstTag( 'meta' ).content , { content: "test" } ) ;

parse ref.

var o ;
var ctx = { name: "Bob" , bob: { age: 43 } } ;

o = parse( "ref: <Ref>" ) ;
doormen.equals( o.ref.get() , undefined ) ;

o = parse( "ref: $name" ) ;
doormen.equals( o.ref.get() , undefined ) ;

o = parse( "ref: $name\nref2: $bob.age" ) ;
doormen.equals( o.ref.get( ctx ) , "Bob" ) ;
doormen.equals( o.ref.toString( ctx ) , "Bob" ) ;
doormen.equals( o.ref2.get( ctx ) , 43 ) ;
doormen.equals( o.ref2.toString( ctx ) , "43" ) ;

parse applicable ref.

var o ;
var ctx = { name: "Bob" , bob: { age: 43 } } ;

o = parse( "ref: $$name\nref2: $$bob.age" ) ;
doormen.equals( o.ref.get( ctx ) , o.ref ) ;
doormen.equals( o.ref2.get( ctx ) , o.ref2 ) ;

doormen.equals( o.ref.apply( ctx ) , "Bob" ) ;
doormen.equals( o.ref2.apply( ctx ) , 43 ) ;

parse expression.

var o ;
var ctx = { name: "Bob" , bob: { age: 43 } , bill: { age: 37 } } ;

//o = parse( "exp: <Expression>" ) ;
//doormen.equals( o.exp , undefined ) ;

o = parse( "exp: $= $name" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , "Bob" ) ;

o = parse( "exp: $= $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , 43 ) ;

o = parse( "exp: $= $bob.age + 2" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , 45 ) ;

o = parse( "exp: $= 5 + $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , 48 ) ;

o = parse( "exp: $=5 + $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , 48 ) ;

o = parse( "exp: $= $bob.age - $bill.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , 6 ) ;

o = parse( "exp: $= - $bill.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , -37 ) ;

o = parse( "exp: $= ( $bill.age + 3 ) / 10" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , 4 ) ;

o = parse( "exp: $= $bill.age < $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , true ) ;

o = parse( "exp: $= $bill.age > $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , false ) ;

o = parse( "exp: $= $bill.age == $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , false ) ;

o = parse( "exp: $= ( $bill.age + 6 ) == $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , true ) ;

o = parse( "exp: $= $bill.age != $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , true ) ;

o = parse( "exp: $= ( $bill.age + 6 ) != $bob.age" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , false ) ;

o = parse( "exp: $= ! ( $bill.age == $bob.age )" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , true ) ;

o = parse( "exp: $= ! ( ( $bill.age + 6 ) == $bob.age )" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , false ) ;

parse expression.

var o ;
var ctx = { name: "Bob" , bob: { age: 43 } , bill: { age: 37 } } ;

o = parse( "exp: $$= $bob.age + 2" ) ;
doormen.equals( o.exp.getFinalValue( ctx ) , o.exp ) ;
doormen.equals( o.exp.apply( ctx ) , 45 ) ;

parse templates.

var o ;

// console.log( o.tpl ) ;
// console.log( o.tpl.toString() ) ;
// console.log( o.tpl.toString( { name: "Bob" } ) ) ;

o = parse( "tpl: <Template>" ) ;
doormen.equals( o.tpl.toString() , '' ) ;

o = parse( "tpl: $> Hello ${name}!" ) ;
doormen.equals( o.tpl.toString() , 'Hello (undefined)!' ) ;
doormen.equals( o.tpl.toString( { name: "Bob" } ) , 'Hello Bob!' ) ;

o = parse( "tpl:\n\t$> Hello ${name}!" ) ;
doormen.equals( o.tpl.toString() , 'Hello (undefined)!' ) ;
doormen.equals( o.tpl.toString( { name: "Bob" } ) , 'Hello Bob!' ) ;

o = parse( "tpl:\n\t$> Hello ${name}!\n\t$> How are you ${name}?" ) ;
doormen.equals( o.tpl.toString() , 'Hello (undefined)!\nHow are you (undefined)?' ) ;
doormen.equals( o.tpl.toString( { name: "Bob" } ) , 'Hello Bob!\nHow are you Bob?' ) ;

o = parse( 'tpl: $"Hello ${name}!"' ) ;
doormen.equals( o.tpl.toString() , 'Hello (undefined)!' ) ;
doormen.equals( o.tpl.toString( { name: "Bob" } ) , 'Hello Bob!' ) ;

// Top-level templates
o = parse( "<Template>" ) ;
doormen.equals( o.toString() , '' ) ;

o = parse( '$"Hello ${name}!"' ) ;
doormen.equals( o.toString() , 'Hello (undefined)!' ) ;
doormen.equals( o.toString( { name: "Bob" } ) , 'Hello Bob!' ) ;

o = parse( '$> Hello ${name}!' ) ;
doormen.equals( o.toString() , 'Hello (undefined)!' ) ;
doormen.equals( o.toString( { name: "Bob" } ) , 'Hello Bob!' ) ;

o = parse( '$> Hey!\n$> Hello ${name}!' ) ;
doormen.equals( o.toString() , 'Hey!\nHello (undefined)!' ) ;
doormen.equals( o.toString( { name: "Bob" } ) , 'Hey!\nHello Bob!' ) ;

parse applicable templates.

var o ;

o = parse( "tpl: $$> Hello ${name}!" ) ;
doormen.equals( o.tpl.toString( { name: "Bob" } ) , 'Hello ${name}!' ) ;
doormen.equals( o.tpl.apply( { name: "Bob" } ) , 'Hello Bob!' ) ;

o = parse( "tpl:\n\t$$> Hello ${name}!" ) ;
doormen.equals( o.tpl.toString( { name: "Bob" } ) , 'Hello ${name}!' ) ;
doormen.equals( o.tpl.apply( { name: "Bob" } ) , 'Hello Bob!' ) ;

o = parse( 'tpl: $$"Hello ${name}!"' ) ;
doormen.equals( o.tpl.toString( { name: "Bob" } ) , 'Hello ${name}!' ) ;
doormen.equals( o.tpl.apply( { name: "Bob" } ) , 'Hello Bob!' ) ;

parse template elements.

var o , o2 ;

o = parse( "el: $%> horse" ) ;
surfaceEquals( o.el , { t: "horse" , babel: Babel.default } ) ;
doormen.equals( o.el.toString() , 'horse' ) ;

o = parse( "el: $%> horse[altn:horse|horses]" ) ;
surfaceEquals( o.el , { t: "horse" , altn: [ "horse" , "horses" ] , babel: Babel.default } ) ;
doormen.equals( o.el.toString() , 'horse' ) ;

o2 = parse( '$> I like ${el}[n:many]!' ) ;
doormen.equals( o2.toString( o ) , 'I like horses!' ) ;

parse template sentence and element, and use a babel instance to localize it.

var o , o2 ;

var babel = Babel.create() ;

babel.extend( {
	en: {
		gIndex: { m: 0 , f: 1 , n: 3 , h: 3 } ,
		elements: {
			apple: { g:'n', altn: [ 'apple' , 'apples' ] } ,
			horse: { altn: [ 'horse' , 'horses' ] } ,
		}
	} ,
	fr: {
		gIndex: { m: 0 , f: 1 , n: 2 , h: 2 } ,
		sentences: {
			"I like ${el}[n:many]!": "J'aime les ${el}[n:many]!" ,
			"I like ${el}[n:many/g:f]!": "J'aime les ${el}[n:many/g:f]!" ,
		} ,
		elements: {
			apple: { g:'f', altn: [ 'pomme' , 'pommes' ] } ,
			horse: { altng: [ [ 'cheval' , 'jument' ] , [ 'chevaux' , 'juments' ] ] } ,
		}
	}
} ) ;

var babelFr = babel.use( 'fr' ) ;

// Using Babel fr
o = parse( "el: $%> horse" ) ;
surfaceEquals( o.el , { t: "horse" , babel: Babel.default } ) ;
doormen.equals( o.el.toString( { __babel: babelFr } ) , 'cheval' ) ;

o = parse( "el: $%> horse[altn:horse|horses]" ) ;
surfaceEquals( o.el , { t: "horse" , altn: [ "horse" , "horses" ] , babel: Babel.default } ) ;
doormen.equals( o.el.toString( { __babel: babelFr } ) , 'cheval' ) ;
o.__babel = babelFr ;

o2 = parse( '$> I like ${el}[n:many]!' ) ;
doormen.equals( o2.toString( o ) , "J'aime les chevaux!" ) ;

o2 = parse( '$> I like ${el}[n:many/g:f]!' ) ;
doormen.equals( o2.toString( o ) , "J'aime les juments!" ) ;

o.el.g = 'f' ;
o2 = parse( '$> I like ${el}[n:many]!' ) ;
doormen.equals( o2.toString( o ) , "J'aime les juments!" ) ;

parse a file with operators.

var o = parse( fs.readFileSync( __dirname + '/sample/kfg/ops.kfg' , 'utf8' ) ) ;

doormen.equals( o , {
	'+attack': 2,
	'+defense': -1,
	'*time': 0.9,
	'(u-ops)damages': 1.2,
	'()+strange key': 3,
	'()(another strange key)': 5,
	'@include': "path/to/include.kfg",
	'@@mandatory include': "path/to/mandatory-include.kfg",
	'@+include2': 'path/to/include.kfg',
	'@(u-ops)include3': 'path/to/include.kfg',
	'@@(u-ops)include4': 'path/to/mandatory-include.kfg',
	'*>merge': { something: 1, 'something else': 12 },
	'#+foreach': [1,2,3],
	list: [ 'one' , 'two' , { '@@': 'path/to/include.kfg' } ] ,
	'@*>': 'path/to/something',
} ) ;

parse a file with special instances (json, bin, date, regex).

var o = parse( fs.readFileSync( __dirname + '/sample/kfg/instances.kfg' , 'utf8' ) ) ;

//console.log( JSON.stringify( o ) ) ;

doormen.equals(
	JSON.stringify( o ) ,
	'{"a":1234,"bin":{"type":"Buffer","data":[253,16,75,25]},"date1":"2016-04-29T10:08:14.000Z","date2":"2016-04-29T10:08:08.645Z","b":"toto","regex1":{},"sub":{"sub":{"date3":"1970-01-01T00:00:01.000Z","regex2":{}}},"d":2,"json":{"a":1,"b":2,"array":[1,2,"three"]}}'
) ;

doormen.equals( o.regex1 instanceof RegExp , true ) ;
doormen.equals( o.regex1.toString() , "/abc/" ) ;

doormen.equals( o.sub.sub.regex2 instanceof RegExp , true ) ;
doormen.equals( o.sub.sub.regex2.toString() , "/abc/m" ) ;

doormen.equals( o.bin.toString( 'hex' ) , "fd104b19" ) ;

parse a file with special custom instances.

function Simple( value )
{
	var self = Object.create( Simple.prototype ) ;
	self.str = value ;
	return self ;
}

function Complex( value )
{
	var self = Object.create( Complex.prototype ) ;
	self.str = value.str ;
	self.int = value.int ;
	return self ;
}

var options = {
	classes: {
		simple: Simple ,
		complex: Complex
	}
} ;

var o = parse( fs.readFileSync( __dirname + '/sample/kfg/custom-instances.kfg' , 'utf8' ) , options ) ;

//console.log( o ) ;
doormen.equals( JSON.stringify( o ) , '{"simple":{"str":"abc"},"complex":{"str":"hello","int":6}}' ) ;

parse tags.

doormen.equals( JSON.stringify( parse( '[tag]' ) ) , '{"children":[{"name":"tag","attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag] text' ) ) , '{"children":[{"name":"tag","content":"text","attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag] "text"' ) ) , '{"children":[{"name":"tag","content":"text","attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag] > text' ) ) , '{"children":[{"name":"tag","content":"text","attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag]\n\t> text' ) ) , '{"children":[{"name":"tag","content":"text","attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag] true' ) ) , '{"children":[{"name":"tag","content":true,"attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag] 123' ) ) , '{"children":[{"name":"tag","content":123,"attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag] <Object>' ) ) , '{"children":[{"name":"tag","content":{},"attributes":null}]}' ) ;
doormen.equals( JSON.stringify( parse( '[tag] <Object>\n\ta: 1\n\tb: 2' ) ) , '{"children":[{"name":"tag","content":{"a":1,"b":2},"attributes":null}]}' ) ;

parse a file containing tags.

var o = parse( fs.readFileSync( __dirname + '/sample/kfg/tag.kfg' , 'utf8' ) ) ;

//console.log( o ) ;
//console.log( string.inspect( { style: 'color' , depth: 15 } , o ) ) ;
//console.log( string.escape.control( JSON.stringify( o ) ) ) ;
//console.log( JSON.stringify( o ) ) ;

doormen.equals( JSON.stringify( o ) , '{"children":[{"name":"tag","content":{"some":"value","another":"one"},"attributes":"id1"},{"name":"tag","content":{"some":"other value","nested":{"a":1,"b":2,"c":{"children":[{"name":"if","content":{"children":[{"name":"do","content":"some work","attributes":null}]},"attributes":"something > constant"},{"name":"else","content":{"children":[{"name":"do","content":"something else","attributes":null}]},"attributes":null}]}}},"attributes":"id2"},{"name":"container","content":{"children":[{"name":"tag","attributes":null},{"name":"anothertag","attributes":null},{"name":"complex","attributes":"tag hello=\\"<world]]]\\\\\\"!\\" some[3].path[6]"}]},"attributes":null}]}' ) ;

//console.log( o.children[ 2 ].content.children[ 0 ].parent ) ;
//console.log( o.children[ 2 ].content.children[ 0 ].getParentTag() ) ;
doormen.equals( o.children[ 2 ].getParentTag() , null ) ;
doormen.equals( o.children[ 2 ] === o.children[ 2 ].content.children[ 0 ].getParentTag() , true ) ;

parse a file containing tags, with custom tags prototypes.

function IfTag() {}
IfTag.prototype = Object.create( Tag.prototype ) ;
IfTag.prototype.constructor = IfTag ;

IfTag.create = function createIfTag( tag , attributes , content , shouldParse ) {
	var self = Object.create( IfTag.prototype ) ;
	Tag.call( self , 'if' , attributes , content , shouldParse ) ;
	return self ;
} ;

IfTag.prototype.parseAttributes = function parseAttributes( attributes )
{
	var splitted = attributes.split( / +/ ) ;
	return {
		left: splitted[ 0 ] ,
		operator: splitted[ 1 ] ,
		right: splitted[ 2 ]
	} ;
} ;

IfTag.prototype.stringifyAttributes = function stringifyAttributes() {
	return this.attributes.left + ' ' + this.attributes.operator + ' ' + this.attributes.right ;
} ;


var o = parse( fs.readFileSync( __dirname + '/sample/kfg/tag.kfg' , 'utf8' ) , { tags: { if: IfTag.create } } ) ;

//console.log( o ) ;
//console.log( string.inspect( { style: 'color' , depth: 15 } , o ) ) ;
//console.log( string.escape.control( JSON.stringify( o ) ) ) ;

doormen.equals( JSON.stringify( o ) , '{"children":[{"name":"tag","content":{"some":"value","another":"one"},"attributes":"id1"},{"name":"tag","content":{"some":"other value","nested":{"a":1,"b":2,"c":{"children":[{"name":"if","content":{"children":[{"name":"do","content":"some work","attributes":null}]},"attributes":{"left":"something","operator":">","right":"constant"}},{"name":"else","content":{"children":[{"name":"do","content":"something else","attributes":null}]},"attributes":null}]}}},"attributes":"id2"},{"name":"container","content":{"children":[{"name":"tag","attributes":null},{"name":"anothertag","attributes":null},{"name":"complex","attributes":"tag hello=\\"<world]]]\\\\\\"!\\" some[3].path[6]"}]},"attributes":null}]}' ) ;

Meta-Tag

parse meta-tag.

var o ;
o = parse( '[[meta]]\n\tauthor: Joe Doe\n\tcopyright: 2016\nsome: data' ) ;
/*
console.log( o ) ;
console.log( kungFig.getMeta( o ) ) ;
console.log( kungFig.getMeta( o ).getTags( 'meta' )[ 0 ] ) ;
*/
doormen.equals( o , { some: "data" } ) ;
doormen.equals( kungFig.getMeta( o ).getTags( 'meta' )[ 0 ].content , { author: "Joe Doe" , copyright: 2016 } ) ;

//console.log( stringify( o ) ) ;
doormen.equals( stringify( o ) , '[[meta]]\n\tauthor: Joe Doe\n\tcopyright: 2016\n\nsome: data\n' ) ;

stringify meta-tag.

var o ;
o = { some: "data" } ;
kungFig.setMeta( o , [ new Tag( 'meta' , undefined , { author: "Joe Doe" , copyright: 2016 } ) ] ) ;

//console.log( stringify( o ) ) ;
doormen.equals( stringify( o ) , '[[meta]]\n\tauthor: Joe Doe\n\tcopyright: 2016\n\nsome: data\n' ) ;

meta doctype filtering.

var o , kfg ;

kfg = '[[doctype supadoc]]\nsome: data' ;

o = parse( kfg ) ;
doormen.equals( kungFig.getMeta( o ).getTags( "doctype" )[ 0 ].attributes , "supadoc" ) ;
doormen.equals( o , { some: "data" } ) ;

o = parse( kfg , { doctype: "supadoc" } ) ;
doormen.equals( kungFig.getMeta( o ).getTags( "doctype" )[ 0 ].attributes , "supadoc" ) ;
doormen.equals( o , { some: "data" } ) ;

doormen.shouldThrow( () => parse( kfg , { doctype: "baddoc" } ) ) ;

o = parse( kfg , { doctype: [ "cooldoc" , "supadoc" ] } ) ;
doormen.equals( kungFig.getMeta( o ).getTags( "doctype" )[ 0 ].attributes , "supadoc" ) ;
doormen.equals( o , { some: "data" } ) ;

doormen.shouldThrow( () => parse( kfg , { doctype: [ "baddoc" , "wrongdoc" ] } ) ) ;

kfg = '\nsome: data' ;
doormen.shouldThrow( () => parse( kfg , { doctype: "supadoc" } ) ) ;
doormen.shouldThrow( () => parse( kfg , { doctype: [ "supadoc" , "cooldoc" ] } ) ) ;

parse meta-tag, with meta hook.

var o , hookTriggered = 0 ;

var options = {
	metaHook: function( meta ) {
		//console.log( "Received meta: " , meta.getTags( 'meta' )[ 0 ].content ) ;
		doormen.equals( meta.getTags( 'meta' )[ 0 ].content , { author: "Joe Doe" , copyright: 2016 } ) ;
		hookTriggered ++ ;
	}
} ;

o = parse( '[[meta]]\n\tauthor: Joe Doe\n\tcopyright: 2016\nsome: data' , options ) ;

/*
console.log( o ) ;
console.log( kungFig.getMeta( o ) ) ;
console.log( kungFig.getMeta( o ).getTags( 'meta' )[ 0 ] ) ;
*/

doormen.equals( hookTriggered , 1 ) ;
doormen.equals( o , { some: "data" } ) ;
doormen.equals( kungFig.getMeta( o ).getTags( 'meta' )[ 0 ].content , { author: "Joe Doe" , copyright: 2016 } ) ;

//console.log( stringify( o ) ) ;
doormen.equals( stringify( o ) , '[[meta]]\n\tauthor: Joe Doe\n\tcopyright: 2016\n\nsome: data\n' ) ;

meta tag after body started should throw.

doormen.shouldThrow( function() {
	parse( '[[meta]]\n\tauthor: Joe Doe\n\tcopyright: 2016\nsome: data\n[[meta]]' ) ;
} ) ;

meta hook & loading (include, ...).

var o , hookTriggered = 0 , nonIncludeHookTriggered = 0 , includeHookTriggered = 0 ;

var options = {
	metaHook: function( meta , options ) {
		//if ( meta ) { console.log( "Received meta: " , meta , "\n>>>" , meta.getFirstTag( 'meta' ).content ) ; }
		//else { console.log( "No meta" ) ; }
		
		//doormen.equals( meta.getTags( 'meta' )[ 0 ].content , { author: "Joe Doe" , copyright: 2016 } ) ;
		hookTriggered ++ ;
		
		if ( [ "meta-hook.kfg" , "meta-hook-include.kfg" ].indexOf( pathModule.basename( options.file ) ) === -1 )
		{
			throw new Error( "Bad options.file" ) ;
		}
		
		if ( options.isInclude ) { includeHookTriggered ++ ; }
		else { nonIncludeHookTriggered ++ ; }
	}
} ;

o = kungFig.load( __dirname + '/sample/kfg/meta-hook.kfg' , options ) ;

//console.log( "data:" , o ) ;
//console.log( "meta:" , kungFig.getMeta( o ) , "\n###" , kungFig.getMeta( o ).getFirstTag( 'meta' ).content ) ;

doormen.equals( hookTriggered , 2 ) ;
doormen.equals( includeHookTriggered , 1 ) ;
doormen.equals( nonIncludeHookTriggered , 1 ) ;
doormen.equals( o , { include: { some: { more: "content"  } } , some: "content" } ) ;
doormen.equals( kungFig.getMeta( o ).getFirstTag( 'meta' ).content , "master" ) ;

LabelTag

label attributes parse.

doormen.equals(
	LabelTag.parseAttributes( 'label' ) ,
	'label'
) ;

doormen.equals(
	LabelTag.parseAttributes( '' ) ,
	''
) ;

label attributes stringify.

doormen.equals(
	LabelTag.stringifyAttributes( 'label' ) ,
	'label'
) ;

doormen.equals(
	LabelTag.stringifyAttributes( 'label[]' ) ,
	'"label[]"'
) ;

LabelTag parse.

var o = parse( '[LabelTag my-label]' , { tags: { LabelTag: LabelTag } } ) ;

//console.log( "parsed:" , o ) ;

// Doormen fails with constructors ATM
doormen.equals( JSON.parse( JSON.stringify( o ) ) , {
	children: [
		{
			name: 'LabelTag' ,
			attributes: 'my-label' ,
			content: undefined
		}
	] 
} ) ;

VarTag

ClassicTag

classic attributes parse.

doormen.equals(
	ClassicTag.parseAttributes( 'width=1280 height=1024 src="/css/main.css" active' ) ,
	{ width: 1280, height: 1024, src: '/css/main.css', active: true }
) ;

doormen.equals(
	ClassicTag.parseAttributes( 'active width=1280 height=1024 src="/css/main.css"' ) ,
	{ width: 1280, height: 1024, src: '/css/main.css', active: true }
) ;

doormen.equals(
	ClassicTag.parseAttributes( '  width=1280  height = 1024  src="/css/main.css" active ' ) ,
	{ width: 1280, height: 1024, src: '/css/main.css', active: true }
) ;

doormen.equals(
	ClassicTag.parseAttributes( 'width=1280 height=1024 src="/css/main.css" active empty=""' ) ,
	{ width: 1280, height: 1024, src: '/css/main.css', active: true , empty: '' }
) ;

doormen.equals(
	ClassicTag.parseAttributes( 'width:1280 height:1024 src:"/css/main.css" active' , ':' ) ,
	{ width: 1280, height: 1024, src: '/css/main.css', active: true }
) ;

classic attributes stringify.

//console.log( ClassicTag.stringifyAttributes( { width: 1280, height: 1024, src: '/css/main.css', active: true } ) ) ;

doormen.equals(
	ClassicTag.stringifyAttributes( { width: 1280, height: 1024, src: '/css/main.css', active: true } ) ,
	'width=1280 height=1024 src="/css/main.css" active' 
) ;

ClassicTag parse.

var o = parse( '[ClassicTag width=1280 height=1024 src="/css/main.css" active]' , { tags: { ClassicTag: ClassicTag } } ) ;

//console.log( "parsed:" , o ) ;

// Doormen fails with constructors ATM
doormen.equals( JSON.parse( JSON.stringify( o ) ) , {
	children: [
		{
			name: 'ClassicTag' ,
			attributes: { width: 1280, height: 1024, src: '/css/main.css', active: true } ,
			content: undefined
		}
	] 
} ) ;

/*
doormen.equals( o , {
	children: [
		{
			name: 'ClassicTag' ,
			attributes: { width: 1280, height: 1024, src: '/css/main.css', active: true } ,
			content: undefined
		}
	] 
} ) ;
*/

ClassicTag stringify.

var o = new TagContainer( [
	new ClassicTag( 'ClassicTag' , { width: 1280, height: 1024, src: '/css/main.css', active: true } ) 
] ) ;

//console.log( o ) ;

doormen.equals( stringify( o ) , '[ClassicTag width=1280 height=1024 src="/css/main.css" active]\n' ) ;

o = new TagContainer( [
	new ClassicTag( 'ClassicTag' , { width: 1280, height: 1024, src: '/css/main.css', active: true } ) ,
	new ClassicTag( 'ClassicTag' , { fullscreen: true } ) 
] ) ;

//console.log( o ) ;

doormen.equals(
	stringify( o ) ,
	'[ClassicTag width=1280 height=1024 src="/css/main.css" active]\n' +
	'[ClassicTag fullscreen]\n'
) ;

//console.log( parse( stringify( o ) ) ) ;

o = new TagContainer( [
	new ClassicTag( 'ClassicTag' , { width: 1280, height: 1024, src: '/css/main.css', active: true } , { hello: "world!" } ) ,
	new ClassicTag( 'ClassicTag' , { fullscreen: true } ) 
] ) ;

//console.log( o ) ;

doormen.equals(
	stringify( o ) ,
	'[ClassicTag width=1280 height=1024 src="/css/main.css" active]\n' +
	'\thello: world!\n' +
	'[ClassicTag fullscreen]\n'
) ;

ExpressionTag

ExpressionTag parse.

var ctx = { a: 4 , b: 1 } ;
var o = parse( '[ExpressionTag $a > 3]' , { tags: { ExpressionTag: ExpressionTag } } ) ;

//console.log( "parsed:" , o ) ;

// Doormen fails with constructors ATM
doormen.equals( JSON.parse( JSON.stringify( o ) ) , {
	children: [
		{
			name: 'ExpressionTag' ,
			attributes: {
				args: [
					{ refParts: [ 'a' ] } ,
					3
				]
			} ,
			content: undefined
		}
	] 
} ) ;

doormen.equals( typeof o.children[ 0 ].attributes.fnOperator === 'function' , true ) ;
doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , true ) ;
ctx.a = 2 ;
doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , false ) ;


o = parse( '[ExpressionTag 3 <= ( round 3.3 )]' , { tags: { ExpressionTag: ExpressionTag } } ) ;
doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , true ) ;

o = parse( '[ExpressionTag 4 < ( round 3.3 )]' , { tags: { ExpressionTag: ExpressionTag } } ) ;
doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , false ) ;

o = parse( '[ExpressionTag ( round 3.3 ) >= 3]' , { tags: { ExpressionTag: ExpressionTag } } ) ;
doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , true ) ;

o = parse( '[ExpressionTag ( round 3.3 ) >= 4]' , { tags: { ExpressionTag: ExpressionTag } } ) ;
doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , false ) ;

ExpressionTag parse with custom operators.

var ctx = { a: 4 , b: 1 } ;

var operators = {
	triple: function( args ) { return args[ 0 ] * 3 ; }
} ;

var o = parse( '[ExpressionTag triple $a]' , { tags: { ExpressionTag: ExpressionTag } , operators: operators } ) ;

doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , 12 ) ;
ctx.a = 2 ;
doormen.equals( o.children[0].attributes.getFinalValue( ctx ) , 6 ) ;

Loading a config

when trying to load an unexistant file, it should throw.

doormen.shouldThrow( function() { kungFig.load( __dirname + '/sample/unexistant.json' ) ; } ) ;

should load a simple JSON file without dependency.

doormen.equals(
	kungFig.load( __dirname + '/sample/simple.json' ) ,
	{ just: 'a', simple: { test: '!' } }
) ;

should load a simple KFG file without dependency.

//console.log( require( 'util' ).inspect( kungFig.parse( fs.readFileSync( __dirname + '/sample/kfg/katana.kfg' , 'utf8' ) ) , { depth: 10 } ) ) ;
//console.log( require( 'util' ).inspect( kungFig.load( __dirname + '/sample/kfg/katana.kfg' ) , { depth: 10 } ) ) ;

doormen.equals(
	kungFig.load( __dirname + '/sample/kfg/katana.kfg' ) , {
		class: 'katana',
		generic: 'saber',
		hands: 2,
		name: 'katana',
		description: 'This is a wonderful katana with a blueish blade!\nThis is a two-handed weapon.',
		durability: 24,
		melee: {
			'+toHit': -2,
			'+attack': 6,
			'*AT': 12,
			'+reach': 7,
			size: 4,
			'+power': 3,
			damages: [
				{ type: 'cutting', '+damage': 14 },
				{ type: 'fire', damage: 10 }
			]
		}
	}
) ;

should load a simple txt file.

doormen.equals(
	kungFig.load( __dirname + '/sample/txt/lorem.txt' ) ,
	"Lorem ipsum dolor."
) ;

should load a simple JSON file without dependency, containing an array.

//console.log( kungFig.load( __dirname + '/sample/simpleWithArrays.json' ) ) ;
doormen.equals(
	kungFig.load( __dirname + '/sample/simpleWithArrays.json' ) ,
	{ just: [ 'a' , 'simple' , [ 'test' , '!' ] ] }
) ;

should load a simple JSON file without dependency, which is an array.

//console.log( kungFig.load( __dirname + '/sample/simpleArray.json' ) ) ;
doormen.equals(
	kungFig.load( __dirname + '/sample/simpleArray.json' ) ,
	[ 'a' , 'simple' , [ 'test' , '!' ] ]
) ;

when loading a file with an unexistant dependency using the '@@', it should throw.

doormen.shouldThrow( function() { kungFig.load( __dirname + '/sample/withUnexistantInclude.json' ) ; } ) ;

when loading a file with an unexistant dependency using the '@', it should not throw.

doormen.equals(
	kungFig.load( __dirname + '/sample/withUnexistantOptionalInclude.json' ) ,
	{
		simple: "test",
		unexistant: {}
	}
) ;

when loading a file with a bad JSON content dependency using the '@', it should throw.

doormen.shouldThrow( function() { kungFig.load( __dirname + '/sample/withBadOptionalInclude.json' ) ; } ) ;
//kungFig.load( __dirname + '/sample/withBadOptionalInclude.json' ) ;

when loading a file, all Tree-Ops should be reduced.

doormen.equals(
	kungFig.load( __dirname + '/sample/withTreeOps.json' ) ,
	{
		simple: "test",
		int: 7
	}
) ;

when loading a file and explicitly turning the 'reduce' option off, Tree Operations should not be reduced.

doormen.equals(
	kungFig.load( __dirname + '/sample/withTreeOps.json' , { reduce: false } ) ,
	{
		simple: "test",
		int: 5,
		"+int": 2
	}
) ;

should load a JSON file with a txt dependency.

doormen.equals(
	kungFig.load( __dirname + '/sample/withTxtInclude.json' ) ,
	{
		"simple": "test",
		"firstInclude": "Lorem ipsum dolor.",
		"nested": {
			"secondInclude": "Lorem ipsum dolor."
		}
	}
) ;

should load a JSON file with many relative dependencies.

doormen.equals(
	kungFig.load( __dirname + '/sample/withIncludes.json' ) ,
	{
		simple: 'test',
		firstInclude: {
			one: 1,
			two: {
				three: 3,
				four: {
					hello: 'world!'
				},
				five: {
					just: 'a',
					simple: {
						test: '!'
					}
				}
			}
		},
		nested: {
			secondInclude: {
				hello: 'world!'
			}
		}
	}
) ;

should load a JSON file with a glob dependency.

doormen.equals(
	kungFig.load( __dirname + '/sample/withGlobIncludes.json' ) ,
	{
		simple: 'test',
		globInclude: [
			{
				one: 1 ,
				two: {
					five: {
						just: "a" ,
						simple: {
							test: "!" 
						}
					} ,
					four: {
						hello: "world!"
					} ,
					three: 3
				}
			} ,
			{
				hello: "world!" 
			}
		]
	}
) ;

should load a JSON file with a glob+merge dependency.

doormen.equals(
	kungFig.load( __dirname + '/sample/withGlobMerge.json' ) ,
	{
		a: "A" ,
		a2: 12 ,
		sub: {
			b: "two" ,
			b2: "two-two" ,
			subsub: {
				c: 3 ,
				c2: "C2" ,
				c3: "C3"
			}
		}
	}
) ;

should load a JSON file with a glob dependency that resolve to no files.

doormen.equals(
	kungFig.load( __dirname + '/sample/withUnexistantGlobInclude.json' ) ,
	{
		simple: 'test',
		globInclude: []
	}
) ;

should load flawlessly a config with a circular include to itself.

// Build the circular config here
var shouldBe = { "a": "A" } ;
shouldBe.b = shouldBe ;

doormen.equals( kungFig.load( __dirname + '/sample/circular.json' ) , shouldBe ) ;

should RE-load flawlessly a config with a circular include to itself.

// Build the circular config here
var shouldBe = { "a": "A" } ;
shouldBe.b = shouldBe ;

doormen.equals( kungFig.load( __dirname + '/sample/circular.json' ) , shouldBe ) ;

should load flawlessly a config with many circular includes.

// Build the circular config here
var shouldBe = { "hello": "world!" } ;

var a = { "some": "data" } ;
var b = { "more": "data" } ;
a.toBe = b ;
b.toA = a ;

shouldBe.circularOne = a ;
shouldBe.circularTwo = b ;

doormen.equals( kungFig.load( __dirname + '/sample/withCircularIncludes.json' ) , shouldBe ) ;

should RE-load flawlessly a config with many circular includes.

// Build the circular config here
var shouldBe = { "hello": "world!" } ;

var a = { "some": "data" } ;
var b = { "more": "data" } ;
a.toBe = b ;
b.toA = a ;

shouldBe.circularOne = a ;
shouldBe.circularTwo = b ;

doormen.equals( kungFig.load( __dirname + '/sample/withCircularIncludes.json' ) , shouldBe ) ;

should load flawlessly a config with a reference to itself.

doormen.equals(
	kungFig.load( __dirname + '/sample/selfReference.json' ) ,
	{
		"a": "A",
		"sub": {
			"key": "value",
			"refA": "A"
		},
		"refB": {
			"key": "value",
			"refA": "A"
		},
		"refC": "value"
	}
) ;

should load a JSON file with many relative dependencies and sub-references.

doormen.equals(
	kungFig.load( __dirname + '/sample/withIncludesRef.json' ) ,
	{
		simple: 'test',
		firstInclude: {
			three: 3,
			four: {
				hello: 'world!'
			},
			five: {
				just: 'a',
				simple: {
					test: '!'
				}
			}
		},
		nested: {
			secondInclude: 'world!',
			thirdInclude: 3
		}
	}
) ;

should load flawlessly a config with a circular reference to itself.

// Build the circular config here
var shouldBe = {
	"a": "A",
	"sub": {
		"key": "value"
	}
} ;

shouldBe.sub.ref = shouldBe ;

doormen.equals( kungFig.load( __dirname + '/sample/selfCircularReference.json' ) , shouldBe ) ;

Saving a config

should stringify a simple config.

var conf = {
	a: "Haha!",
	b: "Bee!",
	sub: {
		c: "See!"
	}
} ;

//console.log( kungFig.saveKfg( conf ).replace( /\n/g , () => '\\n' ).replace( /\t/g , () => '\\t' ) ) ;
doormen.equals( kungFig.saveJson( conf ) , '{\n  "a": "Haha!",\n  "b": "Bee!",\n  "sub": {\n    "c": "See!"\n  }\n}' ) ;
doormen.equals( kungFig.saveKfg( conf ) , 'a: Haha!\nb: Bee!\nsub:\n\tc: See!\n' ) ;

should stringify a config that have circular references.

var conf = {
	a: "Haha!",
	b: "Bee!",
	sub: {
		c: "See!"
	}
} ;

conf.sub.circular = conf ;

//console.log( kungFig.saveJson( conf ).replace( /\n/g , () => '\\n' ).replace( /\t/g , () => '\\t' ) ) ;
//console.log( kungFig.saveKfg( conf ).replace( /\n/g , () => '\\n' ).replace( /\t/g , () => '\\t' ) ) ;
doormen.equals( kungFig.saveJson( conf ) , '{\n  "a": "Haha!",\n  "b": "Bee!",\n  "sub": {\n    "c": "See!",\n    "@@circular": "#"\n  }\n}' ) ;
doormen.equals( kungFig.saveKfg( conf ) , 'a: Haha!\nb: Bee!\nsub:\n\tc: See!\n\tcircular: @@#\n' ) ;


var conf = {
	a: "Haha!",
	b: "Bee!",
	sub: {
		c: "See!"
	}
} ;

conf.sub.circular = conf.sub ;

//console.log( kungFig.saveKfg( conf ).replace( /\n/g , () => '\\n' ).replace( /\t/g , () => '\\t' ) ) ;
doormen.equals( kungFig.saveJson( conf ) , '{\n  "a": "Haha!",\n  "b": "Bee!",\n  "sub": {\n    "c": "See!",\n    "@@circular": "#sub"\n  }\n}' ) ;
doormen.equals( kungFig.saveKfg( conf ) , 'a: Haha!\nb: Bee!\nsub:\n\tc: See!\n\tcircular: @@#sub\n' ) ;


var conf = {
	a: "Haha!",
	b: "Bee!",
	sub: {
		sub: {
			c: "See!"
		}
	}
} ;

conf.sub.sub.circular = conf.sub.sub ;

//console.log( kungFig.saveKfg( conf ).replace( /\n/g , () => '\\n' ).replace( /\t/g , () => '\\t' ) ) ;
doormen.equals(
	kungFig.saveJson( conf ) , 
	'{\n  "a": "Haha!",\n  "b": "Bee!",\n  "sub": {\n    "sub": {\n      "c": "See!",\n      "@@circular": "#sub.sub"\n    }\n  }\n}'
) ;
doormen.equals( kungFig.saveKfg( conf ) , 'a: Haha!\nb: Bee!\nsub:\n\tsub:\n\t\tc: See!\n\t\tcircular: @@#sub.sub\n' ) ;

should load and save flawlessly a config with many circular includes.

var str ;

str = kungFig.saveJson( kungFig.load( __dirname + '/sample/withCircularIncludes.json' ) ) ;
//console.log( str ) ;
doormen.equals( str , '{\n  "hello": "world!",\n  "circularOne": {\n    "some": "data",\n    "@@toBe": "#circularTwo"\n  },\n  "circularTwo": {\n    "more": "data",\n    "@@toA": "#circularOne"\n  }\n}' ) ;

str = kungFig.saveKfg( kungFig.load( __dirname + '/sample/withCircularIncludes.json' ) ) ;
//console.log( str ) ;
//console.log( str.replace( /\n/g , () => '\\n' ).replace( /\t/g , () => '\\t' ) ) ;
doormen.equals( str , "hello: world!\ncircularOne:\n\tsome: data\n\ttoBe: @@#circularTwo\ncircularTwo:\n\tmore: data\n\ttoA: @@#circularOne\n" ) ;

should load and save to disk flawlessly a config with many circular includes.

var str ;

kungFig.saveJson( kungFig.load( __dirname + '/sample/withCircularIncludes.json' ) , __dirname + '/output.json' ) ;
str = fs.readFileSync( __dirname + '/output.json' ).toString() ;
//console.log( str ) ;
doormen.equals( str , '{\n  "hello": "world!",\n  "circularOne": {\n    "some": "data",\n    "@@toBe": "#circularTwo"\n  },\n  "circularTwo": {\n    "more": "data",\n    "@@toA": "#circularOne"\n  }\n}' ) ;
fs.unlinkSync( __dirname + '/output.json' ) ;

kungFig.saveKfg( kungFig.load( __dirname + '/sample/withCircularIncludes.json' ) , __dirname + '/output.kfg' ) ;
str = fs.readFileSync( __dirname + '/output.kfg' ).toString() ;
//console.log( str ) ;
doormen.equals( str , "hello: world!\ncircularOne:\n\tsome: data\n\ttoBe: @@#circularTwo\ncircularTwo:\n\tmore: data\n\ttoA: @@#circularOne\n" ) ;
fs.unlinkSync( __dirname + '/output.kfg' ) ;

Load meta

should only load meta.

var meta ;

meta = kungFig.loadMeta( __dirname + '/sample/kfg/meta-hook.kfg' ) ;
doormen.equals( meta.getFirstTag( 'meta' ).content , "master" ) ;

meta = kungFig.loadMeta( __dirname + '/sample/kfg/katana.kfg' ) ;
doormen.equals( meta , null ) ;

JS modules

should load a JS module.

doormen.equals(
	kungFig.load( __dirname + '/sample/js/one.js' ) ,
	require(  __dirname + '/sample/js/one.js' )
) ;

should load a JS module exporting a function.

doormen.equals(
	typeof kungFig.load( __dirname + '/sample/function.js' ) ,
	'function'
) ;

doormen.equals(
	kungFig.load( __dirname + '/sample/function.js' )() ,
	'world'
) ;

should load a JSON file with many relative dependencies and sub-references to a JS module.

doormen.equals(
	kungFig.load( __dirname + '/sample/withJsIncludesRef.json' ) ,
	{
		simple: 'test',
		firstInclude: require(  __dirname + '/sample/js/one.js' ) ,
		nested: {
			secondInclude: require(  __dirname + '/sample/js/one.js' ).helloFunc ,
			thirdInclude: require(  __dirname + '/sample/js/one.js' ).awesomeFunc
		}
	}
) ;

Array references

should load flawlessly a config which is an array with simple includes.

var o = kungFig.load( __dirname + '/sample/simpleArrayRef.json' ) ;
//console.log( JSON.stringify( o , null , '  ' ) ) ;
doormen.equals( o , {
	array: [
		{ just: "a", simple: { test: "!" } },
		[ 1,2,3 ]
	],
	refArray: [ 1,2,3 ]
} ) ;
doormen.equals( o.array[ 1 ] === o.refArray , true ) ;

should load flawlessly a config which is an array with many circular includes.

var o = kungFig.load( __dirname + '/sample/withCircularIncludesArray.json' ) ;

// Build the circular config here
var shouldBe = [ "world!" ] ;

var a = [ "data" ] ;
var b = [ "data" ] ;
a[ 1 ] = b ;
b[ 1 ] = a ;

shouldBe[ 1 ] = a ;
shouldBe[ 2 ] = b ;

doormen.equals( o , shouldBe ) ;

should load flawlessly a config which is an array with a reference to itself.

doormen.equals(
	kungFig.load( __dirname + '/sample/selfReferenceArray.json' ) ,
	[
		"A",
		[
			"value",
			"A"
		],
		[
			"value",
			"A"
		],
		"value"
	]
) ;

should load a JSON file which is an array with many relative dependencies and sub-references.

//console.log( kungFig.load( __dirname + '/sample/withIncludesRefArray.json' ) ) ;
doormen.equals(
	kungFig.load( __dirname + '/sample/withIncludesRefArray.json' ) ,
	[
		'test',
		[
			3,
			[ 'hello', 'world!' ],
			{
				just: 'a',
				simple: {
					test: '!'
				}
			}
		],
		[
			'world!',
			'hello',
			3
		]
	]
) ;

should load flawlessly a config which is an array with a circular reference to itself.

//console.log( kungFig.load( __dirname + '/sample/selfCircularReferenceArray.json' ) ) ;

// Build the circular config here
var shouldBe = [ "A" , [ "value" ] ] ;
shouldBe[ 1 ][ 1 ] = shouldBe ;

doormen.equals( kungFig.load( __dirname + '/sample/selfCircularReferenceArray.json' ) , shouldBe ) ;

should stringify a config of arrays.

var str ;

str = kungFig.saveJson( kungFig.load( __dirname + '/sample/withIncludesRefArray.json' ) ) ;
//console.log( str ) ;
doormen.equals( str , '[\n  "test",\n  [\n    3,\n    [\n      "hello",\n      "world!"\n    ],\n    {\n      "just": "a",\n      "simple": {\n        "test": "!"\n      }\n    }\n  ],\n  [\n    "world!",\n    "hello",\n    3\n  ]\n]' ) ;

should load and save flawlessly a config which is an array with many circular includes.

var o , str ;

o = kungFig.load( __dirname + '/sample/withCircularIncludesArray.json' ) ;
//console.log( o ) ;
str = kungFig.saveJson( o ) ;
//console.log( str ) ;
//console.log( str.replace( /\n/g , () => '\\n' ) ) ;
doormen.equals( str , '[\n  "world!",\n  [\n    "data",\n    {\n      "@@": "#[2]"\n    }\n  ],\n  [\n    "data",\n    {\n      "@@": "#[1]"\n    }\n  ]\n]' ) ;

Template

Template#getFinalValue().

var ctx = { a: 42 } ;
ctx.b = Ref.create( '$a' ) ;
ctx.c = Template.create( "Hello, I'm ${a}." ) ;
ctx.d = Template.create( "Hello, I'm ${b}." ) ;
doormen.equals( ctx.c.getFinalValue( ctx ) , "Hello, I'm 42." ) ;
doormen.equals( ctx.d.getFinalValue( ctx ) , "Hello, I'm 42." ) ;

Dynamic.getDeepFinalValue()

Historical non-cloning bug.

var ref1 , tpl1 , tpl2 ;

var ctx = { a: 42 } ;

ctx.b = ref1 = Ref.create( '$a' ) ;
ctx.c = tpl1 = Template.create( "Hello, I'm ${a}." ) ;
ctx.d = tpl2 = Template.create( "Hello, I'm ${b}." ) ;

doormen.equals( Dynamic.getDeepFinalValue( ctx , ctx ) , {
	a: 42 ,
	b: 42 ,
	c: "Hello, I'm 42." ,
	d: "Hello, I'm 42."
} ) ;

doormen.equals( ctx.b === ref1 , true ) ;
doormen.equals( ctx.c === tpl1 , true ) ;
doormen.equals( ctx.d === tpl2 , true ) ;

Ref

Get

parse and get a simple ref.

var ref_ ;

var ctx = {
	a: 1 ,
	b: 2 ,
	sub: {
		c: 3 ,
		sub: {
			d: 4
		}
	}
} ;

ref_ = Ref.parse( '$x' ) ;
doormen.equals( ref_.get( ctx ) , undefined ) ;

ref_ = Ref.parse( '$x.y.z' ) ;
doormen.equals( ref_.get( ctx ) , undefined ) ;

ref_ = Ref.parse( '$a' ) ;
doormen.equals( ref_.get( ctx ) , 1 ) ;

ref_ = Ref.parse( '$b' ) ;
doormen.equals( ref_.get( ctx ) , 2 ) ;

ref_ = Ref.parse( '$sub' ) ;
doormen.equals( ref_.get( ctx ) , ctx.sub ) ;

ref_ = Ref.parse( '$sub.c' ) ;
doormen.equals( ref_.get( ctx ) , 3 ) ;

ref_ = Ref.parse( '$sub.sub' ) ;
doormen.equals( ref_.get( ctx ) , ctx.sub.sub ) ;

ref_ = Ref.parse( '$sub.sub.d' ) ;
doormen.equals( ref_.get( ctx ) , 4 ) ;

ref_ = Ref.parse( '$e' ) ;
doormen.equals( ref_.get( ctx ) , undefined ) ;

ref_ = Ref.parse( '$e.f.g' ) ;
doormen.equals( ref_.get( ctx ) , undefined ) ;

parse and get a ref on a context having arrays.

var ref_ ;

var ctx = {
	a: 1 ,
	array: [ 'one' , 'two' , [ 'three' , 'four' , [ 'five' , 'six' ] ] , { array: [ 'seven' , 'eight' ] } ]
} ;

ref_ = Ref.parse( '$array' ) ;
doormen.equals( ref_.get( ctx ) , ctx.array ) ;

ref_ = Ref.parse( '$array[0]' ) ;
doormen.equals( ref_.get( ctx ) , 'one' ) ;

ref_ = Ref.parse( '$array[10]' ) ;
doormen.equals( ref_.get( ctx ) , undefined ) ;

ref_ = Ref.parse( '$array[10][10]' ) ;
doormen.equals( ref_.get( ctx ) , undefined ) ;

ref_ = Ref.parse( '$array[2][1]' ) ;
doormen.equals( ref_.get( ctx ) , 'four' ) ;

ref_ = Ref.parse( '$array[3]' ) ;
doormen.equals( ref_.get( ctx ) , ctx.array[3] ) ;

ref_ = Ref.parse( '$array[3].array' ) ;
doormen.equals( ref_.get( ctx ) , ctx.array[3].array ) ;

ref_ = Ref.parse( '$array[3].array[1]' ) ;
doormen.equals( ref_.get( ctx ) , 'eight' ) ;

ref_ = Ref.parse( '$[1]' ) ;
doormen.equals( ref_.get( ctx.array ) , 'two' ) ;

ref_ = Ref.parse( '$[2][1]' ) ;
doormen.equals( ref_.get( ctx.array ) , 'four' ) ;

ref_ = Ref.parse( '$[3].array[1]' ) ;
doormen.equals( ref_.get( ctx.array ) , 'eight' ) ;

parse and get a ref with quoted keys.

var ref_ ;

var ctx = {
	key: 'value' ,
	"a key with spaces": {
		"another one": 'sure'
	}
} ;

ref_ = Ref.parse( '$key' ) ;
doormen.equals( ref_.get( ctx ) , 'value' ) ;

ref_ = Ref.parse( '$["key"]' ) ;
doormen.equals( ref_.get( ctx ) , 'value' ) ;

ref_ = Ref.parse( '$["a key with spaces"]' ) ;
doormen.equals( ref_.get( ctx ) , ctx["a key with spaces"] ) ;

ref_ = Ref.parse( '$["a key with spaces"]["another one"]' ) ;
doormen.equals( ref_.get( ctx ) , 'sure' ) ;

parse and get a complex ref (ref having refs).

var ref_ ;

var ctx = {
	a: 1 ,
	b: 3 ,
	c: 0 ,
	k1: 'someKey' ,
	k2: 'anotherKey' ,
	array: [ 'one' , 'two' , [ 'three' , 'four' , [ 'five' , 'six' ] ] ] ,
	object: {
		someKey: 'value' ,
		anotherKey: 'another value'
	}
} ;

ref_ = Ref.parse( '$array[$a]' ) ;
doormen.equals( ref_.get( ctx ) , 'two' ) ;

ref_ = Ref.parse( '$array[$b]' ) ;
doormen.equals( ref_.get( ctx ) , ctx.array[3] ) ;

ref_ = Ref.parse( '$array[$c]' ) ;
doormen.equals( ref_.get( ctx ) , 'one' ) ;

ref_ = Ref.parse( '$object[$k1]' ) ;
doormen.equals( ref_.get( ctx ) , 'value' ) ;

ref_ = Ref.parse( '$object[$k2]' ) ;
doormen.equals( ref_.get( ctx ) , 'another value' ) ;

function in context.

var ref_ ;

var ctx = {
	fn: function myFunc() {}
} ;

ctx.fn.prop = 'val' ;

ref_ = Ref.parse( '$fn' ) ;
doormen.equals( ref_.get( ctx ) , ctx.fn ) ;

ref_ = Ref.parse( '$fn.name' ) ;
doormen.equals( ref_.get( ctx ) , 'myFunc' ) ;

ref_ = Ref.parse( '$fn.prop' ) ;
doormen.equals( ref_.get( ctx ) , 'val' ) ;

Set

set a simple ref.

var ref_ ;

var ctx = {
	a: 1 ,
	b: 2 ,
	sub: {
		c: 3 ,
		sub: {
			d: 4
		}
	}
} ;

ref_ = Ref.parse( '$a' ) ;
ref_.set( ctx , 7 ) ;
doormen.equals( ctx.a , 7 ) ;

ref_ = Ref.parse( '$sub.c' ) ;
ref_.set( ctx , 22 ) ;
doormen.equals( ctx.sub.c , 22 ) ;

ref_ = Ref.parse( '$sub.sub' ) ;
ref_.set( ctx , 'hello' ) ;
doormen.equals( ctx.sub.sub , 'hello' ) ;

doormen.equals( ctx , {
	a: 7 ,
	b: 2 ,
	sub: {
		c: 22 ,
		sub: 'hello'
	}
} ) ;

set a ref on a context having arrays.

var ref_ ;

var ctx = {
	a: 1 ,
	array: [ 'one' , 'two' , [ 'three' , 'four' , [ 'five' , 'six' ] ] , { array: [ 'seven' , 'eight' ] } ]
} ;

ref_ = Ref.parse( '$array[0]' ) ;
ref_.set( ctx , 'ONE' ) ;
doormen.equals( ctx.array[0] , 'ONE' ) ;

ref_ = Ref.parse( '$array[3][1]' ) ;
ref_.set( ctx , 4 ) ;
doormen.equals( ctx.array[3][1] , 4 ) ;

set a complex ref (ref having refs).

var ref_ ;

var ctx = {
	a: 1 ,
	b: 3 ,
	c: 0 ,
	k1: 'someKey' ,
	k2: 'anotherKey' ,
	array: [ 'one' , 'two' , [ 'three' , 'four' , [ 'five' , 'six' ] ] ] ,
	object: {
		someKey: 'value' ,
		anotherKey: 'another value'
	}
} ;

ref_ = Ref.parse( '$array[$a]' ) ;
ref_.set( ctx , 2 ) ;
doormen.equals( ctx.array[1] , 2 ) ;

ref_ = Ref.parse( '$object[$k1]' ) ;
ref_.set( ctx , 'my value' ) ;
doormen.equals( ctx.object.someKey , 'my value' ) ;

ref_ = Ref.parse( '$object[$k2]' ) ;
ref_.set( ctx , 'my other value' ) ;
doormen.equals( ctx.object.anotherKey , 'my other value' ) ;

set and the auto-creation feature.

var ref_ ;

var ctx = {} ;

ref_ = Ref.parse( '$a.b' ) ;
ref_.set( ctx , 7 ) ;

doormen.equals( ctx , {
	a: { b: 7 }
} ) ;

ref_ = Ref.parse( '$c.d.e.f' ) ;
ref_.set( ctx , 'Gee!' ) ;

doormen.equals( ctx , {
	a: { b: 7 } ,
	c: { d: { e: { f: 'Gee!' } } }
} ) ;

ref_ = Ref.parse( '$arr[1]' ) ;
ref_.set( ctx , 'one' ) ;

doormen.equals( ctx , {
	a: { b: 7 } ,
	c: { d: { e: { f: 'Gee!' } } } ,
	arr: [ undefined , 'one' ]
} ) ;

ref_ = Ref.parse( '$arr2[3][2][1]' ) ;
ref_.set( ctx , 'nested' ) ;

doormen.equals( ctx , {
	a: { b: 7 } ,
	c: { d: { e: { f: 'Gee!' } } } ,
	arr: [ undefined , 'one' ] ,
	arr2: [ undefined , undefined , undefined , [ undefined , undefined , [ undefined , 'nested' ] ] ]
} ) ;

function in context.

var ref_ ;

var ctx = {
	fn: function myFunc() {}
} ;

ctx.fn.prop = 'val' ;

ref_ = Ref.parse( '$fn.prop' ) ;
ref_.set( ctx , true ) ;
doormen.equals( ctx.fn.prop , true ) ;

ref_ = Ref.parse( '$fn.prop2' ) ;
ref_.set( ctx , 'plop' ) ;
doormen.equals( ctx.fn.prop2 , 'plop' ) ;

Calling a function

parse and call a function pointed by a ref.

var ref_ ;

var ctx = {
	a: 1 ,
	b: 2 ,
	fn: function( a , b , c ) {
		return a + b + c + this.a + this.b + this.sub.c ;
	} ,
	sub: {
		c: 3 ,
		fn: function( a ) {
			return a + this.c + this.sub.d ;
		} ,
		sub: {
			d: 4 ,
			fn: function( a ) {
				return a + this.d ;
			}
		}
	}
} ;

ref_ = Ref.parse( '$fn' ) ;
doormen.equals( ref_.callFn( ctx , 4 , 5 , 6 ) , 21 ) ;

ref_ = Ref.parse( '$sub.fn' ) ;
doormen.equals( ref_.callFn( ctx , 10 ) , 17 ) ;

ref_ = Ref.parse( '$sub.sub.fn' ) ;
doormen.equals( ref_.callFn( ctx , -5 ) , -1 ) ;

Misc

Ref#getFinalValue().

var ctx = { a: 42 } ;
ctx.b = Ref.create( '$a' ) ;
ctx.c = Ref.create( '$b' ) ;
ctx.d = Ref.create( '$c' ) ;
doormen.equals( ctx.b.getFinalValue( ctx ) , 42 ) ;
doormen.equals( ctx.c.getFinalValue( ctx ) , 42 ) ;
doormen.equals( ctx.d.getFinalValue( ctx ) , 42 ) ;

Ref#getDeepFinalValue().

var ctx = { a: 42 , container: {} } ;
ctx.container.b = Ref.create( '$a' ) ;
ctx.container.c = Ref.create( '$container.b' ) ;
ctx.container.d = Ref.create( '$container.c' ) ;
ctx.refContainer = Ref.create( '$container' ) ;
doormen.equals( ctx.refContainer.getDeepFinalValue( ctx ) , { b:42 , c:42 , d:42 } ) ;

Ref#toString().

var ctx = { a: 42 } ;
ctx.b = Ref.create( '$a' ) ;
ctx.c = Ref.create( '$b' ) ;
ctx.d = Ref.create( '$c' ) ;
doormen.equals( ctx.b.toString( ctx ) , "42" ) ;
doormen.equals( ctx.c.toString( ctx ) , "42" ) ;
doormen.equals( ctx.d.toString( ctx ) , "42" ) ;

Parser edge cases

Should stop parsing at first non-enclosed space.

var ref_ = Ref.parse( '$x y z' ) ;
doormen.equals( ref_.refParts , [ 'x' ] ) ;

Operator behaviours

simple stack and reduce on a single object.

var creature = {
	hp: 8 ,
	attack: 5 ,
	defense: 3 ,
	move: 1 ,
	"+defense": 3
} ;

doormen.equals(
	kungFig.stack( creature ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 3 ,
		move: 1 ,
		"+defense": 3
	}
) ;

doormen.equals(
	kungFig.reduce( creature ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 6 ,
		move: 1
	}
) ;

simple stack and reduce on two and three objects.

var creature = {
	hp: 8 ,
	attack: 5 ,
	defense: 3 ,
	move: 1 ,
	"+defense": 3
} ;

var amulet = {
	"+defense": 1 ,
	"+hp": 1
} ;

var ring = {
	"+defense": 1 ,
	"#+hp": [1,1]
} ;

doormen.equals(
	kungFig.stack( creature , amulet ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 3 ,
		move: 1 ,
		"#+defense": [3,1] ,
		"+hp": 1
	}
) ;

doormen.equals(
	kungFig.stack( creature , amulet , ring ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 3 ,
		move: 1 ,
		"#+defense": [3,1,1] ,
		"#+hp": [1,1,1]
	}
) ;

doormen.equals(
	kungFig.reduce( {} , creature , amulet ) ,
	{
		hp: 9 ,
		attack: 5 ,
		defense: 7 ,
		move: 1
	}
) ;

doormen.equals(
	kungFig.reduce( {} , creature , amulet , ring ) ,
	{
		hp: 11 ,
		attack: 5 ,
		defense: 8 ,
		move: 1
	}
) ;

check stack behaviour bug, when a 'foreach' and 'non-foreach' key are mixed.

var creature = {
	hp: 8 ,
	attack: 5 ,
	defense: 3 ,
	move: 1 ,
	"+defense": 3
} ;

var warrior = {
	hp: 10 ,
	"#+defense": [2] ,
	evasion: 7
} ;

doormen.equals(
	kungFig.stack( creature , warrior ) ,
	{
		attack: 5,
		defense: 3,
		move: 1,
		'#hp': [ 8, 10 ],
		'#+defense': [ 3, 2 ],
		evasion: 7
	}
) ;

doormen.equals(
	kungFig.stack( warrior , creature ) ,
	{
		attack: 5,
		defense: 3,
		move: 1,
		'#hp': [ 10 , 8 ],
		'#+defense': [ 2, 3 ],
		evasion: 7
	}
) ;

mixing + and * for the same base key should preserve operation order (first *, then +).

var creature = {
	hp: 8 ,
	attack: 5 ,
	defense: 3 ,
	move: 1
} ;

var shield = {
	"+defense": 3 ,
} ;

var enchantedArmor = {
	"*defense": 2 ,
	"+defense": 1 ,
	"+magic": 1
} ;

var helmet = {
	"+defense": 1 ,
} ;

doormen.equals(
	kungFig.stack( creature , shield , enchantedArmor ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 3 ,
		move: 1 ,
		"#+defense": [3,1] ,
		"*defense": 2 ,
		"+magic": 1
	}
) ;

doormen.equals(
	kungFig.stack( creature , shield , enchantedArmor , helmet ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 3 ,
		move: 1 ,
		"#+defense": [3,1,1] ,
		"*defense": 2 ,
		"+magic": 1
	}
) ;

doormen.equals(
	kungFig.reduce( creature , shield , enchantedArmor , helmet ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 11 ,
		move: 1 ,
		"+magic": 1
	}
) ;

the combining after operator *>.

var tree = {
	subtree: {
		a: 3,
		b: 5,
		c: 11
	}
} ;

var mods = {
	"*>subtree": {
		"+a": 1,
		"+b": 3,
		c: 12
	}
} ;

//console.log( kungFig.stack( tree , mods ) ) ;
doormen.equals(
	kungFig.stack( tree , mods ) ,
	{
		//*
		subtree: {
			a: 3,
			b: 5,
			c: 11
		},
		"*>subtree": {
			"+a": 1,
			"+b": 3,
			c: 12
		}
		//*/
		
		/*
		subtree: {
			a: 3,
			b: 5,
			c: 12,
			"+a": 1,
			"+b": 3
		},
		//*/
	}
) ;

doormen.equals(
	kungFig.reduce( tree , mods ) ,
	{
		subtree: {
			a: 4,
			b: 8,
			c: 12
		}
	}
) ;

*> and *>> priorities.

var tree = {
	subtree: {
		a: 3,
		b: 5
	}
} ;

var mods1 = {
	"*>>subtree": {
		a: 1
	}
} ;

var mods2 = {
	"*>subtree": {
		a: 2
	}
} ;

doormen.equals(
	kungFig.reduce( tree , mods1 , mods2 ) ,
	{ subtree: { a: 1, b: 5 } }
) ;

doormen.equals(
	kungFig.reduce( tree , mods2 , mods1 ) ,
	{ subtree: { a: 1, b: 5 } }
) ;

tree = {
	subtree: {
		a: 3,
		b: 5
	} ,
	"*>>subtree": {
		a: 1
	} ,
	"*>subtree": {
		a: 2
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{ subtree: { a: 1, b: 5 } }
) ;

tree = {
	subtree: {
		a: 3,
		b: 5
	} ,
	"*>subtree": {
		a: 2
	} ,
	"*>>subtree": {
		a: 1
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{ subtree: { a: 1, b: 5 } }
) ;

<* and <<* priorities.

var tree = {
	subtree: {
		b: 5
	}
} ;

var mods1 = {
	"<<*subtree": {
		a: 1
	}
} ;

var mods2 = {
	"<*subtree": {
		a: 2
	}
} ;

doormen.equals(
	kungFig.reduce( tree , mods1 , mods2 ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

doormen.equals(
	kungFig.reduce( tree , mods2 , mods1 ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

tree = {
	subtree: {
		b: 5
	} ,
	"<<*subtree": {
		a: 1
	} ,
	"<*subtree": {
		a: 2
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

tree = {
	subtree: {
		b: 5
	} ,
	"<*subtree": {
		a: 2
	} ,
	"<<*subtree": {
		a: 1
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

the combining before operator <*.

var tree = {
	subtree: {
		a: 3,
		b: 5,
		c: 11
	}
} ;

var mods = {
	"<*subtree": {
		"+a": 1,
		"+b": 3,
		c: 12,
		d: 7
	}
} ;

//console.log( kungFig.stack( tree , mods ) ) ;
doormen.equals(
	kungFig.stack( tree , mods ) ,
	{
		//*
		subtree: {
			a: 3,
			b: 5,
			c: 11
		},
		"<*subtree": {
			"+a": 1,
			"+b": 3,
			c: 12,
			d: 7
		}
		//*/
		
		/*
		subtree: {
			a: 3,
			b: 5,
			c: 11,
			d: 7,
			"+a": 1,
			"+b": 3
		}
		//*/
	}
) ;

doormen.equals(
	kungFig.reduce( tree , mods ) ,
	{
		subtree: {
			a: 4,
			b: 8,
			c: 11,
			d: 7
		}
	}
) ;

the combining after operator *> with no baseKey should combine in the root element.

var tree = {
	a: 3,
	b: 5,
	c: 11
} ;

var mods = {
	"*>": {
		"+a": 1,
		"+b": 3,
		c: 12
	}
} ;

//console.log( kungFig.stack( tree , mods ) ) ;
doormen.equals(
	kungFig.stack( tree , mods ) ,
	//*
	{
		a: 3,
		b: 5,
		c: 11,
		"*>": {
			"+a": 1,
			"+b": 3,
			c: 12
		}
	}
	//*/
	
	/*
	{
		a: 3,
		b: 5,
		c: 12,
		"+a": 1,
		"+b": 3
	}
	//*/
) ;

doormen.equals(
	kungFig.reduce( tree , mods ) ,
	{
		a: 4,
		b: 8,
		c: 12
	}
) ;

//console.log( "\n---------\n" ) ;

tree = {
	a: 3,
	b: 5,
	c: 11,
	"*>": {
		"+a": 1,
		"+b": 3,
		c: 12
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{
		a: 4,
		b: 8,
		c: 12
	}
) ;

the combining before operator <* with no baseKey should combine in the root element.

var tree = {
	a: 3,
	b: 5,
	c: 11
} ;

var mods = {
	"<*": {
		"+a": 1,
		"+b": 3,
		c: 12,
		d: 7
	}
} ;

//console.log( kungFig.stack( tree , mods ) ) ;
doormen.equals(
	kungFig.stack( tree , mods ) ,
	//*
	{
		a: 3,
		b: 5,
		c: 11,
		"<*": {
			"+a": 1,
			"+b": 3,
			c: 12,
			d: 7
		}
	}
	//*/
	
	/*
	{
		a: 3,
		b: 5,
		c: 11,
		d: 7,
		"+a": 1,
		"+b": 3
	}
	//*/
) ;

doormen.equals(
	kungFig.reduce( tree , mods ) ,
	{
		a: 4,
		b: 8,
		c: 11,
		d: 7
	}
) ;

tree = {
	a: 3,
	b: 5,
	c: 11,
	"<*": {
		"+a": 1,
		"+b": 3,
		c: 12,
		d: 7
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{
		a: 4,
		b: 8,
		c: 11,
		d: 7
	}
) ;

root and non-root operator priorities.

var tree = {
	subtree: {
		a: 3,
		b: 5
	}
} ;

var mods1 = {
	"*>subtree": {
		a: 1
	}
} ;

var mods2 = {
	"*>": {
		subtree: {
			a: 2
		}
	}
} ;

doormen.equals(
	kungFig.reduce( tree , mods1 , mods2 ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

doormen.equals(
	kungFig.reduce( tree , mods2 , mods1 ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

tree = {
	subtree: {
		a: 3,
		b: 5
	} ,
	"*>subtree": {
		a: 1
	} ,
	"*>": {
		subtree: {
			a: 2
		}
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

tree = {
	subtree: {
		a: 3,
		b: 5
	} ,
	"*>": {
		subtree: {
			a: 2
		}
	} ,
	"*>subtree": {
		a: 1
	}
} ;

doormen.equals(
	kungFig.reduce( tree ) ,
	{ subtree: { a: 2, b: 5 } }
) ;

the concat after (append) operator +>.

var tree = {
	array: [ 3,5,11 ]
} ;

var mods = {
	"+>array": [ 2,7 ]
} ;

//console.log( kungFig.stack( tree , mods ) ) ;
doormen.equals(
	kungFig.stack( tree , mods ) ,
	{
		array: [ 3,5,11 ],
		"+>array": [ 2,7 ]
	}
) ;

doormen.equals(
	kungFig.reduce( tree , mods ) ,
	{
		array: [ 3,5,11,2,7 ]
	}
) ;

the concat before (prepend) operator <+.

var tree = {
	array: [ 3,5,11 ]
} ;

var mods = {
	"<+array": [ 2,7 ]
} ;

//console.log( kungFig.stack( tree , mods ) ) ;
doormen.equals(
	kungFig.stack( tree , mods ) ,
	{
		array: [ 3,5,11 ],
		"<+array": [ 2,7 ]
	}
) ;

doormen.equals(
	kungFig.reduce( tree , mods ) ,
	{
		array: [ 2,7,3,5,11 ]
	}
) ;

arrays should not be combined recursively.

var o = { a: [ { b: 2, c: 3 }, { d: 5 } ] } ;
var o2 = { a: [ { b: 52 } ] } ;

doormen.equals(
	kungFig.reduce( {} , o , o2 ) ,
	{ a: [ { b: 52 } ] }
) ;

Complex, deeper test

simple foreach.

var creature = {
	hp: 8 ,
	attack: 5 ,
	defense: 3 ,
	move: 1 ,
	"#+defense": [3,4,5]
} ;

doormen.equals(
	kungFig.reduce( creature ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 15 ,
		move: 1
	}
) ;

combining foreach on nested objects.

var creature = {
	hp: 8 ,
	attack: 5 ,
	defense: 3 ,
	move: 1 ,
	attacks: {
		kick: {
			toHit: 10,
			damage: 15,
			elements: {
				impact: true
			}
		}
	} ,
	"#*>": [
		{
			hp: 10,
			evasion: 5,
			attacks: {
				kick: {
					toHit: 8,
					elements: {
						lightning: true,
						wind: true
					}
				}
			}
		} ,
		{
			hp: 9,
			attacks: {
				kick: {
					elements: {
						fire: true,
						wind: false
					}
				}
			}
		}
	]
} ;

doormen.equals(
	kungFig.reduce( creature ) ,
	{
		hp: 9 ,
		attack: 5 ,
		defense: 3 ,
		move: 1 ,
		evasion: 5 ,
		attacks: {
			kick: {
				toHit: 8,
				damage: 15,
				elements: {
					impact: true,
					lightning: true,
					fire: true,
					wind: false
				}
			}
		} ,
	}
) ;

To regular object

simple tree-ops object.

var creature = {
	hp: 8 ,
	attack: 5 ,
	"*attack": 1.2 ,
	defense: 3 ,
	move: 1 ,
	"+defense": 3
} ;

doormen.equals(
	kungFig.toObject( creature ) ,
	{
		hp: 8 ,
		attack: 5 ,
		defense: 3 ,
		move: 1 ,
	}
) ;

doormen.equals(
	kungFig.reduceToObject( creature ) ,
	{
		hp: 8 ,
		attack: 6 ,
		defense: 6 ,
		move: 1
	}
) ;

edge cases.

var o = {
	"()*.kfg": "*.gz" ,
	"()*/*.jpeg": "*/*.jpg"
} ;

doormen.equals(
	kungFig.toObject( o ) ,
	{
		"*.kfg": "*.gz" ,
		"*/*.jpeg": "*/*.jpg"
	}
) ;

doormen.equals(
	kungFig.reduceToObject( o ) ,
	{
		"*.kfg": "*.gz" ,
		"*/*.jpeg": "*/*.jpg"
	}
) ;

Operator extensions

simple operator extension.

var ext = kungFig.extendOperators( {
	pow: {
		priority: 100 ,
		reduce: function( existing , operands ) {
			var i , iMax = operands.length , operand = 1 ;
			
			for ( i = 0 ; i < iMax ; i ++ )
			{
				if ( ! isNaN( operands[ i ] ) )
				{
					operand *= + operands[ i ] ;
				}
			}
			
			if ( ! isNaN( existing ) )
			{
				existing = Math.pow( + existing , operand ) ;
				operands.length = 0 ;
				return existing ;
			}
			else
			{
				operands[ 0 ] = operand ;
				operands.length = 1 ;
				return existing ;
			}
		}
	}
} ) ;

var tree = {
	a: 3,
	b: 5,
	"+b": 2,
	"(pow)a": 2
} ;

//console.log( ext ) ;

doormen.equals(
	ext.reduce( tree ) ,
	{ a: 9, b: 7 }
) ;

tree = {
	a: 3,
	b: 5,
	"(pow)a": 2
} ;

var mods = {
	"(pow)a": 3
} ;

//console.log( ext.stack( tree , mods ) ) ;

doormen.equals(
	ext.stack( tree , mods ) ,
	{ a: 3, b: 5, "(#pow)a": [2,3] }
) ;

//console.log( ext.reduce( tree , mods ) ) ;

doormen.equals(
	ext.reduce( tree , mods ) ,
	{ a: 729, b: 5 }
) ;