Refactoring: nested functions
This commit is contained in:
parent
8bdc6035a8
commit
15be6fca8e
|
@ -6274,7 +6274,6 @@ end
|
|||
|
||||
-- Gets OS short name
|
||||
-- It is just intented to know if it is Linux, macOS or Windows.
|
||||
-- Sorry
|
||||
-- @return string: "linux" or "bsd" or "macos" or "windows"
|
||||
function os.uname()
|
||||
local status, output = io.try("uname")
|
||||
|
@ -6312,6 +6311,20 @@ function os.lang()
|
|||
return "en", "US", "UTF-8"
|
||||
end
|
||||
|
||||
-- Gets an equivalency table between ASCII and Unicode
|
||||
-- Note: filled on demand.
|
||||
-- @return table: ASCII-Unicode table equivalency
|
||||
function utf8.table()
|
||||
return {
|
||||
["a"] = {"á", "à", "ä"},
|
||||
["e"] = {"é", "è", "ë"},
|
||||
["i"] = {"í", "ì", "ï"},
|
||||
["o"] = {"ó", "ò", "ö"},
|
||||
["u"] = {"ú", "ù", "ü"},
|
||||
["n"] = {"ñ"},
|
||||
}
|
||||
end
|
||||
|
||||
-- Checks if string is empty
|
||||
-- @return boolean: Empty or not
|
||||
function string:isempty()
|
||||
|
@ -6324,6 +6337,22 @@ function string:linearize()
|
|||
return self:gsub("\n", "\\n")
|
||||
end
|
||||
|
||||
-- Normalizes string
|
||||
-- @return string: Normalized string
|
||||
function string:normalize()
|
||||
self = self:lower()
|
||||
for newchar, chars in pairs(utf8.table()) do
|
||||
for _, oldchar in ipairs(chars) do
|
||||
self = self:gsub(oldchar, newchar)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- Adds indent
|
||||
-- @param num number: Indent size, 2 by default
|
||||
-- @param char string: Indent character, space by default
|
||||
-- @return strin: Indented string
|
||||
function string:indent(num, char)
|
||||
num = num or 2
|
||||
char = char or " "
|
||||
|
@ -6331,20 +6360,34 @@ function string:indent(num, char)
|
|||
return char .. self:gsub("\n", "\n" .. char)
|
||||
end
|
||||
|
||||
-- Splits the string
|
||||
-- Note: only support splitting by one character.
|
||||
-- Could be solved by slicing with find.
|
||||
-- @param sep string: String separator, space by default
|
||||
-- @return table: String matches
|
||||
function string:split(sep)
|
||||
sep = sep or "%s+"
|
||||
local parts = {}
|
||||
for part in self:gmatch("([^" .. sep .. "]+)") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
-- Removes spaces at the beginning of the string
|
||||
-- @return string: left stripped string
|
||||
-- @return string: Left stripped string
|
||||
function string:lstrip()
|
||||
return self:gsub("^%s+", "")
|
||||
end
|
||||
|
||||
-- Removes spaces at the end of the string
|
||||
-- @return string: right stripped string
|
||||
-- @return string: Right stripped string
|
||||
function string:rstrip()
|
||||
return self:gsub("%s+$", "")
|
||||
end
|
||||
|
||||
-- Removes spaces at the beginning and at the end of the string
|
||||
-- @return string: stripped string
|
||||
-- @return string: Stripped string
|
||||
function string:strip()
|
||||
self = self:lstrip():rstrip()
|
||||
return self
|
||||
|
@ -6384,14 +6427,26 @@ function string:isdir()
|
|||
return false
|
||||
end
|
||||
|
||||
-- Reads file content
|
||||
-- @return string or nil: File content or nil
|
||||
function string:read_text()
|
||||
-- Reads file content as string
|
||||
-- @return string or nil: File as string or nil
|
||||
function string:readtext()
|
||||
if self:exists() then
|
||||
return io.open(self):read("*a")
|
||||
end
|
||||
end
|
||||
|
||||
-- Read file content as lines
|
||||
-- @return table: Table of file lines or nil
|
||||
function string:readlines()
|
||||
local lines = {}
|
||||
if self:exists() then
|
||||
for line in io.open(self):lines() do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Gets file without suffix
|
||||
-- @return string: File wihtout suffix
|
||||
function string:stem()
|
||||
|
@ -6399,7 +6454,7 @@ function string:stem()
|
|||
end
|
||||
|
||||
-- Gets file extensions
|
||||
-- @return table: list of file extensions
|
||||
-- @return table: List of file extensions
|
||||
function string:suffixes()
|
||||
local suffixes = {}
|
||||
for suffix in self:gmatch("%.%a+") do
|
||||
|
@ -6409,7 +6464,7 @@ function string:suffixes()
|
|||
end
|
||||
|
||||
-- Gets file final extension
|
||||
-- @return string: final file extension
|
||||
-- @return string: Final file extension
|
||||
function string:suffix()
|
||||
local suffixes = self:suffixes()
|
||||
if suffixes[#suffixes] then
|
||||
|
@ -6445,7 +6500,7 @@ if pandoc ~= nil then
|
|||
-- @return string: Output file content
|
||||
function pandoc.convert(ifile, oformat, ofile, iformat)
|
||||
iformat = (iformat == nil and pandoc.getext(ifile) or iformat)
|
||||
local doc = pandoc.write(pandoc.read(ifile:read_text(), iformat), oformat)
|
||||
local doc = pandoc.write(pandoc.read(ifile:readtext(), iformat), oformat)
|
||||
if ofile ~= nil then
|
||||
local eol = (os:isunix() and "\n" or "\r\n")
|
||||
io.open(ofile, "w"):write(doc, eol):close()
|
||||
|
@ -6492,27 +6547,32 @@ lit.metastruct = {
|
|||
}
|
||||
|
||||
-- Grammars
|
||||
lit.g = {}
|
||||
function lit.g(name)
|
||||
|
||||
-- Lexical elements
|
||||
lit.newline = lpeg.P"\r"^-1 * lpeg.P"\n"
|
||||
lit.space = lpeg.S" \t"
|
||||
lit.anyspace = lpeg.S" \t\r\n"
|
||||
lit.spot = (1 - lit.anyspace)
|
||||
lit.any = (lit.spot + lit.space)
|
||||
lit.yamlheader = lit.space^0 * lpeg.P"---" * lit.space^0 * lit.newline
|
||||
lit.yamlfooter = lit.space^0 * lpeg.P"..." * lit.space^0 * lit.newline^-1
|
||||
lit.yamlbody = -lit.yamlfooter * lit.any^0 * lit.newline
|
||||
lit.id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0
|
||||
lit.ref = lpeg.P"#" * lit.id
|
||||
-- Lexical elements
|
||||
local newline = lpeg.P"\r"^-1 * lpeg.P"\n"
|
||||
local space = lpeg.S" \t"
|
||||
local anyspace = lpeg.S" \t\r\n"
|
||||
local spot = (1 - anyspace)
|
||||
local any = (spot + space)
|
||||
local yamlheader = space^0 * lpeg.P"---" * space^0 * newline
|
||||
local yamlfooter = space^0 * lpeg.P"..." * space^0 * newline^-1
|
||||
local yamlbody = -yamlfooter * any^0 * newline
|
||||
local id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0
|
||||
local ref = lpeg.P"#" * id
|
||||
|
||||
-- Blocks grammar
|
||||
lit.g.block = lpeg.P {
|
||||
"Block";
|
||||
Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name");
|
||||
YAML = lpeg.C(lit.yamlheader * lit.yamlbody^0 * lit.yamlfooter);
|
||||
Code = lpeg.C((lit.any + lit.newline)^0);
|
||||
}
|
||||
local grammars = {
|
||||
-- Blocks grammar
|
||||
["block"] = lpeg.P {
|
||||
"Block";
|
||||
Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name");
|
||||
YAML = lpeg.C(yamlheader * yamlbody^0 * yamlfooter);
|
||||
Code = lpeg.C((any + newline)^0);
|
||||
},
|
||||
}
|
||||
|
||||
return grammars[name]
|
||||
end
|
||||
|
||||
-- Evals Lisp code
|
||||
--[[
|
||||
|
@ -6531,74 +6591,80 @@ function lit.eval(code)
|
|||
end
|
||||
]]--
|
||||
|
||||
function lit.stringify_quoted(quoted)
|
||||
local quote = (quoted.quotetype == SingleQuoted and '"' or "'")
|
||||
quoted = pandoc.utils.stringify(quoted.content)
|
||||
return pandoc.Str(quote .. quoted .. quote)
|
||||
end
|
||||
|
||||
function lit.stringify_rawinline(rawinline)
|
||||
local str = rawinline.text
|
||||
str = str:gsub("\\n", "\n"):gsub("\\t", "\t")
|
||||
return pandoc.Str(str)
|
||||
end
|
||||
|
||||
function lit.stringify(content)
|
||||
if pandoc.utils.type(content) == "Inlines" then
|
||||
return pandoc.utils.stringify(content:walk {
|
||||
Quoted = function(e) return lit.stringify_quoted(e) end,
|
||||
RawInline = function(e) return lit.stringify_rawinline(e) end,
|
||||
Inline = function(inline) return pandoc.utils.stringify(inline) end,
|
||||
})
|
||||
else
|
||||
return pandoc.utils.stringify(content)
|
||||
end
|
||||
end
|
||||
|
||||
function lit.string2other(str)
|
||||
if str == "true" or str == "false" then
|
||||
return str == "true"
|
||||
-- WARN "1" always num; cfr. https://pandoc.org/lua-filters.html#type-metavalue
|
||||
elseif tonumber(str) then
|
||||
return tonumber(str)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
function lit.meta2table(meta)
|
||||
|
||||
local function stringify(content)
|
||||
|
||||
local function stringify_quoted(quoted)
|
||||
local quote = (quoted.quotetype == SingleQuoted and '"' or "'")
|
||||
quoted = pandoc.utils.stringify(quoted.content)
|
||||
return pandoc.Str(quote .. quoted .. quote)
|
||||
end
|
||||
|
||||
local function stringify_rawinline(rawinline)
|
||||
local str = rawinline.text
|
||||
str = str:gsub("\\n", "\n"):gsub("\\t", "\t")
|
||||
return pandoc.Str(str)
|
||||
end
|
||||
|
||||
if pandoc.utils.type(content) == "Inlines" then
|
||||
return pandoc.utils.stringify(content:walk {
|
||||
Quoted = function(e) return stringify_quoted(e) end,
|
||||
RawInline = function(e) return stringify_rawinline(e) end,
|
||||
Inline = function(inline) return pandoc.utils.stringify(inline) end,
|
||||
})
|
||||
else
|
||||
return pandoc.utils.stringify(content)
|
||||
end
|
||||
end
|
||||
|
||||
local function string2other(str)
|
||||
if str == "true" or str == "false" then
|
||||
return str == "true"
|
||||
-- Note that "1" always num;
|
||||
-- cfr. https://pandoc.org/lua-filters.html#type-metavalue
|
||||
elseif tonumber(str) then
|
||||
return tonumber(str)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(meta) do
|
||||
if pandoc.utils.type(v) == "table" then
|
||||
meta[k] = lit.meta2table(v)
|
||||
else
|
||||
meta[k] = lit.string2other(lit.stringify(v))
|
||||
meta[k] = string2other(stringify(v))
|
||||
end
|
||||
end
|
||||
return meta
|
||||
end
|
||||
|
||||
function lit.debuglevel(str)
|
||||
if str == "ERROR" then
|
||||
lit.status = false
|
||||
return 2
|
||||
elseif str == "WARNING" then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
function lit.puts(key, ...)
|
||||
|
||||
function lit.setmsg(msg, ...)
|
||||
local lang = os.lang()
|
||||
msg = (msg[lang] ~= nil and msg[lang] or msg)
|
||||
for i, str in ipairs({...}) do
|
||||
msg = msg:gsub("#" .. i, str)
|
||||
local function debuglevel(str)
|
||||
if str == "ERROR" then
|
||||
lit.status = false
|
||||
return 2
|
||||
elseif str == "WARNING" then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
function lit.getmsg(key, ...)
|
||||
local msg = lit.meta2table(pandoc.read([[---
|
||||
local function getmsg(key, ...)
|
||||
|
||||
local function setmsg(msg, ...)
|
||||
local lang = os.lang()
|
||||
msg = (msg[lang] ~= nil and msg[lang] or msg)
|
||||
for i, str in ipairs({...}) do
|
||||
msg = msg:gsub("#" .. i, str)
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
local msg = lit.meta2table(pandoc.read([[---
|
||||
INFO:
|
||||
parsing:
|
||||
en: Parsing:\n#1
|
||||
|
@ -6634,97 +6700,24 @@ ERROR:
|
|||
es: YAML inválido
|
||||
...
|
||||
]]).meta)
|
||||
local levelname, level = "INFO", 0
|
||||
for mtype, msgs in pairs(msg) do
|
||||
if msgs[key] ~= nil then
|
||||
key = lit.setmsg(msgs[key], ...)
|
||||
levelname, level = mtype, lit.debuglevel(mtype)
|
||||
break
|
||||
local levelname, level = "INFO", 0
|
||||
for mtype, msgs in pairs(msg) do
|
||||
if msgs[key] ~= nil then
|
||||
key = setmsg(msgs[key], ...)
|
||||
levelname, level = mtype, debuglevel(mtype)
|
||||
break
|
||||
end
|
||||
end
|
||||
return key, levelname, level
|
||||
end
|
||||
return key, levelname, level
|
||||
end
|
||||
|
||||
function lit.puts(key, ...)
|
||||
local verbosity = lit.debuglevel(PANDOC_STATE.verbosity)
|
||||
local msg, levelname, level = lit.getmsg(key, ...)
|
||||
local verbosity = debuglevel(PANDOC_STATE.verbosity)
|
||||
local msg, levelname, level = getmsg(key, ...)
|
||||
if level >= verbosity then
|
||||
print("[" .. levelname .. "] [LIT] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
function lit.getmetaval(meta, key)
|
||||
local mtype = pandoc.utils.type(meta[key])
|
||||
local mval = meta[key]
|
||||
if mtype == "Inlines" then
|
||||
mval = pandoc.utils.stringify(mval)
|
||||
mtype = pandoc.utils.type(mval)
|
||||
end
|
||||
return mval, mtype
|
||||
end
|
||||
|
||||
function lit.checkextra(meta)
|
||||
for key, val in pairs(meta) do
|
||||
local missing1 = lit.metastruct["mandatory"][key] == nil
|
||||
local missing2 = lit.metastruct["optional"][key] == nil
|
||||
local mval = lit.getmetaval(meta, key)
|
||||
if missing1 and missing2 then
|
||||
lit.puts("invalid_key", key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function lit.checkmeta_type(type, meta, key)
|
||||
local mval, mtype = lit.getmetaval(meta, key)
|
||||
if type ~= mtype then
|
||||
if type == "path" then
|
||||
if not(pandoc.path.directory(mval):isdir()) then
|
||||
lit.puts("invalid_path", mval, key)
|
||||
end
|
||||
else
|
||||
lit.puts("invalid_type", mtype, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function lit.checkmeta_match(table, mval, key)
|
||||
for _, pattern in pairs(table) do
|
||||
if mval:match("^" .. pattern .. "$") then
|
||||
return ""
|
||||
end
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
function lit.checkmeta_table(table, meta, key)
|
||||
local mval = lit.getmetaval(meta, key)
|
||||
local err = lit.checkmeta_match(table, mval, key)
|
||||
if not(err:isempty()) then
|
||||
lit.puts("invalid_value", mval, err)
|
||||
end
|
||||
end
|
||||
|
||||
function lit.checkmeta(meta, kind)
|
||||
for key, val in pairs(lit.metastruct[kind]) do
|
||||
if meta[key] == nil and kind == "mandatory" then
|
||||
lit.puts("no_key", key)
|
||||
elseif meta[key] and type(val) == "table" then
|
||||
lit.checkmeta_table(val, meta, key)
|
||||
elseif meta[key] then
|
||||
lit.checkmeta_type(val, meta, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function lit.setmeta(meta)
|
||||
lit.checkmeta(meta, "mandatory")
|
||||
lit.checkmeta(meta, "optional")
|
||||
lit.checkextra(meta)
|
||||
-- TODO checks for 2) duplicates
|
||||
-- Set defaults if lit.status = true
|
||||
return meta
|
||||
end
|
||||
|
||||
-- TODO
|
||||
function lit.assert(e, status)
|
||||
if status == false then
|
||||
|
@ -6734,32 +6727,98 @@ function lit.assert(e, status)
|
|||
return e
|
||||
end
|
||||
|
||||
function lit.parseyaml(rawyaml)
|
||||
-- TODO io.try
|
||||
local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1")
|
||||
if yaml:read("*l"):sub(1, 1) == "{" then
|
||||
yaml = pandoc.read(rawyaml).meta
|
||||
if pandoc.utils.stringify(yaml):isempty() then
|
||||
lit.puts("yaml_empty")
|
||||
else
|
||||
return lit.setmeta(yaml)
|
||||
end
|
||||
else
|
||||
lit.puts("yaml_invalid")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function lit.parseblock(parsed)
|
||||
lit.puts("parsing", table.concat(parsed, ""):indent())
|
||||
local yaml = lit.parseyaml(parsed[1])
|
||||
-- TODO: parsecode
|
||||
return "TODO"
|
||||
end
|
||||
|
||||
function lit.getblock(codeblock)
|
||||
local parsed = lpeg.match(lit.g.block, codeblock.text)
|
||||
parsed = (parsed ~= nil and lit.parseblock(parsed) or parsed)
|
||||
|
||||
local function checkextra(meta)
|
||||
for key, val in pairs(meta) do
|
||||
local missing1 = lit.metastruct["mandatory"][key] == nil
|
||||
local missing2 = lit.metastruct["optional"][key] == nil
|
||||
if missing1 and missing2 then
|
||||
lit.puts("invalid_key", key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function checkmeta(meta, kind)
|
||||
|
||||
local function checktype(type1, meta, key)
|
||||
local val = meta[key]
|
||||
local type2 = type(val)
|
||||
if type1 ~= type2 then
|
||||
if type1 == "path" then
|
||||
if not(pandoc.path.directory(val):isdir()) then
|
||||
lit.puts("invalid_path", val, key)
|
||||
end
|
||||
else
|
||||
lit.puts("invalid_type", type2, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function checktable(table, meta, key)
|
||||
|
||||
local function checkmatch(table, val, key)
|
||||
for _, pattern in pairs(table) do
|
||||
if val:match("^" .. pattern .. "$") then
|
||||
return ""
|
||||
end
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
local val = meta[key]
|
||||
local err = checkmatch(table, val, key)
|
||||
if not(err:isempty()) then
|
||||
lit.puts("invalid_value", val, err)
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(lit.metastruct[kind]) do
|
||||
if meta[key] == nil and kind == "mandatory" then
|
||||
lit.puts("no_key", key)
|
||||
elseif meta[key] and type(val) == "table" then
|
||||
checktable(val, meta, key)
|
||||
elseif meta[key] then
|
||||
checktype(val, meta, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function setmeta(meta)
|
||||
local tmeta = lit.meta2table(meta)
|
||||
checkmeta(tmeta, "mandatory")
|
||||
checkmeta(tmeta, "optional")
|
||||
checkextra(tmeta)
|
||||
-- TODO checks for 2) duplicates
|
||||
-- Set defaults if lit.status = true
|
||||
return meta
|
||||
end
|
||||
|
||||
local function parseyaml(rawyaml)
|
||||
-- TODO io.try
|
||||
local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1")
|
||||
if yaml:read("*l"):sub(1, 1) == "{" then
|
||||
yaml = pandoc.read(rawyaml).meta
|
||||
if pandoc.utils.stringify(yaml):isempty() then
|
||||
lit.puts("yaml_empty")
|
||||
else
|
||||
return setmeta(yaml)
|
||||
end
|
||||
else
|
||||
lit.puts("yaml_invalid")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function inspect(parsed)
|
||||
lit.puts("parsing", table.concat(parsed, ""):indent())
|
||||
local yaml = parseyaml(parsed[1])
|
||||
-- TODO: parsecode
|
||||
return "TODO"
|
||||
end
|
||||
|
||||
local parsed = lpeg.match(lit.g("block"), codeblock.text)
|
||||
parsed = (parsed ~= nil and inspect(parsed) or parsed)
|
||||
return codeblock, lit.status
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ end
|
|||
|
||||
-- Gets OS short name
|
||||
-- It is just intented to know if it is Linux, macOS or Windows.
|
||||
-- Sorry
|
||||
-- @return string: "linux" or "bsd" or "macos" or "windows"
|
||||
function os.uname()
|
||||
local status, output = io.try("uname")
|
||||
|
@ -51,6 +50,20 @@ function os.lang()
|
|||
return "en", "US", "UTF-8"
|
||||
end
|
||||
|
||||
-- Gets an equivalency table between ASCII and Unicode
|
||||
-- Note: filled on demand.
|
||||
-- @return table: ASCII-Unicode table equivalency
|
||||
function utf8.table()
|
||||
return {
|
||||
["a"] = {"á", "à", "ä"},
|
||||
["e"] = {"é", "è", "ë"},
|
||||
["i"] = {"í", "ì", "ï"},
|
||||
["o"] = {"ó", "ò", "ö"},
|
||||
["u"] = {"ú", "ù", "ü"},
|
||||
["n"] = {"ñ"},
|
||||
}
|
||||
end
|
||||
|
||||
-- Checks if string is empty
|
||||
-- @return boolean: Empty or not
|
||||
function string:isempty()
|
||||
|
@ -63,6 +76,22 @@ function string:linearize()
|
|||
return self:gsub("\n", "\\n")
|
||||
end
|
||||
|
||||
-- Normalizes string
|
||||
-- @return string: Normalized string
|
||||
function string:normalize()
|
||||
self = self:lower()
|
||||
for newchar, chars in pairs(utf8.table()) do
|
||||
for _, oldchar in ipairs(chars) do
|
||||
self = self:gsub(oldchar, newchar)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- Adds indent
|
||||
-- @param num number: Indent size, 2 by default
|
||||
-- @param char string: Indent character, space by default
|
||||
-- @return strin: Indented string
|
||||
function string:indent(num, char)
|
||||
num = num or 2
|
||||
char = char or " "
|
||||
|
@ -70,20 +99,34 @@ function string:indent(num, char)
|
|||
return char .. self:gsub("\n", "\n" .. char)
|
||||
end
|
||||
|
||||
-- Splits the string
|
||||
-- Note: only support splitting by one character.
|
||||
-- Could be solved by slicing with find.
|
||||
-- @param sep string: String separator, space by default
|
||||
-- @return table: String matches
|
||||
function string:split(sep)
|
||||
sep = sep or "%s+"
|
||||
local parts = {}
|
||||
for part in self:gmatch("([^" .. sep .. "]+)") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
-- Removes spaces at the beginning of the string
|
||||
-- @return string: left stripped string
|
||||
-- @return string: Left stripped string
|
||||
function string:lstrip()
|
||||
return self:gsub("^%s+", "")
|
||||
end
|
||||
|
||||
-- Removes spaces at the end of the string
|
||||
-- @return string: right stripped string
|
||||
-- @return string: Right stripped string
|
||||
function string:rstrip()
|
||||
return self:gsub("%s+$", "")
|
||||
end
|
||||
|
||||
-- Removes spaces at the beginning and at the end of the string
|
||||
-- @return string: stripped string
|
||||
-- @return string: Stripped string
|
||||
function string:strip()
|
||||
self = self:lstrip():rstrip()
|
||||
return self
|
||||
|
@ -123,14 +166,26 @@ function string:isdir()
|
|||
return false
|
||||
end
|
||||
|
||||
-- Reads file content
|
||||
-- @return string or nil: File content or nil
|
||||
function string:read_text()
|
||||
-- Reads file content as string
|
||||
-- @return string or nil: File as string or nil
|
||||
function string:readtext()
|
||||
if self:exists() then
|
||||
return io.open(self):read("*a")
|
||||
end
|
||||
end
|
||||
|
||||
-- Read file content as lines
|
||||
-- @return table: Table of file lines or nil
|
||||
function string:readlines()
|
||||
local lines = {}
|
||||
if self:exists() then
|
||||
for line in io.open(self):lines() do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Gets file without suffix
|
||||
-- @return string: File wihtout suffix
|
||||
function string:stem()
|
||||
|
@ -138,7 +193,7 @@ function string:stem()
|
|||
end
|
||||
|
||||
-- Gets file extensions
|
||||
-- @return table: list of file extensions
|
||||
-- @return table: List of file extensions
|
||||
function string:suffixes()
|
||||
local suffixes = {}
|
||||
for suffix in self:gmatch("%.%a+") do
|
||||
|
@ -148,7 +203,7 @@ function string:suffixes()
|
|||
end
|
||||
|
||||
-- Gets file final extension
|
||||
-- @return string: final file extension
|
||||
-- @return string: Final file extension
|
||||
function string:suffix()
|
||||
local suffixes = self:suffixes()
|
||||
if suffixes[#suffixes] then
|
||||
|
@ -184,7 +239,7 @@ if pandoc ~= nil then
|
|||
-- @return string: Output file content
|
||||
function pandoc.convert(ifile, oformat, ofile, iformat)
|
||||
iformat = (iformat == nil and pandoc.getext(ifile) or iformat)
|
||||
local doc = pandoc.write(pandoc.read(ifile:read_text(), iformat), oformat)
|
||||
local doc = pandoc.write(pandoc.read(ifile:readtext(), iformat), oformat)
|
||||
if ofile ~= nil then
|
||||
local eol = (os:isunix() and "\n" or "\r\n")
|
||||
io.open(ofile, "w"):write(doc, eol):close()
|
||||
|
|
|
@ -9,7 +9,7 @@ local function make_dist()
|
|||
-- Chomps file
|
||||
local function chomp(str, without)
|
||||
local without = without or false
|
||||
str = string.read_text(str):strip()
|
||||
str = string.readtext(str):strip()
|
||||
if without then
|
||||
return str:gsub("\n[^\n]+$", "")
|
||||
end
|
||||
|
@ -24,7 +24,7 @@ local function make_dist()
|
|||
local pan = chomp("src/pandoc.lua")
|
||||
local nat = chomp("src/natural.lua", true)
|
||||
local lit = chomp("src/literate.lua", true)
|
||||
local msg = ("src/locale.yaml"):read_text()
|
||||
local msg = ("src/locale.yaml"):readtext()
|
||||
local license = string.strip([[
|
||||
Computable Pandoc & Fennel Bundle:
|
||||
A Pandoc filter for literate and natural programming
|
||||
|
|
|
@ -54,7 +54,7 @@ local function getresult(file, f, v, t)
|
|||
local tmp, name = "tmp.json", pandoc.path.filename(file)
|
||||
local json = pandoc.path.join({"tests", "asts", name .. ".json"})
|
||||
local ok, out = io.try(getcmd("pandoc"), f, v, t, "-t json", "-o", tmp, file)
|
||||
local json1, json2 = tmp:read_text(), json:read_text()
|
||||
local json1, json2 = tmp:readtext(), json:readtext()
|
||||
os.remove("tmp.json")
|
||||
if (ok and json2 == nil) or (ok and json1 == json2) then
|
||||
return "pass", out:strip()
|
||||
|
|
378
src/literate.lua
378
src/literate.lua
|
@ -29,27 +29,32 @@ lit.metastruct = {
|
|||
}
|
||||
|
||||
-- Grammars
|
||||
lit.g = {}
|
||||
function lit.g(name)
|
||||
|
||||
-- Lexical elements
|
||||
lit.newline = lpeg.P"\r"^-1 * lpeg.P"\n"
|
||||
lit.space = lpeg.S" \t"
|
||||
lit.anyspace = lpeg.S" \t\r\n"
|
||||
lit.spot = (1 - lit.anyspace)
|
||||
lit.any = (lit.spot + lit.space)
|
||||
lit.yamlheader = lit.space^0 * lpeg.P"---" * lit.space^0 * lit.newline
|
||||
lit.yamlfooter = lit.space^0 * lpeg.P"..." * lit.space^0 * lit.newline^-1
|
||||
lit.yamlbody = -lit.yamlfooter * lit.any^0 * lit.newline
|
||||
lit.id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0
|
||||
lit.ref = lpeg.P"#" * lit.id
|
||||
-- Lexical elements
|
||||
local newline = lpeg.P"\r"^-1 * lpeg.P"\n"
|
||||
local space = lpeg.S" \t"
|
||||
local anyspace = lpeg.S" \t\r\n"
|
||||
local spot = (1 - anyspace)
|
||||
local any = (spot + space)
|
||||
local yamlheader = space^0 * lpeg.P"---" * space^0 * newline
|
||||
local yamlfooter = space^0 * lpeg.P"..." * space^0 * newline^-1
|
||||
local yamlbody = -yamlfooter * any^0 * newline
|
||||
local id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0
|
||||
local ref = lpeg.P"#" * id
|
||||
|
||||
-- Blocks grammar
|
||||
lit.g.block = lpeg.P {
|
||||
"Block";
|
||||
Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name");
|
||||
YAML = lpeg.C(lit.yamlheader * lit.yamlbody^0 * lit.yamlfooter);
|
||||
Code = lpeg.C((lit.any + lit.newline)^0);
|
||||
}
|
||||
local grammars = {
|
||||
-- Blocks grammar
|
||||
["block"] = lpeg.P {
|
||||
"Block";
|
||||
Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name");
|
||||
YAML = lpeg.C(yamlheader * yamlbody^0 * yamlfooter);
|
||||
Code = lpeg.C((any + newline)^0);
|
||||
},
|
||||
}
|
||||
|
||||
return grammars[name]
|
||||
end
|
||||
|
||||
-- Evals Lisp code
|
||||
--[[
|
||||
|
@ -68,165 +73,98 @@ function lit.eval(code)
|
|||
end
|
||||
]]--
|
||||
|
||||
function lit.stringify_quoted(quoted)
|
||||
local quote = (quoted.quotetype == SingleQuoted and '"' or "'")
|
||||
quoted = pandoc.utils.stringify(quoted.content)
|
||||
return pandoc.Str(quote .. quoted .. quote)
|
||||
end
|
||||
|
||||
function lit.stringify_rawinline(rawinline)
|
||||
local str = rawinline.text
|
||||
str = str:gsub("\\n", "\n"):gsub("\\t", "\t")
|
||||
return pandoc.Str(str)
|
||||
end
|
||||
|
||||
function lit.stringify(content)
|
||||
if pandoc.utils.type(content) == "Inlines" then
|
||||
return pandoc.utils.stringify(content:walk {
|
||||
Quoted = function(e) return lit.stringify_quoted(e) end,
|
||||
RawInline = function(e) return lit.stringify_rawinline(e) end,
|
||||
Inline = function(inline) return pandoc.utils.stringify(inline) end,
|
||||
})
|
||||
else
|
||||
return pandoc.utils.stringify(content)
|
||||
end
|
||||
end
|
||||
|
||||
function lit.string2other(str)
|
||||
if str == "true" or str == "false" then
|
||||
return str == "true"
|
||||
-- WARN "1" always num; cfr. https://pandoc.org/lua-filters.html#type-metavalue
|
||||
elseif tonumber(str) then
|
||||
return tonumber(str)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
function lit.meta2table(meta)
|
||||
|
||||
local function stringify(content)
|
||||
|
||||
local function stringify_quoted(quoted)
|
||||
local quote = (quoted.quotetype == SingleQuoted and '"' or "'")
|
||||
quoted = pandoc.utils.stringify(quoted.content)
|
||||
return pandoc.Str(quote .. quoted .. quote)
|
||||
end
|
||||
|
||||
local function stringify_rawinline(rawinline)
|
||||
local str = rawinline.text
|
||||
str = str:gsub("\\n", "\n"):gsub("\\t", "\t")
|
||||
return pandoc.Str(str)
|
||||
end
|
||||
|
||||
if pandoc.utils.type(content) == "Inlines" then
|
||||
return pandoc.utils.stringify(content:walk {
|
||||
Quoted = function(e) return stringify_quoted(e) end,
|
||||
RawInline = function(e) return stringify_rawinline(e) end,
|
||||
Inline = function(inline) return pandoc.utils.stringify(inline) end,
|
||||
})
|
||||
else
|
||||
return pandoc.utils.stringify(content)
|
||||
end
|
||||
end
|
||||
|
||||
local function string2other(str)
|
||||
if str == "true" or str == "false" then
|
||||
return str == "true"
|
||||
-- Note that "1" always num;
|
||||
-- cfr. https://pandoc.org/lua-filters.html#type-metavalue
|
||||
elseif tonumber(str) then
|
||||
return tonumber(str)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(meta) do
|
||||
if pandoc.utils.type(v) == "table" then
|
||||
meta[k] = lit.meta2table(v)
|
||||
else
|
||||
meta[k] = lit.string2other(lit.stringify(v))
|
||||
meta[k] = string2other(stringify(v))
|
||||
end
|
||||
end
|
||||
return meta
|
||||
end
|
||||
|
||||
function lit.debuglevel(str)
|
||||
if str == "ERROR" then
|
||||
lit.status = false
|
||||
return 2
|
||||
elseif str == "WARNING" then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function lit.setmsg(msg, ...)
|
||||
local lang = os.lang()
|
||||
msg = (msg[lang] ~= nil and msg[lang] or msg)
|
||||
for i, str in ipairs({...}) do
|
||||
msg = msg:gsub("#" .. i, str)
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
function lit.getmsg(key, ...)
|
||||
local msg = lit.meta2table(pandoc.read([[#locale()]]).meta)
|
||||
local levelname, level = "INFO", 0
|
||||
for mtype, msgs in pairs(msg) do
|
||||
if msgs[key] ~= nil then
|
||||
key = lit.setmsg(msgs[key], ...)
|
||||
levelname, level = mtype, lit.debuglevel(mtype)
|
||||
break
|
||||
end
|
||||
end
|
||||
return key, levelname, level
|
||||
end
|
||||
|
||||
function lit.puts(key, ...)
|
||||
local verbosity = lit.debuglevel(PANDOC_STATE.verbosity)
|
||||
local msg, levelname, level = lit.getmsg(key, ...)
|
||||
|
||||
local function debuglevel(str)
|
||||
if str == "ERROR" then
|
||||
lit.status = false
|
||||
return 2
|
||||
elseif str == "WARNING" then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
local function getmsg(key, ...)
|
||||
|
||||
local function setmsg(msg, ...)
|
||||
local lang = os.lang()
|
||||
msg = (msg[lang] ~= nil and msg[lang] or msg)
|
||||
for i, str in ipairs({...}) do
|
||||
msg = msg:gsub("#" .. i, str)
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
local msg = lit.meta2table(pandoc.read([[#locale()]]).meta)
|
||||
local levelname, level = "INFO", 0
|
||||
for mtype, msgs in pairs(msg) do
|
||||
if msgs[key] ~= nil then
|
||||
key = setmsg(msgs[key], ...)
|
||||
levelname, level = mtype, debuglevel(mtype)
|
||||
break
|
||||
end
|
||||
end
|
||||
return key, levelname, level
|
||||
end
|
||||
|
||||
local verbosity = debuglevel(PANDOC_STATE.verbosity)
|
||||
local msg, levelname, level = getmsg(key, ...)
|
||||
if level >= verbosity then
|
||||
print("[" .. levelname .. "] [LIT] " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
function lit.getmetaval(meta, key)
|
||||
local mtype = pandoc.utils.type(meta[key])
|
||||
local mval = meta[key]
|
||||
if mtype == "Inlines" then
|
||||
mval = pandoc.utils.stringify(mval)
|
||||
mtype = pandoc.utils.type(mval)
|
||||
end
|
||||
return mval, mtype
|
||||
end
|
||||
|
||||
function lit.checkextra(meta)
|
||||
for key, val in pairs(meta) do
|
||||
local missing1 = lit.metastruct["mandatory"][key] == nil
|
||||
local missing2 = lit.metastruct["optional"][key] == nil
|
||||
local mval = lit.getmetaval(meta, key)
|
||||
if missing1 and missing2 then
|
||||
lit.puts("invalid_key", key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function lit.checkmeta_type(type, meta, key)
|
||||
local mval, mtype = lit.getmetaval(meta, key)
|
||||
if type ~= mtype then
|
||||
if type == "path" then
|
||||
if not(pandoc.path.directory(mval):isdir()) then
|
||||
lit.puts("invalid_path", mval, key)
|
||||
end
|
||||
else
|
||||
lit.puts("invalid_type", mtype, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function lit.checkmeta_match(table, mval, key)
|
||||
for _, pattern in pairs(table) do
|
||||
if mval:match("^" .. pattern .. "$") then
|
||||
return ""
|
||||
end
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
function lit.checkmeta_table(table, meta, key)
|
||||
local mval = lit.getmetaval(meta, key)
|
||||
local err = lit.checkmeta_match(table, mval, key)
|
||||
if not(err:isempty()) then
|
||||
lit.puts("invalid_value", mval, err)
|
||||
end
|
||||
end
|
||||
|
||||
function lit.checkmeta(meta, kind)
|
||||
for key, val in pairs(lit.metastruct[kind]) do
|
||||
if meta[key] == nil and kind == "mandatory" then
|
||||
lit.puts("no_key", key)
|
||||
elseif meta[key] and type(val) == "table" then
|
||||
lit.checkmeta_table(val, meta, key)
|
||||
elseif meta[key] then
|
||||
lit.checkmeta_type(val, meta, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function lit.setmeta(meta)
|
||||
lit.checkmeta(meta, "mandatory")
|
||||
lit.checkmeta(meta, "optional")
|
||||
lit.checkextra(meta)
|
||||
-- TODO checks for 2) duplicates
|
||||
-- Set defaults if lit.status = true
|
||||
return meta
|
||||
end
|
||||
|
||||
-- TODO
|
||||
function lit.assert(e, status)
|
||||
if status == false then
|
||||
|
@ -236,32 +174,98 @@ function lit.assert(e, status)
|
|||
return e
|
||||
end
|
||||
|
||||
function lit.parseyaml(rawyaml)
|
||||
-- TODO io.try
|
||||
local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1")
|
||||
if yaml:read("*l"):sub(1, 1) == "{" then
|
||||
yaml = pandoc.read(rawyaml).meta
|
||||
if pandoc.utils.stringify(yaml):isempty() then
|
||||
lit.puts("yaml_empty")
|
||||
else
|
||||
return lit.setmeta(yaml)
|
||||
end
|
||||
else
|
||||
lit.puts("yaml_invalid")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function lit.parseblock(parsed)
|
||||
lit.puts("parsing", table.concat(parsed, ""):indent())
|
||||
local yaml = lit.parseyaml(parsed[1])
|
||||
-- TODO: parsecode
|
||||
return "TODO"
|
||||
end
|
||||
|
||||
function lit.getblock(codeblock)
|
||||
local parsed = lpeg.match(lit.g.block, codeblock.text)
|
||||
parsed = (parsed ~= nil and lit.parseblock(parsed) or parsed)
|
||||
|
||||
local function checkextra(meta)
|
||||
for key, val in pairs(meta) do
|
||||
local missing1 = lit.metastruct["mandatory"][key] == nil
|
||||
local missing2 = lit.metastruct["optional"][key] == nil
|
||||
if missing1 and missing2 then
|
||||
lit.puts("invalid_key", key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function checkmeta(meta, kind)
|
||||
|
||||
local function checktype(type1, meta, key)
|
||||
local val = meta[key]
|
||||
local type2 = type(val)
|
||||
if type1 ~= type2 then
|
||||
if type1 == "path" then
|
||||
if not(pandoc.path.directory(val):isdir()) then
|
||||
lit.puts("invalid_path", val, key)
|
||||
end
|
||||
else
|
||||
lit.puts("invalid_type", type2, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function checktable(table, meta, key)
|
||||
|
||||
local function checkmatch(table, val, key)
|
||||
for _, pattern in pairs(table) do
|
||||
if val:match("^" .. pattern .. "$") then
|
||||
return ""
|
||||
end
|
||||
end
|
||||
return key
|
||||
end
|
||||
|
||||
local val = meta[key]
|
||||
local err = checkmatch(table, val, key)
|
||||
if not(err:isempty()) then
|
||||
lit.puts("invalid_value", val, err)
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(lit.metastruct[kind]) do
|
||||
if meta[key] == nil and kind == "mandatory" then
|
||||
lit.puts("no_key", key)
|
||||
elseif meta[key] and type(val) == "table" then
|
||||
checktable(val, meta, key)
|
||||
elseif meta[key] then
|
||||
checktype(val, meta, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function setmeta(meta)
|
||||
local tmeta = lit.meta2table(meta)
|
||||
checkmeta(tmeta, "mandatory")
|
||||
checkmeta(tmeta, "optional")
|
||||
checkextra(tmeta)
|
||||
-- TODO checks for 2) duplicates
|
||||
-- Set defaults if lit.status = true
|
||||
return meta
|
||||
end
|
||||
|
||||
local function parseyaml(rawyaml)
|
||||
-- TODO io.try
|
||||
local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1")
|
||||
if yaml:read("*l"):sub(1, 1) == "{" then
|
||||
yaml = pandoc.read(rawyaml).meta
|
||||
if pandoc.utils.stringify(yaml):isempty() then
|
||||
lit.puts("yaml_empty")
|
||||
else
|
||||
return setmeta(yaml)
|
||||
end
|
||||
else
|
||||
lit.puts("yaml_invalid")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function inspect(parsed)
|
||||
lit.puts("parsing", table.concat(parsed, ""):indent())
|
||||
local yaml = parseyaml(parsed[1])
|
||||
-- TODO: parsecode
|
||||
return "TODO"
|
||||
end
|
||||
|
||||
local parsed = lpeg.match(lit.g("block"), codeblock.text)
|
||||
parsed = (parsed ~= nil and inspect(parsed) or parsed)
|
||||
return codeblock, lit.status
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue