using afPlastic::SrcCodeSnippet
using concurrent::AtomicRef
using concurrent::Actor
** Meta data about an efan template.
**
** Generated by the 'EfanCompiler'.
const class EfanMeta {
** The 'Type' of the compiled efan template.
const Type type
** The generated fantom code of the efan template (for the inquisitive).
const Str typeSrc
** Where the template originated from. Example, 'file://layout.efan'.
const Uri templateLoc
** The original efan template source string.
const Str templateSrc
** The 'ctx' parameter type (if any) the template was compiled against.
const Type? ctxType
** The name of the 'ctx' variable (if any) the template was compiled with.
const Str? ctxName
** The rendering method on 'type'.
const Method renderMethod
** The key in 'Actor.locals' that stores the 'StrBuf' that efan renders to.
** See EFan
internal
const Str strBufKey
// plastic will always need the concurrent pod (to store JVM unique pod name),
// so we're not adding any extra dependencies here
internal const AtomicRef instanceRef := AtomicRef()
internal const Int srcCodePadding
@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.templateLoc) this.templateLoc = `wherever`
if (null == this.templateSrc) this.templateSrc = ""
}
** Renders the efan template.
**
** More specifically, this creates an instance of 'type' (via 'Type.make') and calls the efan
** render method with the given 'ctx' argument.
**
** Convenience for:
**
** syntax: fantom
** renderFrom(instance, ctx)
Str render(Obj? ctx) {
renderFrom(instance, ctx)
}
** Calls the render method on the given template.
** Any errs thrown are wrapped in an 'EfanErr' that shows where in the efan template the error occurred.
Str renderFrom(Obj instance, Obj? ctx) {
if (instance.typeof.fits(type).not)
throw ArgErr("Given instance does not fit template type: ${instance.typeof.qname} => ${type.qname}")
// cater for nested renders (see TestLayout)
old := Actor.locals[strBufKey]
Actor.locals[strBufKey] = StrBuf()
try return renderMethod.call(instance, ctx)
catch (Err err)
throw efanRuntimeErr(err)
finally {
if (old == null)
Actor.locals.remove(strBufKey)
else
Actor.locals[strBufKey] = old
}
}
** Returns an instance of 'type' via 'type.make()'.
**
** If 'type' is const and no 'ctorParams' are specified then the instance is cached and returned in later invocations.
Obj instance(Obj[]? ctorParams := null) {
if ((ctorParams == null || ctorParams.isEmpty) && type.isConst) {
if (instanceRef.val == null)
instanceRef.val = type.make
return instanceRef.val
}
return type.make(ctorParams)
}
** Converts the given err to a 'EfanRuntimeErr' that shows where in the efan template the error occurred.
** If the given err can not be converted, it is returned as is.
**
** For advanced use only.
@NoDoc
Err efanRuntimeErr(Err cause) {
regex := Regex.fromStr("^\\s*?${type.qname}\\.${renderMethod.name}\\s\\(${type.pod.name}:([0-9]+)\\)\$")
trace := cause.traceToStr
srcCodeLineNo := trace.splitLines.eachWhile |line -> Int?| {
reggy := regex.matcher(line)
return reggy.find ? reggy.group(1).toInt : null
}
if (srcCodeLineNo == null)
return cause
templateLineNo := findTemplateLineNo(typeSrc, srcCodeLineNo) ?: throw cause
srcCodeSnippet := SrcCodeSnippet(templateLoc, templateSrc)
efanErr := EfanRuntimeErr(srcCodeSnippet, templateLineNo, cause.msg, srcCodePadding, cause)
// allow afxEfan to convert EfanErrs to AfxErrs
newErr := Env.cur.index("afEfan.errFn").eachWhile |qname| {
try return Method.findMethod(qname, false)?.call(efanErr) as Err
catch { /* Meh */ return null }
} ?: efanErr
return newErr
}
internal static Int? findTemplateLineNo(Str typeSrc, 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
}
}