From 0408a4a31f5ebf9a9c780d77162a57336fd10db1 Mon Sep 17 00:00:00 2001 From: Lorenzo Cogotti Date: Sun, 2 Oct 2022 14:40:30 +0200 Subject: [PATCH] [strings] Add path utility functions, improve documentation. --- strings.lua | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/strings.lua b/strings.lua index a22e60d..2f00d1c 100644 --- a/strings.lua +++ b/strings.lua @@ -6,10 +6,69 @@ local strings = {} +-- Platform preferred path separator +local SEP = package.config:sub(1,1) + +--- Remove redundant slashes and resolve dot and dot-dots in path. +-- +-- @string path a file path +-- @string[opt] sep separator pattern, '/' for Unix, '\\' for Windows (default) +-- @string[optchain] osep target separator pattern, '/' for Unix, '\\' for Windows, uses the platform preferred path separator by default. +-- @treturn string cleared path +function strings.clearpath(path, sep, osep) + sep = sep or '\\' -- conservative, both / and \ as seps + osep = osep or SEP + + local dot, dotdot, sepsub, esub + if sep == '\\' then + -- Windows style separator pattern + dot, dotdot = '[\\/]+%.?[\\/]', '[^\\/]+[\\/]%.%.[\\/]?' + sepsub, esub = '[\\/]', '[\\/]$' + elseif sep == '/' then + -- Unix like separators only + dot, dotdot = '/+%.?/', '[^/]+/%.%./?' + sepsub, esub = '/', '/$' + else + error("Unsupported separator pattern: "..tostring(sep)) + end + if osep ~= '\\' and osep ~= '/' then + error("Unsupported target separator pattern: "..tostring(osep)) + end + + local k + + repeat -- /./ -> / + path,k = path:gsub(dot, osep, 1) + until k == 0 + + repeat -- A/../ -> (empty) + path,k = path:gsub(dotdot, '', 1) + until k == 0 + + -- Make separators consistent + path = path:gsub(sepsub, osep) + path = path:gsub(esub, '') -- never leave trailing separator + return path == '' and '.' or path +end + +--- Split path into components: directory, basename, extension. +-- +-- @string path path to be split +-- @treturn string directory name (including separator), '' if none was found in path +-- @treturn string file name without extension, '' if none was found in path +-- @treturn string file extension including '.', '' if none was found in path +function strings.splitpath(path) + return path:match("(.-)([^\\/]-)(%.?[^%.\\/]*)$") +end --- Test whether a string starts with a prefix. +-- +-- This is an optimized version of: return s:sub(1, #prefix) == prefix. +-- +-- @string s string to be tested +-- @string prefix prefix to test for +-- @treturn bool true if prefix is found, false otherwise function strings.startswith(s, prefix) - -- optimized version of: return s:sub(1, #prefix) == prefix for i = 1,#prefix do if s:byte(i) ~= prefix:byte(i) then return false @@ -19,8 +78,14 @@ function strings.startswith(s, prefix) end --- Test whether a string ends with a trailing suffix. +-- +-- This is an optimized version of: return trailing == "" +-- or s:sub(-#trailing) == trailing. +-- +-- @string s string to be tested +-- @string trailing suffix to test for +-- @treturn bool true if suffix is found, false otherwise function strings.endswith(s, trailing) - -- optimized version of: return trailing == "" or s:sub(-#trailing) == trailing local n1,n2 = #s,#trailing for i = 0,n2-1 do