Skip to content

mustacheExt 扩展参考 ​

原文: https://finproducts.atlassian.net/wiki/spaces/JIDD3/pages/34793554547/FIN+5.3.1+mustacheExt 中文整理基于 FIN 5.3.1

导读: Mustache 模板系统

以下为 API 参考,保留英文以便对照源码;目录已列出(英文标题便于检索);概述已尽量中译。


目录(导读) ​

  • classes
  • Slot Details
    • call
    • make
    • wrappedFunc
    • addTemplate
    • context
    • getReader
    • run
    • createMustacheVisitor
    • resolvePartialPath
    • mustache
    • find
    • get
    • iterate
    • allTags
    • markerTags
    • valueTags
    • wrap
    • wrapAny
    • wrapGrid
    • wrapList
    • templatize
    • fromDict
    • fromMap
    • name
    • type
    • value
    • applyTemplate
    • parseTemplate

概述(中译) ​

src 类 FantomWrapper : Obj, Wrapper

构造函数

字段

方法

插槽详情

src 虚拟重写 Obj? call(List? scopes)


API 正文(英文) ​

make ​

src new make(|List?->Obj?| f)


wrappedFunc ​

src |List?->Obj?| wrappedFunc


FinstackMustacheResolver ​

src class FinstackMustacheResolver : Obj, MustacheResolver

Constructors

make

Fields

context

Methods

addTemplate
getReader

Slot Details ​


addTemplate ​

src Void addTemplate(Str name, Str template)


context ​

src HxContext context


getReader ​

src virtual override Reader? getReader(Str? resourceName)


make ​

src new make(HxContext context)


HaystackMustache ​

src class HaystackMustache : DefaultMustache

Constructors

make

Methods

run

Slot Details ​


make ​

src new make(TemplateContext cx, MustacheFactory factory, Code[] codes, Str name)


run ​

src virtual override Writer? run(Writer? writer, List? scopes)


HaystackMustacheFactory ​

src class HaystackMustacheFactory : DefaultMustacheFactory

Constructors

make

Methods

createMustacheVisitor
resolvePartialPath

Slot Details ​


createMustacheVisitor ​

src virtual override MustacheVisitor? createMustacheVisitor()


make ​

src new make(HxContext context, FinstackMustacheResolver resolver)


resolvePartialPath ​

src virtual override Str? resolvePartialPath(Str? dir, Str? name, Str? extension)


HaystackMustacheVisitor ​

src class HaystackMustacheVisitor : DefaultMustacheVisitor

Constructors

make

Methods

mustache

Slot Details ​


make ​

src new make(MustacheFactory factory)


mustache ​

src virtual override Mustache? mustache(TemplateContext? cx)


HaystackObjectHandler ​

src class HaystackObjectHandler : SimpleObjectHandler

Constructors

make

Methods

find
get
iterate

Slot Details ​


find ​

src virtual override Wrapper? find(Str? name, List? scopes)


get ​

src virtual override Obj? get(Str? name, Obj? scope)


iterate ​

src virtual override Writer? iterate(Iteration? iteration, Writer? writer, Obj? object, List? scopes)


make ​

src new make(HxContext context)


MustacheDict ​

src const class MustacheDict : TypedDict

Constructors

make

Methods

allTags
markerTags
valueTags
wrap
wrapAny
wrapGrid
wrapList

Slot Details ​


allTags ​

src NameValuePair[] allTags()


make ​

src new make(Dict rec, |This| f)


markerTags ​

src Str[] markerTags()


valueTags ​

src NameValuePair[] valueTags()


wrap ​

src static MustacheDict wrap(Dict rec)


wrapAny ​

src static Obj wrapAny(Obj item)


wrapGrid ​

src static Grid wrapGrid(Grid grid)


wrapList ​

src static List wrapList(List list)


MustacheFuncs ​

src class MustacheFuncs : Obj

Methods

templatize

Slot Details ​


templatize ​

src @Axon static Str templatize(Obj scope, Str template)


MustacheLib ​

src const class MustacheLib : HxLib

Slot Details ​


NameValuePair ​

src const class NameValuePair : Obj

Constructors

make

Fields

name
value

Methods

fromDict
fromMap
type

Slot Details ​


fromDict ​

src static NameValuePair[] fromDict(Dict dict)


fromMap ​

src static NameValuePair[] fromMap(Map map)


make ​

src new make(Str name, Obj? value)


name ​

src const Str name


type ​

src Str type()


value ​

src const Obj? value


TemplateProcessor ​

src class TemplateProcessor : Obj

Constructors

make

Methods

applyTemplate
parseTemplate

Slot Details ​


applyTemplate ​

src Str applyTemplate(Obj item)


make ​

src new make(HxContext context)


parseTemplate ​

src Void parseTemplate(Str templateStr, [Str:Str]? partials := null)


using [java] com.github.mustachejava::MustacheResolver
using [java] java.io::StringReader
using [java] java.io::Reader

using hx
using hxIO
using haystack

class FinstackMustacheResolver : MustacheResolver
{
  HxContext context
  private Str:Str templates := [:]
  
  new make(HxContext context)
  {
    this.context = context
  }
  
  public Void addTemplate(Str name, Str template)
  {
    this.templates.set(name.trim, template)
  }
  
  override public Reader? getReader(Str? resourceName)
  {
    Str template := ""
    
    if (resourceName.startsWith("@"))
    {
      // Look up partial in the DB
      templateRec := context.db.readById(Ref(resourceName), false)
      
      if (templateRec != null)
      {
        templateHandle := IOHandle.fromObj(this.context.rt, templateRec)
        template = templateHandle.inToBuf.readAllStr
      }
    }
    else
      template = this.templates.get(resourceName.trim) ?: ""
    
    return StringReader(template)
  }
}

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST


using [java] com.github.mustachejava.codes::DefaultMustache
using [java] com.github.mustachejava::MustacheFactory
using [java] com.github.mustachejava::TemplateContext
using [java] com.github.mustachejava::Code
using [java] com.github.mustachejava.util::InternalArrayList
using [java] java.util::List as JavaList
using [java] java.io::Writer
using haystack

class HaystackMustache : DefaultMustache
{
  new make(
    TemplateContext cx,
    MustacheFactory factory,
    Code[] codes,
    Str name) : super(cx, factory, codes, name)
  {
    
  }
  
  override public Writer? run(Writer? writer, JavaList? scopes)
  {
    return super.run(
      writer,
      this.mapScopesToHaystack(scopes)
    )
  }
  
  private JavaList? mapScopesToHaystack(JavaList? scopes)
  {
    if (scopes == null)
      return null
    
    items := (Obj[]) scopes.toArray([,])
    result := InternalArrayList()
          
    items.each |item| 
    {
      if (item is Grid)
      {
        result.add(((Grid) item).map |row|
        {
          MustacheDict.wrap(row)
        })
      }
      else
      if (item is Dict)
      {
        result.add(MustacheDict.wrap(item))
      }
      else
      {
        result.add(item)
      }
    }
    
    return result
  }
}

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST


using [java] com.github.mustachejava::DefaultMustacheFactory
using [java] com.github.mustachejava::MustacheVisitor
using hx

class HaystackMustacheFactory : DefaultMustacheFactory
{
  private FinstackMustacheResolver resolver
  
  new make(HxContext context, FinstackMustacheResolver resolver): super(resolver)
  {
    this.oh = HaystackObjectHandler(context)
    this.resolver = resolver
  }
  
  override public MustacheVisitor? createMustacheVisitor()
  {
    return HaystackMustacheVisitor(this)
  }
    
  // We will enable passing partials in and caching them. The factory doesn't have access
  // to the file system. The result will be looked up in the FinstackMustacheResovler
  // It will allow names that are added or records. {{> @12345 }}
  override public Str? resolvePartialPath(Str? dir, Str? name, Str? extension)
  {
    return name
  }
}

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST


using [java] com.github.mustachejava::DefaultMustacheVisitor
using [java] com.github.mustachejava::Mustache
using [java] com.github.mustachejava::MustacheFactory
using [java] com.github.mustachejava::TemplateContext
using [java] com.github.mustachejava::Code

class HaystackMustacheVisitor : DefaultMustacheVisitor 
{
  private static const Code[] EMPTY_CODES := [,]
  
  new make(MustacheFactory factory) : super(factory)
  {
    
  }
  
  override public Mustache? mustache(TemplateContext? cx)
  {
    return HaystackMustache(
      cx,
      this.df,
      this.list.toArray(EMPTY_CODES),
      cx.file()
    )
  }
}

// resolvePartialPath gives path name
// factory.compilePartial(pathName)

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST


using [java] com.github.mustachejava.reflect::SimpleObjectHandler
using [java] com.github.mustachejava.reflect::AbstractObjectHandler
using [java] com.github.mustachejava.util::Wrapper
using [java] com.github.mustachejava.util::InternalArrayList
using [java] com.github.mustachejava::Iteration
using [java] java.util::List as JavaList
using [java] fanx.interop::Interop
using [java] java.io::Writer

using haystack
using hx

class HaystackObjectHandler : SimpleObjectHandler
{
  private HxContext context
  
  new make(HxContext context): super()
  {
    this.context = context
  }
  
  public override Obj? get(Str? name, Obj? scope)
  {
    try
    {
      if (scope is Dict || scope is Map)
        return this.processDict(scope, name)
      
      if (scope is Ref)
        return this.processRef((Ref) scope, name)
      
      /*
      if (scope is Grid)
        return this.processGrid((Grid) scope, name)
      
      if (scope is List)
        return this.processList((List) scope, name)
      */
      
      if (scope is Str || scope is DateTime || scope is Time || scope is Number)
      {
        // TODO: create a better way to handle this.
        // this only calls a method that takes no paramters
        // properly parse and enable method calls with paramters
        // in the template. (i.e. {{date.toLocal("mm-dd-yyyy")}} )
        // May be possible to treat {{ expression }} as axon
        // and evaluate it against the scope
        
        // Possible method
        result := this.callMethod(name, scope)
        
        if (result != null)
          return result
      }
      
      return super.get(name, scope)
    }
    catch (Err error)
    {
      echo ("Error occurred: $error")
    }
    
    return "?"
  }
  
  public override Wrapper? find(Str? name, JavaList? scopes)
  {
    if (name?.contains("->") == true)
    {
      return FantomWrapper() | JavaList? innerScopes -> Obj? |
      {
        for (i := innerScopes.size - 1; i >= 0; i--)
        {
          scope := innerScopes.get(i)
          
          if (scope != null)
          {
            index := name.index("->")
            
            if (index == null)
            {
              result := get(name, scope)
          
              if (result != SimpleObjectHandler.NOT_FOUND)
                return result
            }
            else
            {
              subScopes := makeInternalList(scope)
              wrapper := find(name[0..index - 1], subScopes)
              
              if (wrapper != null)
              {
                scope = wrapper.call(subScopes)
                
                if (scope == null)
                  continue
                
                // DeReference
                subScopes = makeInternalList(
                  this.processRef(scope)
                )
                
                return find(
                  name[index + 2..-1],
                  subScopes
                ).call(subScopes)
              }
            }
          }
        }
        
        return null
      } as Wrapper
    }
    
    return super.find(name, scopes)
  }
  
  public override Writer? iterate(
    Iteration? iteration,
    Writer? writer,
    Obj? object,
    JavaList? scopes)
  {
    if (object is Grid)
      object = ((Grid) object).toRows
    
    if (object is List)
      object = this.processList(object)
    
    return super.iterate(iteration, writer, object, scopes)
  }
  
  private Obj? processGrid(Grid grid, Str? tagName := null)
  {
    if (tagName == "meta")
      return grid.meta
    
    if (tagName == "size")
      return grid.size
    
    return this.processList(grid.toRows)    
  }
  
  private Obj? processList(List list, Str? tagName := null)
  {
    if (tagName == "size")
      return list.size
    
    return Interop.toJava(
      MustacheDict.wrapList(list)
    )
    
  }
  
  private Obj? processDict(Obj dict, Str? tagName)
  {
    if (tagName == null)
    {
      if (dict is Dict)
        return MustacheDict.wrap(dict)
      
      if (dict is Map)
        return NameValuePair.fromMap(dict)
      
      return dict
    }
    
    Obj? result
    
    if (dict is Dict)
    {
      if (((Dict) dict).has(tagName))
      {
        result = ((Dict) dict).get(tagName)
        
        if (result is Str && ((Str) result).contains("\$"))
          result = Etc.macro(result, dict)
      }
      else
      {       
        // Check for methods or fields of the object
        result = this.callMethod(tagName, dict)
      }
      
    }
    else
    {
      // Map
      result = ((Map) dict).containsKey(tagName)
        ? ((Map) dict).get(tagName, false)
        : null
    }
    
    if (result != null)
    {
      if (result is Grid)
        return this.processGrid((Grid) result)
      
      if (result is List)
        return this.processList((List) result)
      
      if (result is Dict)
        result = MustacheDict.wrap(result)

      return result
    }
    
    return AbstractObjectHandler.NOT_FOUND
  }
  
  private Obj? processRef(Ref ref, Str? name := null)
  {
    if (name == "dis")
      return ref.dis
    
    if (name == "id")
      return ref.id
    
    record := this.context.db.readById(ref, false)
    
    if (record != null)
    {
      if (name != null)
      {
        if (record.has(name))
        {
          recTagVal := record.get(name)
          
          if (recTagVal is Dict)
            return MustacheDict.wrap(recTagVal)
          
          return recTagVal
        }
      }
      else
        return MustacheDict.wrap(record)
    }
    
    return AbstractObjectHandler.NOT_FOUND
  }
  
  private Obj? callMethod(Str name, Obj scope)
  {
    // Check for methods or fields of the object
    method := scope.typeof.method(name, false)
    
    if (method != null)
    {
      return method.call(scope)
    }
    else
    {
      field := scope.typeof.field(name, false)
      
      if (field != null)
        return field.get(scope)
    }
    
    return null
  }
  
  private JavaList makeInternalList(Obj scope)
  {
    scopes := InternalArrayList()
    scopes.add(scope)
    
    return scopes
  }
}

const class MustacheDict : TypedDict
{
  new make(Dict rec, | This | f) : super(rec)
  {
    f(this)
  }
  
  public Str[] markerTags()
  {
    tags := [,]
    
    this.each |value, tagName|
    {
      if (value is Marker)
        tags.add(tagName)
    }
    
    return tags
  }
  
  public NameValuePair[] valueTags()
  {
    values := [,]
    
    this.each |value, tagName|
    {
      if (value is Marker)
        return // Skip Markers
      
      values.add(NameValuePair(tagName, value))
    }
    
    return values
  }
  
  public NameValuePair[] allTags()
  {
    return NameValuePair.fromDict(this)
  }
  
  public static Obj wrapAny(Obj item)
  {
    if (item is Grid)
      return wrapGrid(item)
    
    if (item is List)
      return wrapList(item)
    
    if (item is Dict)
      return wrap(item)
    
    return item
  }
  
  public static MustacheDict wrap(Dict rec)
  {
    return create(MustacheDict#, rec)
  }
  
  public static Grid wrapGrid(Grid grid)
  {
    return grid.map |row| {
      return MustacheDict.wrapAny(row)
    }
  }
  
  public static List wrapList(List list)
  {
    return list.map | item | {
      return MustacheDict.wrapAny(item)
    }
  }
}

class FantomWrapper : Wrapper
{
  | JavaList? scopes -> Obj? | wrappedFunc
  
  new make(| JavaList? scopes -> Obj? | f)
  {
    this.wrappedFunc = f
  }
  
  override public Obj? call(JavaList? scopes)
  {
    return this.wrappedFunc.call(scopes)
  }
}

const class NameValuePair
{
  const Str name
  const Obj? value
  
  new make(Str name, Obj? value)
  {
    this.name = name
    this.value = value
  }
  
  public Str type()
  {
    if (this.value == null)
      return "null"
    
    return this.value.typeof.name
  }
  
  public static NameValuePair[] fromMap(Map map)
  {
    result := [,]
    
    map.each |value, name| {
      result.add(NameValuePair(name, value))
    }
    
    return result
  }
  
  public static NameValuePair[] fromDict(Dict dict)
  {
    result := [,]
    
    dict.each |value, name| {
      result.add(NameValuePair(name, value))
    }
    
    return result
  }
}

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST


using haystack
using hx
using axon

class MustacheFuncs
{
  // Mustache
  @Axon
  public static Str templatize(Obj scope, Str template)
  {
    processor := TemplateProcessor(HxContext.curHx)
    processor.parseTemplate(template)
    
    return processor.applyTemplate(scope)
  }
}

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST


using hx

const class MustacheLib : HxLib
{
}

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST


using hx
using haystack
using [java] com.github.mustachejava::Mustache
using [java] com.github.mustachejava::DefaultMustacheFactory
using [java] java.io::StringReader
using [java] java.io::StringWriter

class TemplateProcessor 
{
  private DefaultMustacheFactory factory
  private FinstackMustacheResolver resolver
  private Mustache? template
  
  new make(HxContext context)
  {
    this.resolver = FinstackMustacheResolver(context)
    this.factory = HaystackMustacheFactory(
      context,
      this.resolver
    )
  }
  
  public Void parseTemplate(Str templateStr, [Str:Str]? partials := null)
  {
    if (partials != null)
    {
      partials.each |partialStr, partialName| {
        
        echo ("Adding Partial: $partialName")
        this.resolver.addTemplate(partialName, partialStr)
      }
    }
        
    this.template = this.factory.compile(StringReader(templateStr), "template")
  }
  
  public Str applyTemplate(Obj item)
  {
    writer := StringWriter(1000)
    
    this.template.execute(
      writer,
      [item]
    )
    
    writer.flush
    return writer.toString
  }
}

Skyspark 3.1.12 ∙ 20-Oct-2026 14:19 CEST