export class YAMLParser { private static TAB : string = "\t" ; private static NEWLINE : string = "\n" ; private static TWO_SPACES : string = "  " ; private static countAtStart ( line , value ) { let count = 0 ; while ( true ) { if ( line . startsWith ( value ) ) { count ++ ; line = line . slice ( value . length ) ; } else { break ; } } return count ; } public constructor ( public yamlContents ) { } public parse ( ) { const lines = this . yamlContents . split ( YAMLParser . NEWLINE ) ; const context = new YAMLContext ( ) ; context . push ( new Value ( 0 , 0 , undefined , [ ] ) ) ; for ( const line of lines ) { if ( line . startsWith ( "#" ) ) { continue ; } context . lineNumber ++ ; this . parseLine ( line , context ) ; } return context . root ( ) . array ( ) ; } private parseLine ( line , context ) { const cleanLine = line . trim ( ) ; let tabs = 0 ; if ( cleanLine . length > 0 ) { YAMLParser . countAtStart ( line , YAMLParser . TAB ) ; if ( tabs === 0 ) { tabs = YAMLParser . countAtStart ( line , YAMLParser . TWO_SPACES ) ; } } if ( tabs - context . tabs > 1 ) { throw new Error ( "INVALID - Line " + context . lineNumber + ": previous line is " + context . tabs + ". This line is: " + tabs + ". Cannot increase indent by more than one tab" ) ; } context . popTo ( tabs ) ; if ( cleanLine . length === 0 ) { context . push ( new Value ( context . lineNumber , tabs , undefined , null ) ) ; } else if ( cleanLine . endsWith ( ":" ) ) { const name = cleanLine . substring ( 0 , cleanLine . length - 1 ) ; context . push ( new Value ( context . lineNumber , tabs , name ) ) ; } else if ( cleanLine . startsWith ( "- " ) ) { context . top ( ) . value ( [ ] ) ; const arrayValue = cleanLine . substring ( 2 ) . trim ( ) ; context . top ( ) . array ( ) . push ( new Value ( context . lineNumber , tabs , undefined , arrayValue ) ) ; } else { const name = cleanLine . split ( ":" ) [ 0 ] . trim ( ) ; const value = cleanLine . split ( ":" ) . slice ( 1 ) . join ( ":" ) . trim ( ) ; context . push ( new Value ( context . lineNumber , tabs , name , value ) ) ; } } } export class YAMLContext { public lineNumber = 0 ; public tabs = 0 ; public stack : any [ ] = [ ] ; public top ( ) { return this . stack [ this . stack . length - 1 ] ; } public root ( ) { return this . stack [ 0 ] ; } public popTo ( tabs ) { this . tabs = tabs ; if ( tabs >= this . stack . length ) { return ; } this . stack = this . stack . slice ( 0 , tabs + 1 ) ; } public push ( value ) { if ( this . stack . length > 0 ) { if ( this . top ( ) . isArray ( ) ) { this . top ( ) . array ( ) . push ( value ) ; } else { if ( ! this . top ( ) . value ( ) ) { this . top ( ) . value ( { } ) ; } this . top ( ) . value ( ) [ value . name ( ) as string ] = value ; } } this . stack . push ( value ) ; } } /**
 * Holder for every unique item in our YAML file
 * A value can be a string, an array, an object or null (for an empty line)
 * We parse the file and create a bunch of entries, then
 */ export class Value { public static cleanString ( s ? ) { if ( ! s ) { return undefined ; } if ( s . startsWith ( "'" ) || s . startsWith ( "\"" ) ) { s = s . substring ( 1 ) ; } if ( s . endsWith ( "'" ) || s . endsWith ( "\"" ) ) { s = s . substring ( 0 , s . length - 1 ) ; } return s ; } public constructor ( public line , public tabs , private _name ? , private _value ? ) { } public name ( ) { return Value . cleanString ( this . _name ) ; } public isArray ( ) { return Array . isArray ( this . _value ) ; } public isNull ( ) { return this . _value === null ; } public isString ( ) { return ( typeof this . _value ) === "string" ; } public isEmpty ( ) { return this . _value . length === 0 ; } public isObject ( ) { return ( typeof this . _value ) === "object" ; } public object ( ) { const o = this . _value as any ; const newObject = { } ; for ( const key of Object . keys ( o ) ) { const value = o [ key ] ; let flatValue ; if ( value . isString ( ) ) { flatValue = value . string ( ) ; } else if ( value . isArray ( ) ) { flatValue = value . stringArray ( ) ; } else if ( value . isObject ( ) ) { flatValue = value . object ( ) ; } newObject [ key ] = flatValue ; } return newObject ; } public array ( ) { return this . _value as Value [ ] ; } public stringArray ( ) { const strings = [ ] ; for ( const value of this . array ( ) ) { strings . push ( value . string ( ) ) ; } return strings ; } public string ( ) { return Value . cleanString ( this . _value ) as string ; } public toString ( ) { return "Name: " + this . _name + " Value: " + this . _value ; } public value ( v ? ) { if ( ! v ) { return this . _value ; } if ( this . _value ) { return this . _value ; } this . _value = v ; return this . _value ; } }