lua-dog/src/dog.lua

302 lines
9.0 KiB
Lua

-- Variable for storing all the functions
local dog = {}
-- Functions are divided according to the standard library to extend
dog.io = {}
dog.os = {}
dog.string = {}
dog.utf8 = {}
-- Imports dog functions to _G
-- Allows using the other functions without a prefix, i.e.:
-- dog.os.uname() => os.uname()
function dog.import()
local msg = "[WARNING] [DOG] "
msg = msg .. "Ignoring import: '@1' already exists; use '@2' instead"
for libkey, dogfns in pairs(dog) do
local lib = _G[libkey]
if type(dogfns) == "table" then
for fnkey, fn in pairs(dogfns) do
if not lib[fnkey] then
lib[fnkey] = fn
else
local name = libkey .. "." .. fnkey .. "()"
msg = msg:gsub("@1", name):gsub("@2", "dog." .. name)
print(msg)
end
end
end
end
end
-- Tries popen
-- @param ... string: Chunks for popen
-- @return boolean, string: Status and output of popen
function dog.io.try(...)
local cmd = table.concat({ ... }, " ") .. " 2>&1"
local handle = io.popen(cmd)
local output = handle:read("*a")
local status = (handle:close() ~= nil and true or false)
return status, output
end
-- Gets OS short name
-- It is just intented to know if it is Linux, macOS or Windows.
-- @return string: "linux" or "bsd" or "macos" or "windows"
function dog.os.uname()
local status, output = dog.io.try("uname")
if status then
output = output:gsub(" .*", ""):lower()
if output ~= "linux" and output:match("bsd") ~= nil then
return "bsd"
elseif output ~= "linux" then
return "macos"
end
return "linux"
end
return "windows"
end
-- Checks if OS is windows
-- @return boolean: Windows or not
function dog.os.iswin() return dog.os.uname() == "windows" end
-- Checks if OS is Unix
-- @return boolean: Unix or not
function dog.os.isunix() return dog.os.uname() ~= "windows" end
-- Gets OS language
-- @return string, string, string: Language, locale and encoding
function dog.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
-- @return boolean: Empty or not
function dog.string.isempty(str) return str == "" end
-- Changes newlines so everything is in one line
-- @return string: String with formatted newlines as literal "\n"
function dog.string.linearize(str) return str:gsub("\n", "\\n") end
-- Normalizes string
-- @return string: Normalized string
function dog.string.normalize(str)
str = str:lower()
for newchar, chars in pairs(dog.utf8.table()) do
for _, oldchar in ipairs(chars) do
str = str:gsub(oldchar, newchar)
end
end
return str
end
-- Adds indent
-- @param num number: Indent size, 2 by default
-- @param char string: Indent character, space by default
-- @return strin: Indented string
function dog.string.indent(str, num, char)
num = num or 2
char = char or " "
char = string.rep(char, num)
return char .. str: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 dog.string.split(str, sep)
sep = sep or "%s+"
local parts = {}
for part in str:gmatch("([^" .. sep .. "]+)") do
table.insert(parts, part)
end
return parts
end
-- Removes spaces at the beginning of the string
-- @return string: Left stripped string
function dog.string.lstrip(str) return str:gsub("^%s+", "") end
-- Removes spaces at the end of the string
-- @return string: Right stripped string
function dog.string.rstrip(str) return str:gsub("%s+$", "") end
-- Removes spaces at the beginning and at the end of the string
-- @return string: Stripped string
function dog.string.strip(str)
str = dog.string.lstrip(str)
return dog.string.rstrip(str)
end
-- Alias of strip
function dog.string.trim(str) return dog.string.strip(str) 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 a directory
-- @return boolean: Exists or not
function dog.string.exists(str) return os.rename(str, str) ~= nil end
-- Checks if string is a file
-- @return boolean: File or not
function dog.string.isfile(str)
if dog.string.exists(str) then return io.open(str, "a+") ~= nil end
return false
end
-- Checks if string is a directory
-- @return boolean: Directory or not
function dog.string.isdir(str)
if dog.string.exists(str) then
return io.open(str, "a+") == nil
elseif str == "." or str == ".." then
return true
end
return false
end
-- Reads file content as string
-- @return string or nil: File as string or nil
function dog.string.readtext(str)
if dog.string.exists(str) then return io.open(str):read("*a") end
end
-- Read file content as lines
-- @return table: Table of file lines or nil
function dog.string.readlines(str)
local lines = {}
if dog.string.exists(str) then
for line in io.open(str):lines() do
table.insert(lines, line)
end
end
return lines
end
-- Gets file without suffix
-- @return string: File wihtout suffix
function dog.string.stem(str) return str:gsub("%.%a+$", "") end
-- Gets file extensions
-- @return table: List of file extensions
function dog.string.suffixes(str)
local suffixes = {}
for suffix in str:gmatch("%.%a+") do
table.insert(suffixes, suffix)
end
return suffixes
end
-- Gets file final extension
-- @return string: Final file extension
function dog.string.suffix(str)
local suffixes = str:suffixes()
if suffixes[#suffixes] then return suffixes[#suffixes] end
return ""
end
-- Gets an equivalency table between ASCII and Unicode
-- Note: filled on demand.
-- @return table: ASCII-Unicode table equivalency
function dog.utf8.table()
return {
["a"] = { "á", "à", "ä" },
["e"] = { "é", "è", "ë" },
["i"] = { "í", "ì", "ï" },
["o"] = { "ó", "ò", "ö" },
["u"] = { "ú", "ù", "ü" },
["n"] = { "ñ" },
}
end
-- Extends Pandoc Library
-- Check: https://pandoc.org/lua-filters.html#module-pandoc
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)
local ext = dog.string.suffix(file):gsub("^.", "")
return ((ext == "md" or dog.string.isempty(ext)) and "markdown" or ext)
end
-- Pandoc converter
-- Converts input file name into output format name.
-- 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: Output file content
function pandoc.convert(ifile, oformat, ofile, iformat)
iformat = (iformat == nil and pandoc.getext(ifile) or iformat)
local itext = pandoc.read(dog.string.readtext(ifile), iformat)
local doc = pandoc.write(itext, oformat)
if ofile ~= nil then
local eol = (dog.os:isunix() and "\n" or "\r\n")
io.open(ofile, "w"):write(doc, eol):close()
end
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)
return pandoc.utils.stringify(content:walk({
Plain = function(plain)
table.insert(plain.content, pandoc.Space())
return plain
end,
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,
SoftBreak = function(_) return pandoc.Str("\n") end,
Inline = function(inline) return pandoc.utils.stringify(inline) end,
}))
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
if type(v) == "boolean" then
newmeta[k] = v
elseif tonumber(v) then
newmeta[k] = tonumber(v)
else
newmeta[k] = pandoc.utils.rawstringify(v)
end
end
end
return newmeta
end
end
return dog