sourceafBedSheetEfan::EfanTemplates.fan

using afConcurrent
using afIoc
using afIocConfig
using afEfan

** Renders Embedded Fantom (efan) templates against a given context.
const mixin EfanTemplates {

    ** Renders the given template with the ctx.
    ** 
    ** **Warning:** Overuse of this method could cause a memory leak! A new Fantom Type is created 
    ** on every call. 
    abstract Str renderFromStr(Str efan, Obj? ctx := null)
    
    ** Renders an '.efan' template file with the given ctx. 
    ** The compiled '.efan' template is cached for re-use.   
    abstract Str renderFromFile(File efanFile, Obj? ctx := null)

    ** Returns (or compiles) a renderer for the given file.
    abstract EfanRenderer rendererForFile(File efanFile, Type? ctxType := null)
}

internal const class EfanTemplatesImpl : EfanTemplates {
    private const static Log    log := Utils.getLog(EfanTemplates#)
    private const SynchronizedFileMap   fileCache
    
    
    @Inject @Config { id="afEfan.templateTimeout" }
    private const Duration templateTimeout
    
    @Inject @Config { id="afEfan.ctxVarName" }  
    private const Str ctxVarName

    @Inject private const EfanViewHelpers   viewHelpers
    @Inject private const EfanCompiler      compiler

    new make(ActorPools actorPools, |This|in) { 
        in(this)
        fileCache = SynchronizedFileMap(actorPools["afBedSheetEfan.fileCache"], templateTimeout)
    }

    override Str renderFromStr(Str efan, Obj? ctx := null) {
        renderer := compiler.compile(`rendered/from/str`, efan, ctx?.typeof, viewHelpers.mixins)
        return renderer.render(ctx)
    }

    override Str renderFromFile(File efanFile, Obj? ctx := null) {
        renderer := rendererForFile(efanFile, ctx?.typeof)
        
        return renderer->render(ctx)
    }
    
    override EfanRenderer rendererForFile(File efanFile, Type? ctxType := null) {
        if (!efanFile.exists)
            throw IOErr(ErrMsgs.templatesFileNotFound(efanFile))

        renderer := (EfanRenderer) fileCache.getOrAddOrUpdate(efanFile) |->Obj| {
            template    := efanFile.readAllStr
            renderer    := compiler.compile(efanFile.normalize.uri, template, ctxType, viewHelpers.mixins)
            return renderer
        }
        
        if (ctxType != null) {
            rendererCtxType := renderer.efanMetaData.ctxType
            if (rendererCtxType == null || !ctxType.fits(rendererCtxType)) {
                log.warn(LogMsgs.templatesCtxDoesNotFitRendererCtx(ctxType, rendererCtxType, efanFile))
                template    := efanFile.readAllStr
                renderer    = compiler.compile(efanFile.normalize.uri, template, ctxType, viewHelpers.mixins)
                fileCache[efanFile] = renderer
            }
        }
    
        return renderer
    }
}