diff --git a/scripts/api/filesys.lua b/scripts/api/filesys.lua new file mode 100644 index 0000000..0609dd0 --- /dev/null +++ b/scripts/api/filesys.lua @@ -0,0 +1,17 @@ +require "api.platform" + +local FileSys = require "api.platform.filesys_impl" + +if os.name() == Platform.WINDOWS then + require "api.platform.win.filesys" +elseif os.name() == Platform.LINUX then + require "api.platform.linux.filesys" +elseif os.name() == Platform.MACOS then + game.Log("MacOS specific implementation missing, loading linux filesys module", game.LOGGER_WARNING) + require "api.platform.linux.filesys" +else + game.Log("OS Platform not recognized, loading linux filesys module", game.LOGGER_WARNING) + require "api.platform.linux.filesys" +end + +return FileSys diff --git a/scripts/api/platform.lua b/scripts/api/platform.lua new file mode 100644 index 0000000..817301a --- /dev/null +++ b/scripts/api/platform.lua @@ -0,0 +1,39 @@ + +---@note setup code from: https://stackoverflow.com/a/30960054 +local BinaryFormat = package.cpath:match("%p[\\|/]?%p(%a+)") + +if BinaryFormat == "dll" then + function os.name() + return "Windows" + end +elseif BinaryFormat == "so" then + function os.name() + return "Linux" + end +elseif BinaryFormat == "dylib" then + function os.name() + return "MacOS" + end +end + +BinaryFormat = nil + +Platform = { + WINDOWS = "Windows", + LINUX = "Linux", + MACOS = "MacOS" +} + +---Get OS platform lua is running on +function GetPlatform() + return os.name() +end + +---Merge base module table with implementation table, overwriting base +---@param base any +---@param implementation any +function MergeModules(base, implementation) + for key, value in pairs(implementation) do + base[key] = value + end +end diff --git a/scripts/api/platform/filesys_impl.lua b/scripts/api/platform/filesys_impl.lua new file mode 100644 index 0000000..d96ad93 --- /dev/null +++ b/scripts/api/platform/filesys_impl.lua @@ -0,0 +1,126 @@ +require "common.globals" + +---@class FileSys +local FileSys = { + __name = "FileSys", + sep = "/" +} + +---Return a normalized absolutized version of `path` +---@param path string +---@return string +function FileSys.abspath(path) + return FileSys.normpath(FileSys.join(FileSys.getcwd(), path)) +end + +---Return the base name of `path` +---@param path string +---@return string +function FileSys.basename(path) + return string.sub(path, string.rfind(path, FileSys.sep, 1, true) + 1) +end + +---Return the directory name of `path` +---@param path string +---@return string +function FileSys.dirname(path) + return FileSys.split(path)[1] +end + +---Return true if `path` refers to an existing path +---@param path string +---@return boolean, string? +function FileSys.exists(path) + local ok, err, code = os.rename(path, path) + if not ok then + game.Log("err: "..err..", code: "..code, game.LOGGER_DEBUG) + if code == 13 then + -- Permission denied, but it exists + return true + end + end + return ok, err +end + +---Return a string representing the current working directory +---@return string +function FileSys.getcwd() + assert(false, FileSys.__name .. ".getcwd() : Not Implemented") + return "" +end + +---Join one or more path components with the platform specific separator +---@param path string +---@param ... string +---@return string +function FileSys.join(path, ...) + local vargs = { select(1, ...) } + local joinedpath = path + + for _, value in ipairs(vargs) do + joinedpath = joinedpath .. FileSys.sep .. value + end + + return joinedpath +end + +---Normalize `path`, collapse redundant separators and up references +---@param path string +---@return string +function FileSys.normpath(path) + local sep = FileSys.sep + + --remove multiple slashes + path = path:gsub("("..sep..")"..sep.."+", "%1") + + --remove './' + path = path:gsub("%."..sep, "") + + --remove all up references + local count = 0 + local upRefPattern = "%w+"..sep.."%.%."..sep + repeat + path, count = path:gsub(upRefPattern, "") + until count ~= 0 + + --remove last slash + path = path:gsub("(.-)"..sep.."$", "%1") + + return path +end + +---Return a relative filepath to `path`, optionally relative to `relativeTo` +---@param path string +---@param relativeTo? string +---@return string +function FileSys.relpath(path, relativeTo) + relativeTo = relativeTo or FileSys.getcwd() + path = path:sub(path:find(relativeTo, 1, true)[2] + 1) + return path +end + +---Return a list of files and directory names in `path` +---@param path string +---@return string[] +function FileSys.scandir(path) + assert(false, FileSys.__name .. ".scandir() : Not Implemented") + return {} +end + +---Split `path` to (head, tail), tail is the last component, head is everything else +---@param path string +---@return string, string +function FileSys.split(path) + local lastSep = path:rfind(FileSys.sep, 1, true) + return path:sub(1, lastSep), path:sub(lastSep + 1) +end + +---Split `path` to (root, ext), such that `root + ext == path`, ext is either empty or starts with a '.' +---@param path string +---@return string, string +function FileSys.splitext(path) + local lastSep = path:rfind(".", 1, true) + return path:sub(1, lastSep - 1), path:sub(lastSep) +end + +return FileSys diff --git a/scripts/api/platform/linux/filesys.lua b/scripts/api/platform/linux/filesys.lua new file mode 100644 index 0000000..b97f3f6 --- /dev/null +++ b/scripts/api/platform/linux/filesys.lua @@ -0,0 +1,37 @@ +local FileSys = require "api.platform.filesys_impl" + +FileSys.sep = "/" + +function FileSys.getcwd() + local cwd, popen = "", io.popen + local pfile, err = popen("pwd") + + if not pfile or err ~= 0 then + game.Log(tostring(FileSys) .. ".getcwd() : popen failed executing " .. tostring(err), game.LOGGER_ERROR) + return nil + end + + cwd = pfile:read() + pfile:close() + + return cwd +end + +function FileSys.scandir(path) + local i, t, popen = 0, {}, io.popen + local pfile, err = popen('find "' .. path .. '" -maxdepth 1 -print0') + + if not pfile or err ~= 0 then + game.Log(tostring(FileSys) .. ".scandir() : popen failed executing " .. tostring(err), game.LOGGER_ERROR) + return nil + end + + for filename in pfile:lines() do + i = i + 1 + t[i] = filename + end + + pfile:close() + + return t +end diff --git a/scripts/api/platform/win/filesys.lua b/scripts/api/platform/win/filesys.lua new file mode 100644 index 0000000..826c3e4 --- /dev/null +++ b/scripts/api/platform/win/filesys.lua @@ -0,0 +1,46 @@ +local FileSys = require "api.platform.filesys_impl" + +FileSys.sep = "\\" + +function FileSys.getcwd() + local cwd, popen = "", io.popen + local pfile, err = popen("cd") + + if not pfile or err ~= 0 then + game.Log(tostring(FileSys) .. ".getcwd() : popen failed executing " .. tostring(err), game.LOGGER_ERROR) + return nil + end + + cwd = pfile:read() + pfile:close() + + return cwd +end + +local baseNormpath = FileSys.normpath +function FileSys.normpath(path) + path = baseNormpath(path) + + path = path:gsub("/", "\\") + + return path +end + +function FileSys.scandir(path) + local i, t, popen = 0, {}, io.popen + local pfile, err = popen('dir "' .. path .. '" /b /ad') + + if not pfile or err ~= 0 then + game.Log(tostring(FileSys) .. ".scandir() : popen failed executing " .. tostring(err), game.LOGGER_ERROR) + return nil + end + + for filename in pfile:lines() do + i = i + 1 + t[i] = filename + end + + pfile:close() + + return t +end diff --git a/scripts/common/globals.lua b/scripts/common/globals.lua index a2478e9..029b028 100644 --- a/scripts/common/globals.lua +++ b/scripts/common/globals.lua @@ -8,3 +8,15 @@ game.MOUSE_MIDDLE = 2 game.KNOB_LEFT = 0 game.KNOB_RIGHT = 1 + +-- some cool extensions to the builtins ---------------------------------------- + +---Looks for the last match of pattern in the string. +---@param s string +---@param pattern string +---@param init? integer +---@param plain? boolean +function string.rfind(s, pattern, init, plain) + pattern = pattern:reverse() + return s:len() - s:reverse():find(pattern, init, plain) + 1 +end