diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bef8de5..67495819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ deprecation policy. see [CONTRIBUTING.md](CONTRIBUTING.md#release-instructions-for-a-new-version) for release instructions ## 1.14.0 (unreleased) + - fix(path): make `path.expanduser` more sturdy + [#469](https://github.com/lunarmodules/Penlight/pull/469) - feat(func): extend `compose` to support N functions [#448](https://github.com/lunarmodules/Penlight/pull/448) - fix(utils) `nil` values in `utils.choose(cond, val1, val2)` diff --git a/lua/pl/path.lua b/lua/pl/path.lua index 1e044e11..600bf057 100644 --- a/lua/pl/path.lua +++ b/lua/pl/path.lua @@ -493,17 +493,35 @@ end -- In windows, if HOME isn't set, then USERPROFILE is used in preference to -- HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows. -- @string P A file path +-- @treturn[1] string The file path with the `~` prefix substituted, or the input path if it had no prefix. +-- @treturn[2] nil +-- @treturn[2] string Error message if the environment variables were unavailable. function path.expanduser(P) assert_string(1,P) - if at(P,1) == '~' then - local home = getenv('HOME') - if not home then -- has to be Windows - home = getenv 'USERPROFILE' or (getenv 'HOMEDRIVE' .. getenv 'HOMEPATH') - end - return home..sub(P,2) - else + if P:sub(1,1) ~= '~' then return P end + + local home = getenv('HOME') + if (not home) and (not path.is_windows) then + -- no more options to try on Nix + return nil, "failed to expand '~' (HOME not set)" + end + + if (not home) then + -- try alternatives on Windows + home = getenv 'USERPROFILE' + if not home then + local hd = getenv 'HOMEDRIVE' + local hp = getenv 'HOMEPATH' + if not (hd and hp) then + return nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)" + end + home = hd..hp + end + end + + return home..sub(P,2) end diff --git a/spec/path_spec.lua b/spec/path_spec.lua new file mode 100644 index 00000000..7f18f29d --- /dev/null +++ b/spec/path_spec.lua @@ -0,0 +1,106 @@ + +-- conditional it/pending blocks per platform +local function nix_it(desc, ...) + if package.config:sub(1,1) == "\\" then + pending("Skip test on Windows: " .. desc, ...) + else + it(desc, ...) + end +end +local function win_it(desc, ...) + if package.config:sub(1,1) == "\\" then + it(desc, ...) + else + pending("Skip test on Unix: " .. desc, ...) + end +end + + + +describe("pl.path", function() + + local path + local mock_envs + local old_get_env + + before_each(function() + mock_envs = {} + old_get_env = os.getenv + os.getenv = function(name) -- luacheck: ignore + return mock_envs[name] + end + package.loaded["pl.path"] = nil + path = require "pl.path" + end) + + after_each(function() + package.loaded["pl.path"] = nil + os.getenv = old_get_env -- luacheck: ignore + end) + + + + describe("expanduser()", function() + + it("should expand ~ to the user's home directory", function() + mock_envs = { + HOME = "/home/user", + } + assert.equal("/home/user/file", path.expanduser("~/file")) + end) + + + nix_it("returns an error if expansion fails: HOME not set", function() + mock_envs = {} + assert.same( + { nil, "failed to expand '~' (HOME not set)" }, + { path.expanduser("~/file")} + ) + end) + + + win_it("returns an error if expansion fails: all Windows vars", function() + mock_envs = {} + assert.same( + { nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)" }, + { path.expanduser("~/file")} + ) + end) + + + win_it("HOME is first in line", function() + mock_envs = { + HOME = "\\home\\user1", + USERPROFILE = "\\home\\user2", + HOMEDRIVE = "C:", + HOMEPATH = "\\home\\user3", + } + assert.equal("\\home\\user1\\file", path.expanduser("~\\file")) + end) + + + win_it("USERPROFILE is second in line", function() + mock_envs = { + --HOME = "\\home\\user1", + USERPROFILE = "\\home\\user2", + HOMEDRIVE = "C:", + HOMEPATH = "\\home\\user3", + } + assert.equal("\\home\\user2\\file", path.expanduser("~\\file")) + end) + + + win_it("HOMEDRIVE/PATH is third in line", function() + mock_envs = { + -- HOME = "\\home\\user1", + -- USERPROFILE = "\\home\\user2", + HOMEDRIVE = "C:", + HOMEPATH = "\\home\\user3", + } + assert.equal("C:\\home\\user3\\file", path.expanduser("~\\file")) + end) + + end) + +end) + diff --git a/tests/test-path.lua b/tests/test-path.lua index 2368b388..8023270a 100644 --- a/tests/test-path.lua +++ b/tests/test-path.lua @@ -179,8 +179,6 @@ do -- path.relpath end --- TODO: path.expanduser - -- TODO: path.tmpname