import is from "is" ; import extend from "extend" ; import Immutable from "immutable" ; import Promise from "../utils/promise" ; import genKey from "../utils/genKey" ; import TemplateShortcut from "./templateShortcut" ; import nunjucks from "nunjucks" ; const NODE_ENDARGS = "%%endargs%%" ; class TemplateBlock extends Immutable . Record ( { name : String ( ) , end : String ( ) , process : Function ( ) , blocks : Immutable . List ( ) , shortcuts : Immutable . Map ( ) , } , "TemplateBlock" ) { getName ( ) { return this . get ( "name" ) ; } getEndTag ( ) { return this . get ( "end" ) || `end${ this . getName ( ) }` ; } getProcess ( ) { return this . get ( "process" ) ; } getBlocks ( ) { return this . get ( "blocks" ) ; } return { TemplateShortcut | undefined } getShortcuts ( ) { const shortcuts = this . get ( "shortcuts" ) ; if ( shortcuts . size === 0 ) { return undefined ; } return TemplateShortcut . createForBlock ( this , shortcuts ) ; } return { string } getExtensionName ( ) { return `Block${ this . getName ( ) }Extension` ; } return { Nunjucks . Extension } toNunjucksExt ( mainContext , blocksOutput ) { blocksOutput = blocksOutput || { } ; const that = this ; const name = this . getName ( ) ; const endTag = this . getEndTag ( ) ; const blocks = this . getBlocks ( ) . toJS ( ) ; function Ext ( ) { this . tags = [ name ] ; this . parse = function ( parser , nodes ) { let lastBlockName = null ; let lastBlockArgs = null ; const allBlocks = blocks . concat ( [ endTag ] ) ; const tok = parser . nextToken ( ) ; lastBlockArgs = parser . parseSignature ( null , true ) ; parser . advanceAfterBlockEnd ( tok . value ) ; const args = new nodes . NodeList ( ) ; const bodies = [ ] ; const blockNamesNode = new nodes . Array ( tok . lineno , tok . colno ) ; const blockArgCounts = new nodes . Array ( tok . lineno , tok . colno ) ; do { const currentBody = parser . parseUntilBlocks . apply ( parser , allBlocks ) ; blockNamesNode . addChild ( new nodes . Literal ( args . lineno , args . colno , lastBlockName ) ) ; blockArgCounts . addChild ( new nodes . Literal ( args . lineno , args . colno , lastBlockArgs . children . length ) ) ; bodies . push ( currentBody ) ; lastBlockArgs . children . forEach ( ( child ) => { args . addChild ( child ) ; } ) ; lastBlockName = parser . nextToken ( ) . value ; if ( lastBlockName != endTag ) { lastBlockArgs = parser . parseSignature ( null , true ) ; } parser . advanceAfterBlockEnd ( lastBlockName ) ; } while ( lastBlockName != endTag ) ; args . addChild ( blockNamesNode ) ; args . addChild ( blockArgCounts ) ; args . addChild ( new nodes . Literal ( args . lineno , args . colno , NODE_ENDARGS ) ) ; return new nodes . CallExtensionAsync ( this , "run" , args , bodies ) ; } ; this . run = function ( context ) { const fnArgs = Array . prototype . slice . call ( arguments , 1 ) ; let args ; const blocks = [ ] ; let bodies = [ ] ; const callback = fnArgs . pop ( ) ; const endArgIndex = fnArgs . indexOf ( NODE_ENDARGS ) ; args = fnArgs . slice ( 0 , endArgIndex ) ; bodies = fnArgs . slice ( endArgIndex + 1 ) ; const blockArgCounts = args . pop ( ) ; const blockNames = args . pop ( ) ; blockNames . forEach ( ( name , i ) => { const countArgs = blockArgCounts [ i ] ; const blockBody = bodies . shift ( ) ; const blockArgs = countArgs > 0 ? args . slice ( 0 , countArgs ) : [ ] ; args = args . slice ( countArgs ) ; const blockKwargs = extractKwargs ( blockArgs ) ; blocks . push ( { name : name , body : blockBody ( ) , args : blockArgs , kwargs : blockKwargs , } ) ; } ) ; const mainBlock = blocks . shift ( ) ; mainBlock . blocks = blocks ; Promise ( ) . then ( ( ) => { const ctx = extend ( { ctx : context , } , mainContext || { } ) ; return that . applyBlock ( mainBlock , ctx ) ; } ) . then ( ( result ) => { return that . blockResultToHtml ( result , blocksOutput ) ; } ) . nodeify ( callback ) ; } ; } return Ext ; } param { Object } inner param { Object } context return { Promise < String > | String } applyBlock ( inner , context ) { const processFn = this . getProcess ( ) ; inner = inner || { } ; inner . args = inner . args || [ ] ; inner . kwargs = inner . kwargs || { } ; inner . blocks = inner . blocks || [ ] ; const r = processFn . call ( context , inner ) ; if ( Promise . isPromiseAlike ( r ) ) { return r . then ( this . normalizeBlockResult . bind ( this ) ) ; } else { return this . normalizeBlockResult ( r ) ; } } param { Object | String } result return { Object } normalizeBlockResult ( result ) { if ( is . string ( result ) ) { result = { body : result } ; } result . name = this . getName ( ) ; return result ; } param { Object } result param { Object } blocksOutput return { string } blockResultToHtml ( result , blocksOutput ) { let indexedKey ; const toIndex = ! result . parse || result . post !== undefined ; if ( toIndex ) { indexedKey = genKey ( ) ; blocksOutput [ indexedKey ] = result ; } if ( result . parse ) { return result . body ; } return `{{-%${ indexedKey }%-}}` ; } param { string } blockName param { Object } block return { TemplateBlock } static create ( blockName , block ) { if ( is . fn ( block ) ) { block = new Immutable . Map ( { process : block , } ) ; } block = new TemplateBlock ( block ) ; block = block . set ( "name" , blockName ) ; return block ; } } param { Array } args return { Object } function extractKwargs ( args ) { const last = args [ args . length - 1 ] ; return is . object ( last ) && last . __keywords ? args . pop ( ) : { } ; } export default TemplateBlock ;