sokol-luajit/editor/twig/aspect/funcs.lua

270 lines
7.8 KiB
Lua

local unpack = unpack or table.unpack
local concat = table.concat
local err = require("aspect.error")
local compiler_error = err.compiler_error
local runtime_error = err.runtime_error
local config = require("aspect.config")
local tags = require("aspect.tags")
local tag_type = config.compiler.tag_type
local dump = require("aspect.utils").dump
local quote_string = require("aspect.utils").quote_string
local strip = require("aspect.utils").strip
local date = require("aspect.date")
local utils = require("aspect.utils")
local range = require("aspect.utils.range")
local pairs = pairs
local ipairs = ipairs
local func = {
args = {},
fn = {},
parsers = {}
}
--- Add function
--- @param name string
--- @param info table
--- @param fn fun(...):any
function func.add(name, info, fn)
func.args[name] = info.args
func.fn[name] = fn
func.parsers[name] = info.parser
end
--- @param __ aspect.output
--- @param name string
--- @param context table
local function parent(__, name, context)
local block
if __.blocks[name] then
for i = __.blocks[name].i + 1, #__.views do
block = __.views[i].blocks[name]
if block then
break
end
end
else
return nil
end
if context then
if block then
block(__, context)
end
elseif block then
return true
end
end
--- Function {{ parent() }}
func.add('parent', {
args = {},
--- @param compiler aspect.compiler
parser = function(compiler)
local tag = compiler:get_last_tag("block")
if not tag then
compiler_error(nil, "syntax", "{{ parent() }} should be called in the {% block %}")
else
tag.parent = true
end
if compiler.tag_type == tag_type.EXPRESSION then -- {{ parent(...) }}
local vars = utils.implode_hashes(compiler:get_local_vars())
if vars then
return '__.fn.parent(__, ' .. quote_string(tag.block_name) .. ', __.setmetatable({ ' .. vars .. '}, { __index = _context }))' ;
else
return '__.fn.parent(__, ' .. quote_string(tag.block_name) .. ', _context)' ;
end
else -- {% if parent(...) %}
return '__.fn.parent(__, ' .. quote_string(tag.block_name) .. ', nil)' ;
end
end
}, parent)
--- @param __ aspect.output
--- @param name string
--- @param template string|nil
local function block(__, context, name, template)
local f
if template then
local view = __:get_view(template)
if view.blocks[name] then
f = view.blocks[name]
end
elseif __.blocks[name] then
f = __.blocks[name].f
end
if context then
if f then
f(__, context)
else
runtime_error(__, "block " .. name .. " not found")
end
else
return f ~= nil
end
end
--- Function {{ block(name, [template]) }}
func.add('block', {
args = {
[1] = {name = "name", type = "string"},
[2] = {name = "template", type = "string"},
},
--- @param compiler aspect.compiler
--- @param args table
parser = function(compiler, args)
if not args.name then
compiler_error(nil, "syntax", "function block() requires argument 'name'")
end
if compiler.tag_type == tag_type.EXPRESSION then -- {{ block(...) }}
local vars, context = utils.implode_hashes(compiler:get_local_vars()), "_context"
if vars then
context = '__.setmetatable({ ' .. vars .. ' }, { __index = _context })' ;
end
return '__.fn.block(__, ' .. context .. ', ' .. args.name .. ', ' .. (args.template or 'nil') .. ')', false
else -- {% if block(...) %}
return '__.fn.block(__, nil, ' .. args.name .. ', ' .. (args.template or 'nil') .. ')'
end
end
}, block)
--- Function {{ include(name, [vars], [ignore_missing], [with_context]) }}
func.add('include', {
args = {
[1] = {name = "name", type = "any"},
[2] = {name = "vars", type = "table"},
[3] = {name = "ignore_missing", type = "boolean"},
[4] = {name = "with_context", type = "boolean"},
},
--- @param compiler aspect.compiler
--- @param args table
parser = function (compiler, args)
if compiler.tag_type == tag_type.EXPRESSION then
args.with_vars = true
return tags.include(compiler, args)
else
return '__.fn.include(__, ' .. args.name .. ', true, nil)'
end
end
}, function (__, names, ignore, context)
local view, error = __.opts.get(names)
if not context then
return view ~= nil
end
if error then
runtime_error(__, error)
elseif not view then
if not ignore then
runtime_error(__, "Template(s) not found. Trying " .. utils.join(names, ", "))
else
return
end
end
view.body(__, context)
end)
--- Function {{ range(from, to, step) }}
func.add('range', {
args = {
[1] = {name = "from", type = "number"},
[2] = {name = "to", type = "number"},
[3] = {name = "step", type = "number"},
}
}, function (__, args)
if not args.from then
__:notice("range(): requires 'from' argument")
return {}
end
if not args.to then
__:notice("range(): requires 'to' argument")
return {}
end
if not args.step or args.step == 0 then
args.step = 1
end
if args.step > 0 and args.to < args.from then
__:notice("range(): 'to' less than 'from' with positive step")
return {}
elseif args.step < 0 and args.to > args.from then
__:notice("range(): 'to' great than 'from' with negative step")
return {}
end
return range.new(args.from, args.to, args.step)
end)
func.add("import", {
args = {}
}, function (__, from, names)
if not from then
runtime_error(__, "import(): requires 'from' argument")
end
local view, error = __.opts.get(from)
if not view then
runtime_error(__, error)
end
if names then
local macros = {}
for i = 1, #names do
local macro = view.macros[ names[i] ]
if not macro then
runtime_error(__, "import(): macro '".. names[i] .. "' doesn't exists in the template " .. view.name)
end
macros[#macros + 1] = macro
end
return unpack(macros)
else
return view.macros or {}
end
end)
--- {{ date(date) }}
func.add("date", {
args = {
[1] = {name = "date", type = "any"}
}
}, function (__, args)
return date.new(args.date)
end)
--- {{ dump(...) }}
func.add("dump", {
args = nil,
--- @param compiler aspect.compiler
--- @param _ nil
--- @param tok aspect.tokenizer
parser = function (compiler, _, tok)
if tok:is(")") then
return "__.fn.dump(__, nil, _context)"
end
local vars = {}
while true do
local from = tok:get_pos()
local expr = compiler:parse_expression(tok)
local name = tok:get_path_as_string(from, tok.i - 1)
vars[#vars + 1] = "{name = " .. quote_string(strip(name)) .. ", value = " .. expr .. "}"
if tok:is(")") then
break
end
tok:require(","):next()
end
return "__.fn.dump(__, {" .. concat(vars, ",") .. "})"
end
}, function (__, args, ctx)
local out = {}
if args then
out[1] = "Dump values:"
for _, v in ipairs(args) do
out[#out + 1] = v.name .. ": " .. dump(v.value) .. "\n"
end
else
out[1] = "Dump context:"
for name, value in pairs(ctx) do
out[#out + 1] = name .. ": " .. dump(value) .. "\n"
end
end
out[#out + 1] = "\nStack:\n" .. __:get_callstack()
return concat(out, "\n")
end)
return func