sourceafEfan::EfanTemplateMeta.fan

using afPlastic::SrcCodeSnippet
using concurrent::AtomicRef

** Meta data about an efan template. 
** 
** Generated by the efan compiler.
const class EfanTemplateMeta {

    ** The 'Type' of the compiled efan template.
    const Type type

    ** The generated fantom code of the efan template (for the inquisitive).
    const Str typeSrc

    ** A unique ID for the template. Defaults to the fully qualified type name.
    const Str templateId
    
    ** Where the template originated from. Example, 'file://layout.efan'. 
    const Uri templateLoc

    ** The original efan template source string.
    const Str templateSrc
    
    // ctx variables used by BedSheetEfan and Genesis - they recompile the template if they change.
    
    ** The 'ctx' type the template was compiled against.
    ** Returns 'null' if a ctx variable was not used.
    const Type? ctxType

    ** The name of the 'ctx' variable the template was compiled with. 
    ** Returns 'null' if a ctx variable was not used.
    const Str? ctxName
    
    internal const Int          srcCodePadding
    internal const AtomicRef    instanceRef := AtomicRef()

    @NoDoc
    new make(|This|? in := null) {
        in?.call(this)
        if (null == this.type)          this.type           = Void#
        if (null == this.typeSrc)       this.typeSrc        = ""
        if (null == this.templateId)    this.templateId     = genId
        if (null == this.templateLoc)   this.templateLoc    = `wherever`
        if (null == this.templateSrc)   this.templateSrc    = ""
    }

    ** The main render method. 
    ** 
    ** 'ctx' must be provided. This prevents you from accidently passing in 'bodyFunc' as the 'ctx'.
    ** Example: 
    ** 
    **   syntax: fantom
    ** 
    **   layout.render() { ... }      // --> WRONG!
    **   layout.render(null) { ... }  // --> CORRECT!
    ** 
    ** The 'bodyFunc' is executed when 'renderBody()' is called. Use it when enclosing content in 
    ** *Layout* templates. Example:
    ** 
    ** pre>
    ** syntax: html
    ** ...
    ** <%= ctx.layout.render(ctx.layoutCtx) { %>
    **   ... my body content ...
    ** <% } %>
    ** ...
    ** <pre
    ** 
    ** The signature for 'bodyFunc' is actually '|->|? bodyFunc' - see source for an explanation of 
    ** why '|Obj?|?' is used.
    Str render(Obj? ctx, |Obj?|? bodyFunc := null) {
        renderFrom(instance, ctx, bodyFunc)
    }

    ** Renders the given template instance, as oppose to the given 
    Str renderFrom(Obj instance, Obj? ctx, |Obj?|? bodyFunc := null) {
        if (instance.typeof.fits(type).not)
            throw ArgErr("Given instance does not fit template type: ${instance.typeof.qname} => ${type.qname}")

        return EfanRenderer.renderTemplate(this, instance, (|->|?) bodyFunc) |->| {
            type.method("_efan_render").call(instance, ctx)
        }
    }
    
    ** Creates an efan template instance. If the template 'type' is const, and no 'ctorParams' are 
    ** specified then the instance is cached for re-use.
    Obj instance(Obj[]? ctorParams := null) {
        if (ctorParams == null && type.isConst) {
            if (instanceRef.val == null)
                instanceRef.val = type.make
            return instanceRef.val
        }
        return type.make(ctorParams)
    }
    
    ** Renders the body of the enclosing efan template. Should only be called from within templates.
    **  
    ** Example, a simple 'layout.html' may be defined as: 
    ** 
    ** pre>
    ** syntax: html
    ** 
    ** <html>
    ** <head>
    **   <title><%= ctx.pageTitle %>
    ** </html>
    ** <body>
    **     <%= renderBody() %>
    ** </html>
    ** <pre
    virtual Str renderBody() {
        EfanRenderer.renderBody
    }

    internal Void throwCompilationErr(Err cause, Int srcCodeLineNo) {
        templateLineNo  := findTemplateLineNo(srcCodeLineNo) ?: throw cause
        srcCodeSnippet  := SrcCodeSnippet(templateLoc, templateSrc)
        throw EfanCompilationErr(srcCodeSnippet, templateLineNo, cause.msg, srcCodePadding, cause)
    }
    
    internal Void throwRuntimeErr(Err cause, Int srcCodeLineNo) {
        templateLineNo  := findTemplateLineNo(srcCodeLineNo) ?: throw cause
        srcCodeSnippet  := SrcCodeSnippet(templateLoc, templateSrc)
        throw EfanRuntimeErr(srcCodeSnippet, templateLineNo, cause.msg, srcCodePadding, cause)
    }

    private Int? findTemplateLineNo(Int srcCodeLineNo) {
        fanLineNo       := srcCodeLineNo - 1    // from 1 to 0 based
        reggy           := Regex<|\s+?// \(efan\) --> ([0-9]+)$|>
        efanLineNo      := (Int?) null
        fanCodeLines    := typeSrc.splitLines
        
        while (fanLineNo > 0 && efanLineNo == null) {
            code := fanCodeLines[fanLineNo]
            reg := reggy.matcher(code)
            if (reg.find) {
                efanLineNo = reg.group(1).toInt
            } else {
                fanLineNo--
            }
        }
        
        return efanLineNo
    }
    
    // Used by afEfanXtra::ComponentCompiler
    ** Clones this object, setting the given values.
    @NoDoc
    EfanTemplateMeta clone([Field:Obj?]? overrides := null) {
        Utils.cloneObj(this) |[Field:Obj?] plan| { plan.setAll(overrides) }
    }
    
    private static Str genId() {
        // format as: "tttttttt-rrrrrrrr"
        time := (DateTime.nowTicks / 1sec.ticks).and(0xffff_ffff)
        rand := (Int.random).and(0xffff_ffff)
        return StrBuf(20).add(time.toHex(8)).addChar('-').add(rand.toHex(8)).toStr
    }
}