--
-- tortir (C) Tor Hveem
-- Simple template based on Tir (C) Zed Shaw
--
-- License: BSD 3-clause : http://tir.mongrel2.org/wiki/license.html
--
--

local require = require
local setmetatable = setmetatable
local io = require "io"
local assert = assert
local string = string
local table = table
local loadstring = loadstring
local load = load
local setfenv = setfenv
local getfenv = getfenv

module('tir',  package.seeall)

-- Simplistic HTML escaping.
function escape(s)
    if s == nil then return '' end

    local esc, i = s:gsub('&', '&amp;'):gsub('<', '&lt;'):gsub('>', '&gt;')
    return esc
end

-- Simplistic Tir template escaping, for when you need to show lua code on web.
local function tirescape(s)
    if s == nil then return '' end

    local esc, i = s:gsub('{', '&#123;'):gsub('}', '&#125;')
    return tir.escape(esc)
end

-- Helper function that loads a file into ram.
function load_file(name)
    local intmp = assert(io.open(name, 'r'))
    local content = intmp:read('*a')
    intmp:close()

    return content
end

-- Used in template parsing to figure out what each {} does.
local VIEW_ACTIONS = {
    ['{%'] = function(code)
        return code
    end,

    ['{{'] = function(code)
        return ('_result[#_result+1] = %s'):format(code)
    end,

    ['{('] = function(code)
        return ([[ 
            if not _children[%s] then
                local tir = require 'template'
                _children[%s] = tir.tload(%s)
            end

            _result[#_result+1] = _children[%s](getfenv())
        ]]):format(code, code, code, code)
    end,

    ['{<'] = function(code)
        return ('local tir = require "tir" _result[#_result+1] =  tir.escape(%s)'):format(code)
    end,
}

-- Takes a view template and optional name (usually a file) and 
-- returns a function you can call with a table to render the view.
function compile_view(tmpl, name)
    local tmpl = tmpl .. '{}'
    local code = {'local _result, _children = {}, {}\n'}

    for text, block in string.gmatch(tmpl, "([^{]-)(%b{})") do
        local act = VIEW_ACTIONS[block:sub(1,2)]
        local output = text

        if act then
            code[#code+1] =  '_result[#_result+1] = [[' .. text .. ']]'
            code[#code+1] = act(block:sub(3,-3))
        elseif #block > 2 then
            code[#code+1] = '_result[#_result+1] = [[' .. text .. block .. ']]'
        else
            code[#code+1] =  '_result[#_result+1] = [[' .. text .. ']]'
        end
    end

    code[#code+1] = 'return table.concat(_result)'

    code = table.concat(code, '\n')
    -- use load from lua 5.2 or luajit
    local func, err = load(code, name, 't',  mt )

    if err then
        assert(func, err)
    end

    return function(context)
        assert(context, "You must always pass in a table for context.")
        setmetatable(context, {__index=_G})
        setfenv(func, context)
        return func()
    end
end


-- Return loaded template
function tload(name)
    local tempf = load_file(name)
    assert(tempf, "Template " .. name .. " does not exist.")
    return compile_view(tempf, name)
end

-- Recompile dynamically
function dynload(name)
    return function (params)
        local tempf = load_file(name)
        assert(tempf, "Template " .. name .. " does not exist.")

        return compile_view(tempf, name)(params)
    end
end
