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

328 lines
11 KiB
Lua

local var_dump = require("aspect.utils").var_dump
local dump = require("aspect.utils").dump
local numerate = require("aspect.utils").numerate_lines
local get_keys = require("aspect.utils").keys
local aspect = require("aspect.template")
local fs_loader = require("aspect.loader.filesystem")
local json = require("aspect.config").json
local table = table
local cli = {
aliases = {
lint = true,
l = "lint",
debug = true,
d = "debug",
include = true,
I = "include",
help = true,
h = "help",
escape = true,
e = "escape",
dump = true,
p = "dump",
arg = true,
a = "arg"
}
}
--- @param arguments table list of arguments
--- @param aliases table list of possible arguments and short aliases
--- @param with_values table arguments with values
--- @return table with arguments and values (true - if no value)
--- @return table list of paths at the end
--- @return string error
function cli.parse_args(arguments, aliases, with_values)
with_values = with_values or {}
aliases = aliases or {}
local vals, paths, i = {}, {}, 1
while i <= #arguments do
local arg = arguments[i]
local prefix, _name, value = arg:match("^(%-%-?)([%a_-]+)=?(.*)")
if value == "" then
value = nil
end
if prefix and #paths == 0 then
if aliases[_name] then
local name
if prefix == "-" and aliases[_name] ~= true then -- this is alias
name = aliases[_name]
else
name = _name
end
if with_values[name] and value then
vals[name] = value
elseif with_values[name] and not value then
if not arguments[i + 1] or (arguments[i + 1] and arguments[i + 1]:match("^%-")) then
return nil, nil, "no value for '".. _name .. "'"
end
if vals[name] then
if type(vals[name]) ~= "table" then
vals[name] = {vals[name]}
end
table.insert(vals[name], arguments[i + 1])
else
vals[name] = arguments[i + 1]
end
i = i + 1
elseif not with_values[name] and value then
return nil, nil, "flag '" .. _name .. "' should be without value"
else
vals[name] = true
end
else
return nil, nil, "unknown flag '" .. _name .. "'"
end
else
table.insert(paths, 1, arg)
end
i = i + 1
end
return vals, paths
end
--- Read whole file
--- @param path string file path
--- @return string data
--- @return string error
function cli.read_file(path)
local f, err, data
f, err = io.open(path, "rb")
if err then
return nil, err
end
data, err = f:read("*all")
if err then
return nil, err
end
f:close()
if not data or data == "" then
return nil, "empty file"
end
return data
end
function cli.run(arguments)
arguments = arguments or {}
local autoescape = false
local options, paths, err = cli.parse_args(arguments, cli.aliases, {include = true, arg = true} )
if err then
return 1, "[ERROR] " .. err
end
--local options = parse_args(arguments, {include = true, I = true})
--for k, v in pairs(options) do
-- if cli.aliases[k] then
-- options[cli.aliases[k]] = v
-- end
--end
if options.help or #arguments < 2 then
return 0, "Aspect " .. aspect._VERSION .. ", the template engine.\n\n" ..[[
Usage: aspect [options] data_file template_name
Syntax: https://github.com/unifire-app/aspect/blob/master/docs/syntax.md
Params:
data_file path to file with fixture (json)
template_name path or name of the template for rendering
Options:
--help -h print this help
--include <dir> -I<dir> use <dir> for loading other templates
--escape -e enables auto-escaping with 'html' strategy.
--lint just lint the template
--dump dump information about template
--debug -d print debug information
--arg -a argument
Examples:
Render JSON file to STDOUT or file:
$ aspect path/to/fixture.json path/to/template.tmpl
$ aspect path/to/fixture.json path/to/template.tmpl >path/to/result.txt
Render data from STDIN (using -):
$ aspect - path/to/template.tmpl
Read template from STDIN (using -):
$ aspect path/to/fixture.json -
Lint the template:
$ aspect --lint path/to/template.tmpl
]]
end
local function verbose(...)
io.stderr:write("[DEBUG] " .. dump(...) .. "\n")
end
if options.debug then
verbose("command options", options)
end
if options.escape then
autoescape = true
end
local template_file, template, tpl, loader = paths[1], nil, nil, nil
--- @type aspect.compiler
local build
local data_file, data = paths[2], nil
--- Select template
local output
local templater = aspect.new()
if template_file == '-' then
if options.debug then
verbose("loading template from STDIN")
end
template, err = io.stdin:read("*a")
template_file = "STDIN"
else
if options.debug then
verbose("loading template from " .. tostring(template_file))
end
template, err = cli.read_file(template_file)
end
if err then
return 1, "[ERROR] Failed to load template: " .. err
end
if options.debug then
verbose("template (" .. string.len(template) .. " bytes): ", numerate(template))
end
if options.include then
if options.debug then
verbose("enable loader from " .. tostring(options.include))
end
loader = fs_loader.new(options.include)
end
templater.loader = function(name, t)
if name == template_file then
return template
elseif loader then
return loader(name, t)
end
end
tpl, err, build = templater:load(template_file)
if err then
return 1, "[ERROR] Failed to load and compile template: " .. tostring(err)
end
if options.debug then
verbose("template code: ", numerate(build:get_code()))
end
if options.lint then
return 0
end
if options.dump then
local out = {}
table.insert(out, "REFS:" .. dump(build.ctx.tpl_refs))
table.insert(out, "VARS:" .. dump(build.ctx.var_refs))
if next(build.blocks) then
for bn, b in pairs(build.blocks) do
table.insert(out, "BLOCK: " .. bn)
table.insert(out, " DESCR: " .. (b.desc or "<none>"))
table.insert(out, " LOCATION: "
.. b.start_line .. "-" .. b.end_line .. " lines or " .. b.start_pos .. "-" .. b.end_pos .. " bytes")
table.insert(out, " REFS: " .. dump(b.ctx.tpl_refs))
table.insert(out, " VARS " .. dump(b.ctx.var_refs))
end
end
if next(build.macros) then
for mn, m in pairs(build.macros) do
table.insert(out, "MACRO: " .. mn)
table.insert(out, " DESCR: " .. (m.desc or "<none>"))
table.insert(out, " LOCATION: "
.. m.start_line .. "-" .. m.end_line .. " lines")
table.insert(out, " ARGS: " .. dump(m.args))
table.insert(out, " REFS: " .. dump(m.ctx.tpl_refs))
table.insert(out, " VARS " .. dump(m.ctx.var_refs))
end
end
--if next(build.refs) then
-- table.insert(out, "REFS:")
-- for tn, t in pairs(build.refs) do
-- table.insert(out, " " .. tn .. ": " .. table.concat(get_keys(t), ", "))
-- end
--end
--for bn, b in pairs(build.blocks) do
-- table.insert(out, "BLOCK: " .. bn)
-- table.insert(out, " lines: " .. b.start_line .. "-" .. b.end_line)
-- table.insert(out, " use parent(): " .. (b.parent and 'yes' or 'no'))
-- if next(b.used_vars) then
-- table.insert(out, " used variables: ")
-- for vn, v in pairs(b.used_vars) do
-- table.insert(out, " " .. vn .. ":")
-- local keys = get_keys(v.keys)
-- if #keys > 0 then
-- table.insert(out, " keys: " .. table.concat(keys, ", "))
-- end
-- table.insert(out, " where: ")
-- for _, w in pairs(v.where) do
-- table.insert(out, " - " .. template_file .. ":" .. w.line .. " in tag " .. (w.tag or "--"))
-- end
-- end
-- end
-- --table.insert(out, "Block " .. bn .. ", lines " .. b.start_line .. "-" .. b.end_line .. ": " .. dump(b))
--end
--for mn, m in pairs(build.blocks) do
--
--end
table.insert(out, "TEMPLATE:")
table.insert(out, numerate(template, " "))
table.insert(out, "CODE:")
table.insert(out, numerate(build:get_code(), " "))
return 0, table.concat(out, "\n")
end
--- Read data
if data_file == '-' and not template then
if options.debug then
verbose("reading data from STDIN")
end
data, err = io.stdin:read("*a")
data_file = "STDIN"
else
if options.debug then
verbose("reading data from " .. tostring(data_file))
end
data, err = cli.read_file(data_file)
end
if err then
return 1, "[ERROR] Failed to read data: " .. err
end
if options.debug then
verbose("data (" .. string.len(data) .. " bytes): ", data)
end
if not json.decode then
return 1, "[ERROR] " .. json.error
end
data, err = json.decode(data)
if not data then
return 1, "[ERROR] Failed to decode data: " .. tostring(err)
end
output, err = templater:render(template_file, data, {
autoescape = autoescape
})
if err then
return 1, "[ERROR] Failed to render template: " .. tostring(err)
end
return 0, tostring(output)
end
function cli.shell()
local code, result = cli.run(_G.arg or {})
if code == 0 then
if result then
io.stdout:write(result)
end
os.exit(0)
else
io.stderr:write(result .. "\n")
os.exit(code)
end
end
return cli