From 15be6fca8e0ba1eca4306c664768d6cf4f7b878a Mon Sep 17 00:00:00 2001 From: perro Date: Mon, 3 Apr 2023 11:39:11 -0700 Subject: [PATCH] Refactoring: nested functions --- dist/lin.lua | 443 +++++++++++++++++------------- opt/lua-extensions/extensions.lua | 75 ++++- scripts/make_dist.lua | 4 +- scripts/test.lua | 2 +- src/literate.lua | 378 ++++++++++++------------- 5 files changed, 510 insertions(+), 392 deletions(-) diff --git a/dist/lin.lua b/dist/lin.lua index bfeff01..3bbf1fe 100644 --- a/dist/lin.lua +++ b/dist/lin.lua @@ -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 diff --git a/opt/lua-extensions/extensions.lua b/opt/lua-extensions/extensions.lua index b8eef20..ce85329 100644 --- a/opt/lua-extensions/extensions.lua +++ b/opt/lua-extensions/extensions.lua @@ -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() diff --git a/scripts/make_dist.lua b/scripts/make_dist.lua index 75c54b1..958cb9a 100644 --- a/scripts/make_dist.lua +++ b/scripts/make_dist.lua @@ -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 diff --git a/scripts/test.lua b/scripts/test.lua index 4b6f275..7044be1 100644 --- a/scripts/test.lua +++ b/scripts/test.lua @@ -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() diff --git a/src/literate.lua b/src/literate.lua index 7b0d467..4095fc3 100644 --- a/src/literate.lua +++ b/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