diff --git a/README.md b/README.md index 48e5c84..83f6c8b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ process of creating works of literature][1]". ## Requirements -- [Pandoc] +- [Pandoc] v3 ## Install diff --git a/dist/lin.lua b/dist/lin.lua index 0de774e..85727c6 100644 --- a/dist/lin.lua +++ b/dist/lin.lua @@ -6302,10 +6302,14 @@ function os.isunix() return os.uname() ~= "windows" end --- Changes newlines so everything is in one line --- @return string: String with formatted newlines as literal "\n" -function string:linearize() - return self:gsub("\n", "\\\\n") +-- Gets OS language +-- @return string, string, string: Language, locale and encoding +function os.lang() + local lang = os.getenv("LANG") + if lang ~= nil then + return lang:match("(%w%w)_?(%w?%w?)%.?(.*)") + end + return "en", "US", "UTF-8" end -- Checks if string is empty @@ -6314,22 +6318,47 @@ function string:isempty() return self == '' end +-- Changes newlines so everything is in one line +-- @return string: String with formatted newlines as literal "\n" +function string:linearize() + return self:gsub("\n", "\\n") +end + +function string:indent(num, char) + num = num or 2 + char = char or " " + char = string.rep(char, num) + return char .. self:gsub("\n", "\n" .. char) +end + +-- Removes spaces at the beginning of the 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 function string:rstrip() return self:gsub("%s+$", "") end +-- Removes spaces at the beginning and at the end of the string +-- @return string: stripped string function string:strip() - return self:lstrip():rstrip() + self = self:lstrip():rstrip() + return self +end + +-- Alias of strip +function string:trim() + return self:strip() end -- The following are heavily influenced by Python pathlib -- Check: https://docs.python.org/3/library/pathlib.html --- Checks if string is a file or directory +-- Checks if string is a file or a directory -- @return boolean: Exists or not function string:exists() return os.rename(self, self) ~= nil @@ -6363,6 +6392,8 @@ function string:read_text() end end +-- Gets file without suffix +-- @return string: File wihtout suffix function string:stem() return self:gsub("%.%a+$", "") end @@ -6394,6 +6425,8 @@ if pandoc ~= nil then -- Gets file extension namespace -- Check: https://pandoc.org/MANUAL.html#general-options + -- If suffix is 'md' or empty, the default is markdown. + -- For the rest, the format is the suffix. -- @param file string: File name -- @return string: File extension according to Pandoc format namespaces function pandoc.getext(file) @@ -6403,22 +6436,21 @@ if pandoc ~= nil then -- Pandoc converter -- Converts input file name into output format name. - -- If ofile is not nil, writes output file name instead of returning content. + -- If ofile is not nil, writes output file name. -- If iformat is nil, defaults to ifile extension name. -- @param ifile string: Input file name -- @param oformat string: Output format name -- @param ofile string or nil: Output file name -- @param iformat string or nil: Input format name - -- @return string or nil: Output file content + -- @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) - if ofile == nil then - return doc - else + if ofile ~= nil then local eol = (os:isunix() and "\n" or "\r\n") io.open(ofile, "w"):write(doc, eol):close() end + return doc end end @@ -6459,6 +6491,94 @@ lit.metastruct = { }, } +-- Messages for the user +lit.msg = { + ["INFO"] = { + ["parsing"] = { + ["en"] = "Parsing:\n#1", + ["es"] = "Realizando análisis sintáctico:\n#1", + }, + }, + ["WARNING"] = { + + }, + ["ERROR"] = { + ["invalid_key"] = { + ["en"] = "Invalid key '#1' with value '#2'", + ["es"] = "Clave '#1' inválida con valor '#2'", + }, + ["invalid_path"] = { + ["en"] = "Invalid path '#1' in key '#2'", + ["es"] = "Ruta '#1' inválida en clave '#2'", + }, + ["invalid_type"] = { + ["en"] = "Invalid type '#1' in key '#2'", + ["es"] = "Tipo '#1' inválido en clave '#2'", + }, + ["invalid_value"] = { + ["en"] = "Invalid value '#1' in key '#2'", + ["es"] = "Valor '#1' inválido en clave '#2'", + }, + ["no_key"] = { + ["en"] = "Key '#1' not found", + ["es"] = "Clave '#1' no encontrada", + }, + ["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", + }, + }, +} + +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 levelname = "INFO" + local level = 0 + for mtype, msgs in pairs(lit.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, ...) + 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] @@ -6469,40 +6589,44 @@ function lit.getmetaval(meta, key) return mval, mtype end +function lit.checkmetaextra(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, mval) + end + end +end + function lit.checkmetatype(type, meta, key) local mval, mtype = lit.getmetaval(meta, key) - local err = "" if type ~= mtype then if type == "path" then if not(pandoc.path.directory(mval):isdir()) then - err = "Invalid path '" .. mval .. "' in key '" .. key .. "'" + lit.puts("invalid_path", mval, key) end else - err = "Invalid type '" .. mtype .. "' in key '" .. key .. "'" + lit.puts("invalid_type", mtype, key) end end - if not(err:isempty()) then lit.puts(err, "ERROR") end end function lit.checkmetatable(table, meta, key) local mval = lit.getmetaval(meta, key) - local err = "" for _, pattern in pairs(table) do - if mval:match("^" .. pattern .. "$") then - err = "" - break - else - err = "Invalid value '" .. mval .. "' in key '" .. key .. "'" + if mval:match("^" .. pattern .. "$") == nil then + lit.puts("invalid_value", mval, key) end end - if not(err:isempty()) then lit.puts(err, "ERROR") end end function lit.checkmeta(meta, kind) for key, val in pairs(lit.metastruct[kind]) do if meta[key] == nil then if kind == "mandatory" then - lit.puts("Key '" .. key .. "' not found", "ERROR") + lit.puts("no_key", key) end else if type(val) == "table" then @@ -6517,7 +6641,8 @@ end function lit.setmeta(meta) lit.checkmeta(meta, "mandatory") lit.checkmeta(meta, "optional") - -- TODO checks for 1) extra keys and 2) duplicates + lit.checkmetaextra(meta) + -- TODO checks for 2) duplicates -- Set defaults if lit.status = true return meta end @@ -6562,51 +6687,32 @@ function lit.eval(code) end ]]-- +-- TODO function lit.assert(e, status) if status == false then - lit.puts("Aborted due previous errors", "ERROR") + lit.puts("aborted") os.exit(1) end return e 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(msg, kind) - local kind = kind or "INFO" - local verbosity = lit.debuglevel(PANDOC_STATE.verbosity) - level = lit.debuglevel(kind) - if level >= verbosity then - print("[" .. kind .. "] [LIT] " .. msg) - end -end - function lit.parseyaml(rawyaml) 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("Empty YAML", "ERROR") + lit.puts("yaml_empty") else return lit.setmeta(yaml) end else - lit.puts("Invalid YAML", "ERROR") + lit.puts("yaml_invalid") return nil end end function lit.parseblock(parsed) - lit.puts("Parsing " .. table.concat(parsed, "\n"):linearize()) + lit.puts("parsing", table.concat(parsed, ""):indent()) local yaml = lit.parseyaml(parsed[1]) -- TODO: parsecode return "TODO" diff --git a/src/extensions.lua b/src/extensions.lua index 88ae7b1..b8eef20 100644 --- a/src/extensions.lua +++ b/src/extensions.lua @@ -41,10 +41,14 @@ function os.isunix() return os.uname() ~= "windows" end --- Changes newlines so everything is in one line --- @return string: String with formatted newlines as literal "\n" -function string:linearize() - return self:gsub("\n", "\\\\n") +-- Gets OS language +-- @return string, string, string: Language, locale and encoding +function os.lang() + local lang = os.getenv("LANG") + if lang ~= nil then + return lang:match("(%w%w)_?(%w?%w?)%.?(.*)") + end + return "en", "US", "UTF-8" end -- Checks if string is empty @@ -53,22 +57,47 @@ function string:isempty() return self == '' end +-- Changes newlines so everything is in one line +-- @return string: String with formatted newlines as literal "\n" +function string:linearize() + return self:gsub("\n", "\\n") +end + +function string:indent(num, char) + num = num or 2 + char = char or " " + char = string.rep(char, num) + return char .. self:gsub("\n", "\n" .. char) +end + +-- Removes spaces at the beginning of the 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 function string:rstrip() return self:gsub("%s+$", "") end +-- Removes spaces at the beginning and at the end of the string +-- @return string: stripped string function string:strip() - return self:lstrip():rstrip() + self = self:lstrip():rstrip() + return self +end + +-- Alias of strip +function string:trim() + return self:strip() end -- The following are heavily influenced by Python pathlib -- Check: https://docs.python.org/3/library/pathlib.html --- Checks if string is a file or directory +-- Checks if string is a file or a directory -- @return boolean: Exists or not function string:exists() return os.rename(self, self) ~= nil @@ -102,6 +131,8 @@ function string:read_text() end end +-- Gets file without suffix +-- @return string: File wihtout suffix function string:stem() return self:gsub("%.%a+$", "") end @@ -133,6 +164,8 @@ if pandoc ~= nil then -- Gets file extension namespace -- Check: https://pandoc.org/MANUAL.html#general-options + -- If suffix is 'md' or empty, the default is markdown. + -- For the rest, the format is the suffix. -- @param file string: File name -- @return string: File extension according to Pandoc format namespaces function pandoc.getext(file) @@ -142,22 +175,21 @@ if pandoc ~= nil then -- Pandoc converter -- Converts input file name into output format name. - -- If ofile is not nil, writes output file name instead of returning content. + -- If ofile is not nil, writes output file name. -- If iformat is nil, defaults to ifile extension name. -- @param ifile string: Input file name -- @param oformat string: Output format name -- @param ofile string or nil: Output file name -- @param iformat string or nil: Input format name - -- @return string or nil: Output file content + -- @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) - if ofile == nil then - return doc - else + if ofile ~= nil then local eol = (os:isunix() and "\n" or "\r\n") io.open(ofile, "w"):write(doc, eol):close() end + return doc end end diff --git a/src/literate.lua b/src/literate.lua index a70cd94..0345184 100644 --- a/src/literate.lua +++ b/src/literate.lua @@ -28,6 +28,94 @@ lit.metastruct = { }, } +-- Messages for the user +lit.msg = { + ["INFO"] = { + ["parsing"] = { + ["en"] = "Parsing:\n#1", + ["es"] = "Realizando análisis sintáctico:\n#1", + }, + }, + ["WARNING"] = { + + }, + ["ERROR"] = { + ["invalid_key"] = { + ["en"] = "Invalid key '#1' with value '#2'", + ["es"] = "Clave '#1' inválida con valor '#2'", + }, + ["invalid_path"] = { + ["en"] = "Invalid path '#1' in key '#2'", + ["es"] = "Ruta '#1' inválida en clave '#2'", + }, + ["invalid_type"] = { + ["en"] = "Invalid type '#1' in key '#2'", + ["es"] = "Tipo '#1' inválido en clave '#2'", + }, + ["invalid_value"] = { + ["en"] = "Invalid value '#1' in key '#2'", + ["es"] = "Valor '#1' inválido en clave '#2'", + }, + ["no_key"] = { + ["en"] = "Key '#1' not found", + ["es"] = "Clave '#1' no encontrada", + }, + ["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", + }, + }, +} + +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 levelname = "INFO" + local level = 0 + for mtype, msgs in pairs(lit.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, ...) + 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] @@ -38,40 +126,44 @@ function lit.getmetaval(meta, key) return mval, mtype end +function lit.checkmetaextra(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, mval) + end + end +end + function lit.checkmetatype(type, meta, key) local mval, mtype = lit.getmetaval(meta, key) - local err = "" if type ~= mtype then if type == "path" then if not(pandoc.path.directory(mval):isdir()) then - err = "Invalid path '" .. mval .. "' in key '" .. key .. "'" + lit.puts("invalid_path", mval, key) end else - err = "Invalid type '" .. mtype .. "' in key '" .. key .. "'" + lit.puts("invalid_type", mtype, key) end end - if not(err:isempty()) then lit.puts(err, "ERROR") end end function lit.checkmetatable(table, meta, key) local mval = lit.getmetaval(meta, key) - local err = "" for _, pattern in pairs(table) do - if mval:match("^" .. pattern .. "$") then - err = "" - break - else - err = "Invalid value '" .. mval .. "' in key '" .. key .. "'" + if mval:match("^" .. pattern .. "$") == nil then + lit.puts("invalid_value", mval, key) end end - if not(err:isempty()) then lit.puts(err, "ERROR") end end function lit.checkmeta(meta, kind) for key, val in pairs(lit.metastruct[kind]) do if meta[key] == nil then if kind == "mandatory" then - lit.puts("Key '" .. key .. "' not found", "ERROR") + lit.puts("no_key", key) end else if type(val) == "table" then @@ -86,7 +178,8 @@ end function lit.setmeta(meta) lit.checkmeta(meta, "mandatory") lit.checkmeta(meta, "optional") - -- TODO checks for 1) extra keys and 2) duplicates + lit.checkmetaextra(meta) + -- TODO checks for 2) duplicates -- Set defaults if lit.status = true return meta end @@ -131,51 +224,32 @@ function lit.eval(code) end ]]-- +-- TODO function lit.assert(e, status) if status == false then - lit.puts("Aborted due previous errors", "ERROR") + lit.puts("aborted") os.exit(1) end return e 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(msg, kind) - local kind = kind or "INFO" - local verbosity = lit.debuglevel(PANDOC_STATE.verbosity) - level = lit.debuglevel(kind) - if level >= verbosity then - print("[" .. kind .. "] [LIT] " .. msg) - end -end - function lit.parseyaml(rawyaml) 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("Empty YAML", "ERROR") + lit.puts("yaml_empty") else return lit.setmeta(yaml) end else - lit.puts("Invalid YAML", "ERROR") + lit.puts("yaml_invalid") return nil end end function lit.parseblock(parsed) - lit.puts("Parsing " .. table.concat(parsed, "\n"):linearize()) + lit.puts("parsing", table.concat(parsed, ""):indent()) local yaml = lit.parseyaml(parsed[1]) -- TODO: parsecode return "TODO"