diff --git a/strings.lua b/strings.lua index 2f00d1c..b2e9ea9 100644 --- a/strings.lua +++ b/strings.lua @@ -8,6 +8,100 @@ local strings = {} -- Platform preferred path separator local SEP = package.config:sub(1,1) +local DOT = string.byte('.') +local SLASH = string.byte('/') +local BACKSLASH = string.byte('\\') +local COLON = string.byte(':') + +--- Find file extension within path. +-- +-- @string path a file path +-- @string[opt] sep path separator pattern, any combination of '/', '\\' or ':' to support various OSes, defaults to all +-- @treturn number extension position within path if an extension is found, nil otherwise +function strings.findpathext(path, sep) + sep = sep or '/\\:' + for i = 1,#sep do + local byt = sep:byte(i) + if byt ~= SLASH and byt ~= BACKSLASH and byt ~= COLON then + error(("Unsupported path separator pattern: %q"):format(sep)) + end + end + + local function issep(byt) + for i = 1,#sep do + if byt == sep:byte(i) then return true end + end + end + + local pos = nil + + for i = #path,2,-1 do + local byt = path:byte(i) + + if byt == DOT and not issep(path:byte(i-1)) then + -- Update extension position + pos = i + elseif issep(byt) then + break + end + end + + return pos +end + +--- Set default file extension. +-- +-- If path contains an extension, returns the path unaltered, +-- otherwise set extension to the specified one. +-- +-- @string path a file path +-- @string ext default extension to be set +-- @string[opt] sep path separator pattern, any combination of '/', '\\' or ':' to support various OSes, defaults to all +-- @treturn string updated path, and separator position +function strings.setdefpathext(path, ext, sep) + if ext:byte(1) ~= DOT then + error(("Bad extension %q: must be a string starting with '.'"):format(ext)) + end + + local pos = strings.findpathext(path, sep) + if not pos then + -- Append default extension + pos = #path + path = path..ext + end + + return path, pos +end + +--- Set file extension. +-- +-- @string path a file path +-- @string ext extension to be set (including '.') +-- @string[opt] sep path separator pattern, any combination of '/', '\\' or ':' to support various OSes, defaults to all +-- @treturn string updated path and separator position +function strings.setpathext(path, ext, sep) + if ext:byte(1) ~= DOT then + error(("Bad extension %q: must be a string starting with '.'"):format(ext)) + end + + local pos = strings.findpathext(path, sep) + if pos then + -- Trim existing extension + path = path:sub(1, pos-1) + end + + return path..ext, pos +end + +--- Get file extension. +-- +-- @string path a file path +-- @string[opt] sep path separator pattern, any combination of '/', '\\' or ':' to support various OSes, defaults to all +-- @treturn string file extension and position, if any is found, nil otherwise +function strings.getpathext(path, sep) + local pos = strings.findpathext(path, sep) + if pos then return path:sub(pos), pos end +end --- Remove redundant slashes and resolve dot and dot-dots in path. --