From becff0191038324799009f8e38a32f711df87b1d Mon Sep 17 00:00:00 2001 From: Jason Perkins Date: Tue, 16 Sep 2014 11:40:47 -0400 Subject: Backport native path functions from Premake-dev --- src/base/path.lua | 182 +++++--------------------------------------- src/host/os_getcwd.c | 34 +++++---- src/host/path_getabsolute.c | 102 +++++++++++++++++++++++++ src/host/path_getrelative.c | 80 +++++++++++++++++++ src/host/path_isabsolute.c | 25 +++--- src/host/path_join.c | 58 ++++++++++++++ src/host/path_normalize.c | 77 +++++++++++++++++++ src/host/path_translate.c | 61 +++++++++++++++ src/host/premake.c | 5 ++ src/host/premake.h | 10 +++ tests/base/test_path.lua | 56 +++++++------- 11 files changed, 474 insertions(+), 216 deletions(-) create mode 100644 src/host/path_getabsolute.c create mode 100644 src/host/path_getrelative.c create mode 100644 src/host/path_join.c create mode 100644 src/host/path_normalize.c create mode 100644 src/host/path_translate.c diff --git a/src/base/path.lua b/src/base/path.lua index 14d00f6..508e8be 100644 --- a/src/base/path.lua +++ b/src/base/path.lua @@ -5,43 +5,6 @@ -- --- --- Get the absolute file path from a relative path. The requested --- file path doesn't actually need to exist. --- - - function path.getabsolute(p) - -- normalize the target path - p = path.translate(p, "/") - if (p == "") then p = "." end - - -- if the directory is already absolute I don't need to do anything - local result = iif (path.isabsolute(p), nil, os.getcwd()) - - -- split up the supplied relative path and tackle it bit by bit - for n, part in ipairs(p:explode("/", true)) do - if (part == "" and n == 1) then - result = "/" - elseif (part == "..") then - result = path.getdirectory(result) - elseif (part ~= ".") then - -- Environment variables embedded in the path need to be treated - -- as relative paths; path.join() makes them absolute - if (part:startswith("$") and n > 1) then - result = result .. "/" .. part - else - result = path.join(result, part) - end - end - end - - -- if I end up with a trailing slash remove it - result = iif(result:endswith("/"), result:sub(1, -2), result) - - return result - end - - -- -- Retrieve the filename portion of a path, without any extension. -- @@ -55,10 +18,10 @@ return name end end - - + + -- --- Retrieve the directory portion of a path, or an empty string if +-- Retrieve the directory portion of a path, or an empty string if -- the path does not include a directory. -- @@ -99,9 +62,9 @@ return "" end end - - - + + + -- -- Retrieve the filename portion of a path. -- @@ -114,71 +77,7 @@ return p end end - - --- --- Returns the relative path from src to dest. --- - - function path.getrelative(src, dst) - -- normalize the two paths - src = path.getabsolute(src) - dst = path.getabsolute(dst) - - -- same directory? - if (src == dst) then - return "." - end - - -- dollar macro? Can't tell what the real path is; use absolute - -- This enables paths like $(SDK_ROOT)/include to work correctly. - if dst:startswith("$") then - return dst - end - - src = src .. "/" - dst = dst .. "/" - - -- find the common leading directories - local idx = 0 - while (true) do - local tst = src:find("/", idx + 1, true) - if tst then - if src:sub(1,tst) == dst:sub(1,tst) then - idx = tst - else - break - end - else - break - end - end - - -- if they have nothing in common return absolute path - local first = src:find("/", 0, true) - if idx <= first then - return dst:sub(1, -2) - end - - -- trim off the common directories from the front - src = src:sub(idx + 1) - dst = dst:sub(idx + 1) - - -- back up from dst to get to this common parent - local result = "" - idx = src:find("/") - while (idx) do - result = result .. "../" - idx = src:find("/", idx + 1) - end - - -- tack on the path down to the dst from here - result = result .. dst - -- remove the trailing slash - return result:sub(1, -2) - end - -- -- Returns true if the filename represents a C/C++ source code file. This check @@ -191,13 +90,13 @@ local ext = path.getextension(fname):lower() return table.contains(extensions, ext) end - + function path.iscppfile(fname) local extensions = { ".cc", ".cpp", ".cxx", ".c", ".s", ".m", ".mm" } local ext = path.getextension(fname):lower() return table.contains(extensions, ext) end - + function path.iscppheader(fname) local extensions = { ".h", ".hh", ".hpp", ".hxx" } local ext = path.getextension(fname):lower() @@ -217,40 +116,6 @@ return table.contains(extensions, ext) end - --- --- Join one or more pieces of a path together into a single path. --- --- @param ... --- One or more path strings. --- @return --- The joined path. --- - - function path.join(...) - local numargs = select("#", ...) - if numargs == 0 then - return ""; - end - - local allparts = {} - for i = numargs, 1, -1 do - local part = select(i, ...) - if part and #part > 0 and part ~= "." then - -- trim off trailing slashes - while part:endswith("/") do - part = part:sub(1, -2) - end - - table.insert(allparts, 1, part) - if path.isabsolute(part) then - break - end - end - end - - return table.concat(allparts, "/") - end -- @@ -263,31 +128,24 @@ p = path.getrelative(newbase, p) return p end - - + + -- -- Convert the separators in a path from one form to another. If `sep` -- is nil, then a platform-specific separator is used. -- + local builtin_translate = path.translate + function path.translate(p, sep) - if (type(p) == "table") then - local result = { } - for _, value in ipairs(p) do - table.insert(result, path.translate(value)) - end - return result - else - if (not sep) then - if (os.is("windows")) then - sep = "\\" - else - sep = "/" - end + if not sep then + if os.is("windows") then + sep = "\\" + else + sep = "/" end - local result = p:gsub("[/\\]", sep) - return result end + return builtin_translate(p, sep) end @@ -309,10 +167,10 @@ -- have competing star replacements to worry about pattern = pattern:gsub("%*%*", "\001") pattern = pattern:gsub("%*", "\002") - + -- Replace the placeholders with their Lua patterns pattern = pattern:gsub("\001", ".*") pattern = pattern:gsub("\002", "[^/]*") - + return pattern end diff --git a/src/host/os_getcwd.c b/src/host/os_getcwd.c index 06c91a2..de8bfbd 100644 --- a/src/host/os_getcwd.c +++ b/src/host/os_getcwd.c @@ -9,26 +9,28 @@ int os_getcwd(lua_State* L) { char buffer[0x4000]; - char* ch; + if (do_getcwd(buffer, 0x4000)) { + lua_pushstring(L, buffer); + return 1; + } + else { + return 0; + } +} + + +int do_getcwd(char* buffer, size_t size) +{ int result; #if PLATFORM_WINDOWS - result = (GetCurrentDirectory(0x4000, buffer) != 0); + result = (GetCurrentDirectory(size, buffer) != 0); + if (result) { + do_translate(buffer, '/'); + } #else - result = (getcwd(buffer, 0x4000) != 0); + result = (getcwd(buffer, size) != 0); #endif - if (!result) - return 0; - - /* convert to platform-neutral directory separators */ - for (ch = buffer; *ch != '\0'; ++ch) - { - if (*ch == '\\') *ch = '/'; - } - - lua_pushstring(L, buffer); - return 1; + return result; } - - diff --git a/src/host/path_getabsolute.c b/src/host/path_getabsolute.c new file mode 100644 index 0000000..36b1c3a --- /dev/null +++ b/src/host/path_getabsolute.c @@ -0,0 +1,102 @@ +/** + * \file path_getabsolute.c + * \brief Returns an absolute version of a relative path. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +void do_getabsolute(char* result, const char* value, const char* relative_to) +{ + int i; + char* ch; + char* prev; + char buffer[0x4000] = { '\0' }; + + /* if the path is not already absolute, base it on working dir */ + if (!do_isabsolute(value)) { + if (relative_to) { + strcpy(buffer, relative_to); + } + else { + do_getcwd(buffer, 0x4000); + } + strcat(buffer, "/"); + } + + /* normalize the path */ + strcat(buffer, value); + do_translate(buffer, '/'); + + /* process it part by part */ + result[0] = '\0'; + if (buffer[0] == '/') { + strcat(result, "/"); + } + + prev = NULL; + ch = strtok(buffer, "/"); + while (ch) { + /* remove ".." where I can */ + if (strcmp(ch, "..") == 0 && (prev == NULL || (prev[0] != '$' && strcmp(prev, "..") != 0))) { + i = strlen(result) - 2; + while (i >= 0 && result[i] != '/') { + --i; + } + if (i >= 0) { + result[i + 1] = '\0'; + } + ch = NULL; + } + + /* allow everything except "." */ + else if (strcmp(ch, ".") != 0) { + strcat(result, ch); + strcat(result, "/"); + } + + prev = ch; + ch = strtok(NULL, "/"); + } + + /* remove trailing slash */ + i = strlen(result) - 1; + if (result[i] == '/') { + result[i] = '\0'; + } +} + + +int path_getabsolute(lua_State* L) +{ + const char* relative_to; + char buffer[0x4000]; + + relative_to = NULL; + if (lua_gettop(L) > 1 && !lua_isnil(L,2)) { + relative_to = luaL_checkstring(L, 2); + } + + if (lua_istable(L, 1)) { + int i = 0; + lua_newtable(L); + lua_pushnil(L); + while (lua_next(L, 1)) { + const char* value = luaL_checkstring(L, -1); + do_getabsolute(buffer, value, relative_to); + lua_pop(L, 1); + + lua_pushstring(L, buffer); + lua_rawseti(L, -3, ++i); + } + return 1; + } + else { + const char* value = luaL_checkstring(L, 1); + do_getabsolute(buffer, value, relative_to); + lua_pushstring(L, buffer); + return 1; + } +} diff --git a/src/host/path_getrelative.c b/src/host/path_getrelative.c new file mode 100644 index 0000000..dc6629b --- /dev/null +++ b/src/host/path_getrelative.c @@ -0,0 +1,80 @@ +/** + * \file path_getrelative.c + * \brief Returns a path relative to another. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +int path_getrelative(lua_State* L) +{ + int i, last, count; + char src[0x4000]; + char dst[0x4000]; + + const char* p1 = luaL_checkstring(L, 1); + const char* p2 = luaL_checkstring(L, 2); + + /* normalize the paths */ + do_getabsolute(src, p1, NULL); + do_getabsolute(dst, p2, NULL); + + /* same directory? */ + if (strcmp(src, dst) == 0) { + lua_pushstring(L, "."); + return 1; + } + + /* dollar macro? Can't tell what the real path might be, so treat + * as absolute. This enables paths like $(SDK_ROOT)/include to + * work as expected. */ + if (dst[0] == '$') { + lua_pushstring(L, dst); + return 1; + } + + /* find the common leading directories */ + strcat(src, "/"); + strcat(dst, "/"); + + last = -1; + i = 0; + while (src[i] && dst[i] && src[i] == dst[i]) { + if (src[i] == '/') { + last = i; + } + ++i; + } + + /* if I end up with just the root of the filesystem, either a single + * slash (/) or a drive letter (c:) then return the absolute path. */ + if (last <= 0 || (last == 2 && src[1] == ':')) { + dst[strlen(dst) - 1] = '\0'; + lua_pushstring(L, dst); + return 1; + } + + /* count remaining levels in src */ + count = 0; + for (i = last + 1; src[i] != '\0'; ++i) { + if (src[i] == '/') { + ++count; + } + } + + /* start my result by backing out that many levels */ + src[0] = '\0'; + for (i = 0; i < count; ++i) { + strcat(src, "../"); + } + + /* append what's left */ + strcat(src, dst + last + 1); + + /* remove trailing slash and done */ + src[strlen(src) - 1] = '\0'; + lua_pushstring(L, src); + return 1; +} diff --git a/src/host/path_isabsolute.c b/src/host/path_isabsolute.c index 961ed77..7412935 100644 --- a/src/host/path_isabsolute.c +++ b/src/host/path_isabsolute.c @@ -1,7 +1,7 @@ /** * \file path_isabsolute.c * \brief Determines if a path is absolute or relative. - * \author Copyright (c) 2002-2009 Jason Perkins and the Premake project + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project */ #include "premake.h" @@ -10,13 +10,18 @@ int path_isabsolute(lua_State* L) { const char* path = luaL_checkstring(L, -1); - if (path[0] == '/' || path[0] == '\\' || path[0] == '$' || (path[0] != '\0' && path[1] == ':')) - { - lua_pushboolean(L, 1); - return 1; - } - else - { - return 0; - } + lua_pushboolean(L, do_isabsolute(path)); + return 1; +} + + +int do_isabsolute(const char* path) +{ + return ( + path[0] == '/' || + path[0] == '\\' || + path[0] == '$' || + (path[0] == '"' && path[1] == '$') || + (path[0] != '\0' && path[1] == ':') + ); } diff --git a/src/host/path_join.c b/src/host/path_join.c new file mode 100644 index 0000000..7d80104 --- /dev/null +++ b/src/host/path_join.c @@ -0,0 +1,58 @@ +/** + * \file path_join.c + * \brief Join two or more pieces of a file system path. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +int path_join(lua_State* L) +{ + int i, len; + const char* part; + char buffer[0x4000]; + char* ptr = buffer; + + /* for each argument... */ + int argc = lua_gettop(L); + for (i = 1; i <= argc; ++i) { + /* if next argument is nil, skip it */ + if (lua_isnil(L, i)) { + continue; + } + + /* grab the next argument */ + part = luaL_checkstring(L, i); + len = strlen(part); + + /* remove trailing slashes */ + while (len > 1 && part[len - 1] == '/') { + --len; + } + + /* ignore empty segments and "." */ + if (len == 0 || (len == 1 && part[0] == '.')) { + continue; + } + + /* if I encounter an absolute path, restart my result */ + if (do_isabsolute(part)) { + ptr = buffer; + } + + /* if the path is already started, split parts */ + if (ptr != buffer && *(ptr - 1) != '/') { + *(ptr++) = '/'; + } + + /* append new part */ + strcpy(ptr, part); + ptr += len; + } + + *ptr = '\0'; + lua_pushstring(L, buffer); + return 1; +} diff --git a/src/host/path_normalize.c b/src/host/path_normalize.c new file mode 100644 index 0000000..64c5cf2 --- /dev/null +++ b/src/host/path_normalize.c @@ -0,0 +1,77 @@ +/** + * \file path_normalize.c + * \brief Removes any weirdness from a file system path string. + * \author Copyright (c) 2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +int path_normalize(lua_State* L) +{ + char buffer[0x4000]; + char* src; + char* dst; + char last; + + const char* path = luaL_checkstring(L, 1); + strcpy(buffer, path); + + src = buffer; + dst = buffer; + last = '\0'; + + while (*src != '\0') { + char ch = (*src); + + /* make sure we're using '/' for all separators */ + if (ch == '\\') { + ch = '/'; + } + + /* add to the result, filtering out duplicate slashes */ + if (ch != '/' || last != '/') { + *(dst++) = ch; + } + + /* ...except at the start of a string, for UNC paths */ + if (src != buffer) { + last = (*src); + } + + ++src; + } + + /* remove any trailing slashes */ + for (--src; src > buffer && *src == '/'; --src) { + *src = '\0'; + } + + /* remove any leading "./" sequences */ + src = buffer; + while (strncmp(src, "./", 2) == 0) { + src += 2; + } + + *dst = '\0'; + lua_pushstring(L, src); + return 1; +} + + +/* Call the scripted path.normalize(), to allow for overrides */ +void do_normalize(lua_State* L, char* buffer, const char* path) +{ + int top = lua_gettop(L); + + lua_getglobal(L, "path"); + lua_getfield(L, -1, "normalize"); + lua_pushstring(L, path); + lua_call(L, 1, 1); + + path = luaL_checkstring(L, -1); + strcpy(buffer, path); + + lua_settop(L, top); +} diff --git a/src/host/path_translate.c b/src/host/path_translate.c new file mode 100644 index 0000000..8996b37 --- /dev/null +++ b/src/host/path_translate.c @@ -0,0 +1,61 @@ +/** + * \file path_translate.c + * \brief Translates between path separators. + * \author Copyright (c) 2002-2013 Jason Perkins and the Premake project + */ + +#include "premake.h" +#include + + +void do_translate(char* value, const char sep) +{ + char* ch; + for (ch = value; *ch != '\0'; ++ch) { + if (*ch == '/' || *ch == '\\') { + *ch = sep; + } + } +} + + +static void translate(char* result, const char* value, const char sep) +{ + strcpy(result, value); + do_translate(result, sep); +} + + +int path_translate(lua_State* L) +{ + const char* sep; + char buffer[0x4000]; + + if (lua_gettop(L) == 1) { + sep = "\\"; + } + else { + sep = luaL_checkstring(L, 2); + } + + if (lua_istable(L, 1)) { + int i = 0; + lua_newtable(L); + lua_pushnil(L); + while (lua_next(L, 1)) { + const char* value = luaL_checkstring(L, 4); + translate(buffer, value, sep[0]); + lua_pop(L, 1); + + lua_pushstring(L, buffer); + lua_rawseti(L, -3, ++i); + } + return 1; + } + else { + const char* value = luaL_checkstring(L, 1); + translate(buffer, value, sep[0]); + lua_pushstring(L, buffer); + return 1; + } +} diff --git a/src/host/premake.c b/src/host/premake.c index ac27566..f529c41 100755 --- a/src/host/premake.c +++ b/src/host/premake.c @@ -35,7 +35,12 @@ extern const char* builtin_scripts[]; /* Built-in functions */ static const luaL_Reg path_functions[] = { + { "getabsolute", path_getabsolute }, + { "getrelative", path_getrelative }, { "isabsolute", path_isabsolute }, + { "join", path_join }, + { "normalize", path_normalize }, + { "translate", path_translate }, { NULL, NULL } }; diff --git a/src/host/premake.h b/src/host/premake.h index 1e69a89..ef7d3fc 100755 --- a/src/host/premake.h +++ b/src/host/premake.h @@ -47,11 +47,21 @@ /* Bootstrapping helper functions */ +void do_getabsolute(char* result, const char* value, const char* relative_to); +int do_getcwd(char* buffer, size_t size); +int do_isabsolute(const char* path); int do_isfile(const char* filename); +void do_normalize(lua_State* L, char* buffer, const char* path); +void do_translate(char* value, const char sep); /* Built-in functions */ +int path_getabsolute(lua_State* L); +int path_getrelative(lua_State* L); int path_isabsolute(lua_State* L); +int path_join(lua_State* L); +int path_normalize(lua_State* L); +int path_translate(lua_State* L); int os_chdir(lua_State* L); int os_copyfile(lua_State* L); int os_getcwd(lua_State* L); diff --git a/tests/base/test_path.lua b/tests/base/test_path.lua index 2332dce..46e3181 100644 --- a/tests/base/test_path.lua +++ b/tests/base/test_path.lua @@ -24,26 +24,26 @@ function suite.getabsolute_RemovesDotDots_OnPosixAbsolute() test.isequal("/ProjectB/bin", path.getabsolute("/ProjectA/../ProjectB/bin")) end - + function suite.getabsolute_OnTrailingSlash() local expected = path.translate(os.getcwd(), "/") .. "/a/b/c" test.isequal(expected, path.getabsolute("a/b/c/")) end - + function suite.getabsolute_OnLeadingEnvVar() test.isequal("$(HOME)/user", path.getabsolute("$(HOME)/user")) end - - function suite.getabsolute_OnMultipleEnvVar() + + function suite.getabsolute_OnMultipleEnvVar() test.isequal("$(HOME)/$(USER)", path.getabsolute("$(HOME)/$(USER)")) end - + function suite.getabsolute_OnTrailingEnvVar() local expected = path.translate(os.getcwd(), "/") .. "/home/$(USER)" test.isequal(expected, path.getabsolute("home/$(USER)")) end - - + + -- -- path.getbasename() tests -- @@ -60,11 +60,11 @@ function suite.getdirectory_ReturnsEmptyString_OnNoDirectory() test.isequal(".", path.getdirectory("filename.ext")) end - + function suite.getdirectory_ReturnsDirectory_OnSingleLevelPath() test.isequal("dir0", path.getdirectory("dir0/filename.ext")) end - + function suite.getdirectory_ReturnsDirectory_OnMultiLeveLPath() test.isequal("dir0/dir1/dir2", path.getdirectory("dir0/dir1/dir2/filename.ext")) end @@ -72,7 +72,7 @@ function suite.getdirectory_ReturnsRootPath_OnRootPathOnly() test.isequal("/", path.getdirectory("/filename.ext")) end - + -- @@ -82,13 +82,13 @@ function suite.getdrive_ReturnsNil_OnNotWindows() test.isnil(path.getdrive("/hello")) end - + function suite.getdrive_ReturnsLetter_OnWindowsAbsolute() test.isequal("x", path.getdrive("x:/hello")) end - - - + + + -- -- path.getextension() tests -- @@ -100,19 +100,19 @@ function suite.getextension_ReturnsExtension() test.isequal(".txt", path.getextension("filename.txt")) end - + function suite.getextension_OnMultipleDots() test.isequal(".txt", path.getextension("filename.mod.txt")) end - + function suite.getextension_OnLeadingNumeric() test.isequal(".7z", path.getextension("filename.7z")) end - + function suite.getextension_OnUnderscore() test.isequal(".a_c", path.getextension("filename.a_c")) end - + function suite.getextension_OnHyphen() test.isequal(".a-c", path.getextension("filename.a-c")) end @@ -130,7 +130,7 @@ function suite.getrelative_ReturnsDoubleDot_OnChildToParent() test.isequal("..", path.getrelative("/a/b/c", "/a/b")) end - + function suite.getrelative_ReturnsDoubleDot_OnSiblingToSibling() test.isequal("../d", path.getrelative("/a/b/c", "/a/b/d")) end @@ -142,19 +142,19 @@ function suite.getrelative_ReturnsChildPath_OnWindowsAbsolute() test.isequal("obj/debug", path.getrelative("C:/Code/Premake4", "C:/Code/Premake4/obj/debug")) end - + function suite.getrelative_ReturnsAbsPath_OnDifferentDriveLetters() test.isequal("D:/Files", path.getrelative("C:/Code/Premake4", "D:/Files")) end - + function suite.getrelative_ReturnsAbsPath_OnDollarMacro() test.isequal("$(SDK_HOME)/include", path.getrelative("C:/Code/Premake4", "$(SDK_HOME)/include")) end - + function suite.getrelative_ReturnsAbsPath_OnRootedPath() test.isequal("/opt/include", path.getrelative("/home/me/src/project", "/opt/include")) end - + -- -- path.isabsolute() tests @@ -171,7 +171,7 @@ function suite.isabsolute_ReturnsFalse_OnRelativePath() test.isfalse(path.isabsolute("a/b/c")) end - + function suite.isabsolute_ReturnsTrue_OnDollarSign() test.istrue(path.isabsolute("$(SDK_HOME)/include")) end @@ -184,11 +184,11 @@ function suite.join_OnValidParts() test.isequal("p1/p2", path.join("p1", "p2")) end - + function suite.join_OnAbsoluteUnixPath() test.isequal("/p2", path.join("p1", "/p2")) end - + function suite.join_OnAbsoluteWindowsPath() test.isequal("C:/p2", path.join("p1", "C:/p2")) end @@ -196,11 +196,11 @@ function suite.join_OnCurrentDirectory() test.isequal("p2", path.join(".", "p2")) end - + function suite.join_OnNilSecondPart() test.isequal("p1", path.join("p1", nil)) end - + function suite.join_onMoreThanTwoParts() test.isequal("p1/p2/p3", path.join("p1", "p2", "p3")) end -- cgit v1.2.3