mint

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

Description

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

@{luaexp}

Will be substituted with the result of the Lua expression.

@{{luastm}}

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)

Parameters

template

The string that should be expanded.

ename

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.

sandbox

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

expstr

The resulting expanded text or nil if an error occurse.

err

A message if an error occurse, otherwise nil.

Code

--ZFUNC-mint-v0
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 )
      else
         func, err = loadstring( str, chunkname)
         if func then setfenv( func, env ) end
      end
      return func, err
   end

   -- 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
        else
           return prefix .. expr( code ) .. suffix
        end
     end
   )

   -- 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"
    end
    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]]'
    end
    local ok, err = pcall(generate)
    sandbox[ ename ] = oldout
    if not ok or err then
       return nil, err..'\nTemplate script: [[\n'..script..'\n]]'
    end
    return expstr
  end
end

return mint

Examples

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

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

-- 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 )
end
t( mint( "@{nestcall()}@{nestcall()}" )( s ), 'BB' )

t()