From f2016b6654136e4b4f007b74a7374c635a8f40fe Mon Sep 17 00:00:00 2001 From: perro Date: Fri, 7 Apr 2023 13:50:35 -0700 Subject: [PATCH] Almost finish the refactoring: work needed on 1) pandoc.metatotable and 2) literate should work over doc not blocks --- dist/lin.lua | 345 +++++++++++++++--------------- opt/lua-extensions/extensions.lua | 44 ++++ src/literate.lua | 251 +++++++++------------- src/locale.yaml | 18 +- src/pandoc.lua | 2 +- 5 files changed, 329 insertions(+), 331 deletions(-) diff --git a/dist/lin.lua b/dist/lin.lua index e161cb1..cabc1db 100644 --- a/dist/lin.lua +++ b/dist/lin.lua @@ -6508,6 +6508,51 @@ if pandoc ~= nil then return doc end + -- Stringifies Pandoc content + -- Avoids undesired behavios of pandoc.utils.stringify, such as quotes and + -- backslashes conversions. + -- @param content pandoc.MetaValue: Pandoc content value + -- @return string: Pandoc stringified value + function pandoc.utils.rawstringify(content) + if pandoc.utils.type(content) == "Inlines" then + return pandoc.utils.stringify(content:walk { + Quoted = function(quoted) + local q = (quoted.quotetype == SingleQuoted and '"' or "'") + return pandoc.Str(q .. pandoc.utils.stringify(quoted.content) .. q) + end, + RawInline = function(rawinline) + return pandoc.Str(rawinline.text:gsub("\\n", "\n"):gsub("\\t", "\t")) + end, + Inline = function(inline) return pandoc.utils.stringify(inline) end, + }) + end + return pandoc.utils.stringify(content) + end + + -- Converts pandoc.Meta to table + -- Note that stringified numbers, such "1", are converted to numbers because + -- Pandoc doesn't make distinctions between strings and numbers. + -- Check: https://pandoc.org/lua-filters.html#type-metavalue + -- @param meta pandoc.Meta: Pandoc MetaValues collection + -- @return table: Pandoc metadata as table + function pandoc.metatotable(meta) + + local newmeta = {} + for k, v in pairs(meta) do + if pandoc.utils.type(v) == "table" then + newmeta[k] = pandoc.metatotable(v) + else + newmeta[k] = pandoc.utils.rawstringify(v) + if newmeta[k] == "true" or meta[k] == "false" then + newmeta[k] = newmeta[k] == "true" + elseif tonumber(newmeta[k]) then + newmeta[k] = tonumber(newmeta[k]) + end + end + end + return newmeta + end + end ----------------------------------- NATURAL ----------------------------------- @@ -6558,11 +6603,12 @@ function lit.g(name) 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 + -- Still not used local id = lpeg.R("az", "AZ") * lpeg.R("az", "AZ", "09")^0 local ref = lpeg.P"#" * id local grammars = { - -- Blocks grammar + -- Block grammar ["block"] = lpeg.P { "Block"; Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name"); @@ -6574,74 +6620,10 @@ function lit.g(name) return grammars[name] end --- Evals Lisp code ---[[ -function lit.eval(code) - local is_passed, out = pcall ( - function () return fennel.eval(code) end, - function (e) return e end - ) - local lua = "" - local out = tostring(out) - local preview = out:gsub("\n.*", "") - if is_passed then - lua = fennel.compileString(code) - end - return {is_passed = is_passed, preview = preview, out = out, lua = lua} -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] = string2other(stringify(v)) - end - end - return meta -end - +-- Prints located messages function lit.puts(key, ...) + -- Returns debug level as number local function debuglevel(str) if str == "ERROR" then lit.status = false @@ -6653,22 +6635,13 @@ function lit.puts(key, ...) end end + -- Gets located message 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([[--- + local msg = pandoc.metatotable(pandoc.read([[--- INFO: - parsing: - en: Parsing:\n#1 - es: Realizando análisis sintáctico:\n#1 + checking: + en: Checking:\n#1 + es: Comprobando:\n#1 WARNING: foo: en: bar @@ -6692,18 +6665,19 @@ ERROR: aborted: en: Aborted due previous errors es: Abortado debido a previos errores - yaml_empty: - en: Empty YAML - es: YAML vacío - yaml_invalid: - en: Invalid YAML - es: YAML inválido + meta_empty: + en: Empty metadata + es: Metadatos vacíos + meta_invalid: + en: Invalid metadata + es: Metadatos inválidos ... ]]).meta) - local levelname, level = "INFO", 0 + local levelname, level, lang = "INFO", 0, os.lang() for mtype, msgs in pairs(msg) do if msgs[key] ~= nil then - key = setmsg(msgs[key], ...) + key = (msgs[key][lang] ~= nil and msgs[key][lang] or msgs[key]["en"]) + for i, str in ipairs({...}) do key = key:gsub("#" .. i, str) end levelname, level = mtype, debuglevel(mtype) break end @@ -6718,122 +6692,139 @@ ERROR: end end +-- TODO +-- Inserts evaluations in inline code function lit.insert(code) return code end -function lit.parse(blocks) +-- Examinates (parses and evaluates) code blocks +function lit.exam(blocks) + -- Evaluates code block + -- Evals Lisp code + --[[ + local function eval(code) + local is_passed, out = pcall ( + function () return fennel.eval(code) end, + function (e) return e end + ) + local lua = "" + local out = tostring(out) + local preview = out:gsub("\n.*", "") + if is_passed then + lua = fennel.compileString(code) + end + return {is_passed = is_passed, preview = preview, out = out, lua = lua} + end + ]]-- + + -- Parses code block local function parse(codeblock, islast) - 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 + -- Checks code block + local function check(parsed) - local function checkmeta(meta, kind) + -- Checks for extra and unwanted metadata + 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 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 + -- Checks for valid metadata + local function checkmeta(meta, kind) - local function checktable(table, meta, key) + -- Checks for valid metadata type + 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 checkmatch(table, val, key) + -- Checks for valid metadata pattern + local function checkpattern(table, meta, key) + local val = tostring(meta[key]) + local err = key for _, pattern in pairs(table) do if val:match("^" .. pattern .. "$") then - return "" - end - end - return key + err = "" + break + end + end + 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 + checkpattern(val, meta, key) + elseif meta[key] then + checktype(val, meta, key) + end + end + end + + -- Parses metadata + local function parsemeta(yaml) + local isok, res = pcall(pandoc.read, yaml) + local meta = {} + if isok and not(pandoc.utils.stringify(res.meta):isempty()) then + meta = pandoc.metatotable(res.meta) + checkmeta(meta, "mandatory") + checkmeta(meta, "optional") + checkextra(meta) + -- TODO: checks for duplicates + elseif isok and pandoc.utils.stringify(res.meta):isempty() then + lit.puts("meta_empty") + else + lit.puts("meta_invalid") end + return meta + end - local val = tostring(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 + lit.puts("checking", table.concat(parsed, ""):indent()) + local meta = parsemeta(parsed[1]) + -- TODO: + -- meta["code"] = checkcode(parsed[2], meta) + -- return meta 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) + local checked = (parsed ~= nil and check(parsed) or parsed) + -- TODO: + -- evaluated = eval(checked) if islast and not(lit.status) then lit.puts("aborted") os.exit(1) end + -- TODO: + -- return evaluated return codeblock end - local function lastblockindex() - local last = 0 - blocks:walk { CodeBlock = function(_) last = last + 1 end } - return last - end - - local curr = 0 + local curr, last = 0, 0 + blocks:walk { CodeBlock = function(_) last = last + 1 end } return blocks:walk { CodeBlock = function(codeblock) curr = curr + 1 - return parse(codeblock, curr == lastblockindex()) + return parse(codeblock, curr == last) end } end @@ -6842,7 +6833,7 @@ end return { -- Parses and evals literate blocks - { Blocks = function(blocks) return lit.parse(blocks) end }, + { Blocks = function(blocks) return lit.exam(blocks) end }, -- Parses and inserts literate evals { Code = function(code) return lit.insert(code) end }, { diff --git a/opt/lua-extensions/extensions.lua b/opt/lua-extensions/extensions.lua index ce85329..a662aa5 100644 --- a/opt/lua-extensions/extensions.lua +++ b/opt/lua-extensions/extensions.lua @@ -247,4 +247,48 @@ if pandoc ~= nil then return doc end + -- Stringifies Pandoc content + -- Avoids undesired behavios of pandoc.utils.stringify, such as quotes and + -- backslashes conversions. + -- @param content pandoc.MetaValue: Pandoc content value + -- @return string: Pandoc stringified value + function pandoc.utils.rawstringify(content) + if pandoc.utils.type(content) == "Inlines" then + return pandoc.utils.stringify(content:walk { + Quoted = function(quoted) + local q = (quoted.quotetype == SingleQuoted and '"' or "'") + return pandoc.Str(q .. pandoc.utils.stringify(quoted.content) .. q) + end, + RawInline = function(rawinline) + return pandoc.Str(rawinline.text:gsub("\\n", "\n"):gsub("\\t", "\t")) + end, + Inline = function(inline) return pandoc.utils.stringify(inline) end, + }) + end + return pandoc.utils.stringify(content) + end + + -- Converts pandoc.Meta to table + -- Note that stringified numbers, such "1", are converted to numbers because + -- Pandoc doesn't make distinctions between strings and numbers. + -- Check: https://pandoc.org/lua-filters.html#type-metavalue + -- @param meta pandoc.Meta: Pandoc MetaValues collection + -- @return table: Pandoc metadata as table + function pandoc.metatotable(meta) + local newmeta = {} + for k, v in pairs(meta) do + if pandoc.utils.type(v) == "table" then + newmeta[k] = pandoc.metatotable(v) + else + newmeta[k] = pandoc.utils.rawstringify(v) + if newmeta[k] == "true" or meta[k] == "false" then + newmeta[k] = newmeta[k] == "true" + elseif tonumber(newmeta[k]) then + newmeta[k] = tonumber(newmeta[k]) + end + end + end + return newmeta + end + end diff --git a/src/literate.lua b/src/literate.lua index ee8e084..525d9f1 100644 --- a/src/literate.lua +++ b/src/literate.lua @@ -45,7 +45,7 @@ function lit.g(name) local ref = lpeg.P"#" * id local grammars = { - -- Blocks grammar + -- Block grammar ["block"] = lpeg.P { "Block"; Block = lpeg.Ct(lpeg.V"YAML" * lpeg.V"Code", "name"); @@ -57,44 +57,10 @@ function lit.g(name) return grammars[name] end -function lit.metatotable(meta) - - local function stringify(content) - if pandoc.utils.type(content) == "Inlines" then - return pandoc.utils.stringify(content:walk { - Quoted = function(quoted) - local q = (quoted.quotetype == SingleQuoted and '"' or "'") - return pandoc.Str(q .. pandoc.utils.stringify(quoted.content) .. q) - end, - RawInline = function(rawinline) - return pandoc.Str(rawinline.text:gsub("\\n", "\n"):gsub("\\t", "\t")) - end, - Inline = function(inline) return pandoc.utils.stringify(inline) end, - }) - end - return pandoc.utils.stringify(content) - end - - for k, v in pairs(meta) do - if pandoc.utils.type(v) == "table" then - meta[k] = lit.metatotable(v) - else - meta[k] = stringify(v) - if meta[k] == "true" or meta[k] == "false" then - meta[k] = meta[k] == "true" - -- Note that "1" always num because Pandoc doesn't make distinctions - -- cfr. https://pandoc.org/lua-filters.html#type-metavalue - elseif tonumber(meta[k]) then - meta[k] = tonumber(meta[k]) - end - end - end - - return meta -end - +-- Prints located messages function lit.puts(key, ...) + -- Returns debug level as number local function debuglevel(str) if str == "ERROR" then lit.status = false @@ -106,8 +72,9 @@ function lit.puts(key, ...) end end + -- Gets located message local function getmsg(key, ...) - local msg = lit.metatotable(pandoc.read([[#locale()]]).meta) + local msg = pandoc.metatotable(pandoc.read([[#locale()]]).meta) local levelname, level, lang = "INFO", 0, os.lang() for mtype, msgs in pairs(msg) do if msgs[key] ~= nil then @@ -125,147 +92,143 @@ function lit.puts(key, ...) if level >= verbosity then print("[" .. levelname .. "] [LIT] " .. msg) end - end -- TODO +-- Inserts evaluations in inline code function lit.insert(code) return code end --- Evals Lisp code ---[[ -function lit.eval(code) - local is_passed, out = pcall ( - function () return fennel.eval(code) end, - function (e) return e end - ) - local lua = "" - local out = tostring(out) - local preview = out:gsub("\n.*", "") - if is_passed then - lua = fennel.compileString(code) +-- Examinates (parses and evaluates) code blocks +function lit.exam(blocks) + + -- Evaluates code block + -- Evals Lisp code + --[[ + local function eval(code) + local is_passed, out = pcall ( + function () return fennel.eval(code) end, + function (e) return e end + ) + local lua = "" + local out = tostring(out) + local preview = out:gsub("\n.*", "") + if is_passed then + lua = fennel.compileString(code) + end + return {is_passed = is_passed, preview = preview, out = out, lua = lua} end - return {is_passed = is_passed, preview = preview, out = out, lua = lua} -end -]]-- - -function lit.parse(blocks) + ]]-- + -- Parses code block local function parse(codeblock, islast) - 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 + -- Checks code block + local function check(parsed) - local function checkmeta(meta, kind) + -- Checks for extra and unwanted metadata + 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 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 + -- Checks for valid metadata + local function checkmeta(meta, kind) - local function checktable(table, meta, key) + -- Checks for valid metadata type + 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 checkmatch(table, val, key) + -- Checks for valid metadata pattern + local function checkpattern(table, meta, key) + local val = tostring(meta[key]) + local err = key for _, pattern in pairs(table) do if val:match("^" .. pattern .. "$") then - return "" - end - end - return key + err = "" + break + end + end + 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 + checkpattern(val, meta, key) + elseif meta[key] then + checktype(val, meta, key) + end + end + end + + -- Parses metadata + local function parsemeta(yaml) + local isok, res = pcall(pandoc.read, yaml) + local meta = {} + if isok and not(pandoc.utils.stringify(res.meta):isempty()) then + meta = pandoc.metatotable(res.meta) + checkmeta(meta, "mandatory") + checkmeta(meta, "optional") + checkextra(meta) + -- TODO: checks for duplicates + elseif isok and pandoc.utils.stringify(res.meta):isempty() then + lit.puts("meta_empty") + else + lit.puts("meta_invalid") end + return meta + end - local val = tostring(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.metatotable(meta) - checkmeta(tmeta, "mandatory") - checkmeta(tmeta, "optional") - checkextra(tmeta) - -- TODO checks for 2) duplicates - -- Set defaults if lit.status = true + lit.puts("checking", table.concat(parsed, ""):indent()) + local meta = parsemeta(parsed[1]) + -- TODO: + -- meta["code"] = checkcode(parsed[2], meta) + -- return meta 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) + local checked = (parsed ~= nil and check(parsed) or parsed) + -- TODO: + -- evaluated = eval(checked) if islast and not(lit.status) then lit.puts("aborted") os.exit(1) end + -- TODO: + -- return evaluated return codeblock end - local function lastblockindex() - local last = 0 - blocks:walk { CodeBlock = function(_) last = last + 1 end } - return last - end - - local curr = 0 + local curr, last = 0, 0 + blocks:walk { CodeBlock = function(_) last = last + 1 end } return blocks:walk { CodeBlock = function(codeblock) curr = curr + 1 - return parse(codeblock, curr == lastblockindex()) + return parse(codeblock, curr == last) end } - end return lit diff --git a/src/locale.yaml b/src/locale.yaml index 14478bb..6c56f8d 100644 --- a/src/locale.yaml +++ b/src/locale.yaml @@ -1,8 +1,8 @@ --- INFO: - parsing: - en: Parsing:\n#1 - es: Realizando análisis sintáctico:\n#1 + checking: + en: Checking:\n#1 + es: Comprobando:\n#1 WARNING: foo: en: bar @@ -26,10 +26,10 @@ ERROR: aborted: en: Aborted due previous errors es: Abortado debido a previos errores - yaml_empty: - en: Empty YAML - es: YAML vacío - yaml_invalid: - en: Invalid YAML - es: YAML inválido + meta_empty: + en: Empty metadata + es: Metadatos vacíos + meta_invalid: + en: Invalid metadata + es: Metadatos inválidos ... diff --git a/src/pandoc.lua b/src/pandoc.lua index 7d3a73b..ad030b1 100644 --- a/src/pandoc.lua +++ b/src/pandoc.lua @@ -2,7 +2,7 @@ return { -- Parses and evals literate blocks - { Blocks = function(blocks) return lit.parse(blocks) end }, + { Blocks = function(blocks) return lit.exam(blocks) end }, -- Parses and inserts literate evals { Code = function(code) return lit.insert(code) end }, {