function mint( template [, ename] ) --> ( sandbox ) --> expstr, err


Expand the Lua code contained in the template. The following pattern in the template will be expanded:


Will be substituted with the result of the Lua expression.


Embeds the Lua statement. This allow to mix Lua code and verbatim text.

The function works in two steps:

  1. returns a generator function that takes a table(sandbox)

  2. returns the generator function the expanded string(expstr)



The string that should be expanded.


The Lua code will have access to a global with this name. It will be a function that will append its first argument to the template output string. This parameter is optional, the default value is _o.


The contents of this table will be injected into the environment of the Lua code (both expressions and statements). This allows you to pass parameters to the template.

Return Values


The resulting expanded text or nil if an error occurse.


A message if an error occurse, otherwise nil.


local function mint( template, ename ) --> ( sandbox ) --> expstr, err
   if not ename then ename = '_o' end
   local function expr(e) return ' '..ename..'('..e..')' end

   local function compat_load( str, env )
      local chunkname = 'mint_script'
      local func, err
      if _VERSION ~= 'Lua 5.1' then
         func, err = load( str, chunkname, 't', env )
         func, err = loadstring( str, chunkname)
         if func then setfenv( func, env ) end
      return func, err

   -- Generate a script that expands the template
   local script = template:gsub( '(.-)@(%b{})([^@]*)',
     function( prefix, code, suffix )
        prefix = expr( string.format( '%q', prefix ) )
        suffix = expr( string.format( '%q', suffix ) )
        code = code:sub( 2, #code-1 )

        if code:match( '^{.*}$' ) then
           return prefix .. code:sub( 2, #code-1 ) .. suffix
           return prefix .. expr( code ) .. suffix

   -- The generator must be run only if at least one @{} was found
   local run_generator = ( script ~= template )

   -- Return a function that executes the script with a custom environment
   return function( sandbox )
    if not run_generator then return script end
    local expstr = ''
    if 'table' ~= type( sandbox ) then
      return nil, "mint generator requires a sandbox"
    local oldout = sandbox[ ename ]
    sandbox[ ename ] = function( out ) expstr = expstr..tostring(out) end
    local generate, err = compat_load( script, sandbox )
    if not generate or err then
       sandbox[ ename ] = oldout
       return nil, err..'\nTemplate script: [[\n'..script..'\n]]'
    local ok, err = pcall(generate)
    sandbox[ ename ] = oldout
    if not ok or err then
       return nil, err..'\nTemplate script: [[\n'..script..'\n]]'
    return expstr

return mint


local t = require( "taptest" )
local mint = require( "mint" )

local function contain( a, b )
  return nil ~= a:find( b, 1, 'plain' )

-- Blank templates are not touched
local m = mint( "ok" )
t( 'ok', m{} )

-- Lua expression expansion with @{}
m = mint( "@{item.a} @{item.b:upper()}" )
t( 'a B', m{ item = { a = 'a', b = 'b' } } )
t( 'B A', m{ item = { a = 'B', b = 'a' } } )

-- Mix lua statements and text with @{{}}
m = mint( "@{{for i=1,3 do}} hello @{item}!@{{end}}" )
t( ' hello world! hello world! hello world!', m{ item = 'world' } )

-- Use the output function to expand text from complex lua code
m = mint( "@{{for i=1,3 do o(' hello '..item..'!') end}}", 'o' )
t( ' hello dude! hello dude! hello dude!', m{ item = 'dude' } )

-- Last text appending
m = mint( "@{'true'} ok" )
t( 'true ok', m{} )

-- Value cast in the output function
m = mint( "@{true} ok" )
t( 'true ok', m{} )

-- The script is reported when a compile error is found
m = mint( "@{{][}}" )
local s, e = m{}
t( s, nil )
t( e, 'Template script: ', contain )
t( e, '_o("")][ _o("")', contain )

-- The script is reported when a running error is found
m = mint( "@{{undefined_function()}}" )
local s, e = m{}
t( s, nil )
t( e, 'Template script: ', contain )
t( e, '_o("")undefined_function() _o("")', contain )

-- Nested template
local s = {}
function s.nestcall()
  return mint( "@{'B'}" )( s )
t( mint( "@{nestcall()}@{nestcall()}" )( s ), 'BB' )