Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(utils/app): allow for global env var defaults #473

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions lua/pl/app.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ local path = require 'pl.path'

local app = {}


--- Sets/clears an environment variable default, to use with `utils.getenv`.
-- This links to `utils.setenv_default`.
-- @function setenv_default
-- @usage -- override the global with this implementation to control
-- -- environment variables with defaults on application level.
-- os.getenv = require("pl.utils").getenv
--
-- utils.setenv_default("MY_APP_CONFIG", "~/.my_app")
app.setenv_default = utils.setenv_default


--- return the name of the current script running.
-- The name will be the name as passed on the command line
-- @return string filename
Expand Down Expand Up @@ -69,17 +81,17 @@ function app.require_here (base, nofollow)
end

--- return a suitable path for files private to this application.
-- These will look like '~/.SNAME/file', with '~' as with expanduser and
-- SNAME is the name of the script without .lua extension.
-- These will look like `'~/.SNAME/file'`, with '~' expanded through `path.expanduser` and
-- `SNAME` is the filename of the script without `'.lua'` extension (see `script_name`).
-- If the directory does not exist, it will be created.
-- @string file a filename (w/out path)
-- @return a full pathname, or nil
-- @return cannot create directory error
-- @usage
-- -- when run from a script called 'testapp' (on Windows):
-- local app = require 'pl.app'
-- print(app.appfile 'test.txt')
-- -- C:\Documents and Settings\steve\.testapp\test.txt
-- print(app.appfile 'some\test.txt')
-- -- C:\Documents and Settings\steve\.testapp\some\test.txt
function app.appfile(file)
local sfullname, err = app.script_name()
if not sfullname then return utils.raise(err) end
Expand All @@ -94,7 +106,9 @@ function app.appfile(file)
end

--- return string indicating operating system.
-- @return 'Windows','OSX' or whatever uname returns (e.g. 'Linux')
-- @treturn[1] string 'Windows' on Windows platforms
-- @treturn[2] string 'OSX' on Apple platforms
-- @treturn[3] string whatever `uname` returns on other platforms (e.g. 'Linux')
function app.platform()
if path.is_windows then
return 'Windows'
Expand Down
2 changes: 1 addition & 1 deletion lua/pl/path.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
-- imports and locals
local _G = _G
local sub = string.sub
local getenv = os.getenv
local tmpnam = os.tmpname
local package = package
local append, concat, remove = table.insert, table.concat, table.remove
local utils = require 'pl.utils'
local getenv = utils.getenv
local assert_string,raise = utils.assert_string,utils.raise

local res,lfs = _G.pcall(_G.require,'lfs')
Expand Down
47 changes: 45 additions & 2 deletions lua/pl/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,49 @@ end
--- OS functions
-- @section OS-functions

do
local env_defaults = {} -- table to track defaults for env variables
local original_getenv = os.getenv -- in case the global function gets patched with this implementation


--- Gets an environment variable, whilst falling back to defaults. The defaults can
-- be set using `utils.setenv`. All Penlight modules will use this method to retrieve
-- environment variables.
-- The name is case-sensitive, except on Windows.
-- @tparam string name the environment variable to lookup.
-- @treturn[1] string the value retrieved
-- @treturn[2] nil if the variables wasn't found, and didn't have a default set
-- @see utils.setenv_default
-- @see app.setenv_default
function utils.getenv(name)
utils.assert_string(1, name)
name = is_windows and name:lower() or name
return original_getenv(name) or env_defaults[name]
end



--- Sets/clears an environment variable default, to use with `utils.getenv`.
-- The name is case-sensitive, except on Windows.
-- @tparam string name the environment variable name to set a default for.
-- @tparam[opt] string value the value to assign as a default, if `nil` the default will be cleared.
-- @return nothing
-- @see utils.getenv
-- @see app.setenv_default
function utils.setenv_default(name, value)
utils.assert_string(1, name)
name = is_windows and name:lower() or name
if value == nil then
env_defaults[name] = nil
else
utils.assert_string(1, value)
env_defaults[name] = value
end
end
end



--- execute a shell command and return the output.
-- This function redirects the output to tempfiles and returns the content of those files.
-- @param cmd a shell command
Expand All @@ -598,8 +641,8 @@ function utils.executeex(cmd, bin)
local errfile = os.tmpname()

if is_windows and not outfile:find(':') then
outfile = os.getenv('TEMP')..outfile
errfile = os.getenv('TEMP')..errfile
outfile = utils.getenv('TEMP')..outfile
errfile = utils.getenv('TEMP')..errfile
end
cmd = cmd .. " > " .. utils.quote_arg(outfile) .. " 2> " .. utils.quote_arg(errfile)

Expand Down
24 changes: 13 additions & 11 deletions spec/path_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,25 @@ end

describe("pl.path", function()

local path
local pl_path
local pl_utils
local mock_envs
local old_get_env

before_each(function()
pl_utils = require "pl.utils"
mock_envs = {}
old_get_env = os.getenv
os.getenv = function(name) -- luacheck: ignore
old_get_env = pl_utils.getenv
pl_utils.getenv = function(name) -- luacheck: ignore
return mock_envs[name]
end
package.loaded["pl.path"] = nil
path = require "pl.path"
pl_path = require "pl.path"
end)

after_each(function()
package.loaded["pl.path"] = nil
os.getenv = old_get_env -- luacheck: ignore
pl_utils.getenv = old_get_env -- luacheck: ignore
end)


Expand All @@ -46,15 +48,15 @@ describe("pl.path", function()
mock_envs = {
HOME = "/home/user",
}
assert.equal("/home/user/file", path.expanduser("~/file"))
assert.equal("/home/user/file", pl_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")}
{ pl_path.expanduser("~/file")}
)
end)

Expand All @@ -63,7 +65,7 @@ describe("pl.path", function()
mock_envs = {}
assert.same(
{ nil, "failed to expand '~' (HOME, USERPROFILE, and HOMEDRIVE and/or HOMEPATH not set)" },
{ path.expanduser("~/file")}
{ pl_path.expanduser("~/file")}
)
end)

Expand All @@ -75,7 +77,7 @@ describe("pl.path", function()
HOMEDRIVE = "C:",
HOMEPATH = "\\home\\user3",
}
assert.equal("\\home\\user1\\file", path.expanduser("~\\file"))
assert.equal("\\home\\user1\\file", pl_path.expanduser("~\\file"))
end)


Expand All @@ -86,7 +88,7 @@ describe("pl.path", function()
HOMEDRIVE = "C:",
HOMEPATH = "\\home\\user3",
}
assert.equal("\\home\\user2\\file", path.expanduser("~\\file"))
assert.equal("\\home\\user2\\file", pl_path.expanduser("~\\file"))
end)


Expand All @@ -97,7 +99,7 @@ describe("pl.path", function()
HOMEDRIVE = "C:",
HOMEPATH = "\\home\\user3",
}
assert.equal("C:\\home\\user3\\file", path.expanduser("~\\file"))
assert.equal("C:\\home\\user3\\file", pl_path.expanduser("~\\file"))
end)

end)
Expand Down
108 changes: 108 additions & 0 deletions spec/utils-getenv-setenv_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
local utils = require("pl.utils")

describe("pl.utils", function()

local TEST_NAME = "name" .. tostring(math.random(1000))

after_each(function()
utils.setenv_default(TEST_NAME, nil)
end)


describe("setenv_default", function ()

it("errors if name isn't a string", function()
assert.has.errors(function()
utils.setenv_default(nil, "value")
end)
assert.has.errors(function()
utils.setenv_default(123, "value")
end)
end)


it("doesn't error if name is a string", function()
assert.has.no.errors(function()
utils.setenv_default(TEST_NAME, "value")
end)
end)


it("errors if value isn't a string", function()
assert.has.errors(function()
utils.setenv_default(TEST_NAME, 123)
end)
assert.has.errors(function()
utils.setenv_default(123, "value")
end)
end)


it("doesn't error if value is nil or a string", function()
assert.has.no.errors(function()
utils.setenv_default(TEST_NAME, "value")
end)
assert.has.no.errors(function()
utils.setenv_default(TEST_NAME, nil)
end)
end)

end)



describe("getenv", function ()

it("errors if name isn't a string", function()
assert.has.errors(function()
utils.getenv(nil)
end)
assert.has.errors(function()
utils.getenv(123)
end)
end)


it("doesn't error if name is a string", function()
assert.has.no.errors(function()
utils.getenv(TEST_NAME)
end)
end)


it("returns values set by setenv_default", function()
utils.setenv_default(TEST_NAME, "value")
assert.equal(utils.getenv(TEST_NAME), "value")

utils.setenv_default(TEST_NAME, nil)
assert.is_nil(utils.getenv(TEST_NAME))
end)


it("returns defaults only as fallback", function()
-- PATH is set on all systems, so we use that to test
finally(function()
utils.setenv_default("PATH", nil)
end)

utils.setenv_default("PATH", "value")
assert.not_nil(utils.getenv("PATH"))
assert.not_equal(utils.getenv("PATH"), "value")
end)


if utils.is_windows then
it("is case-insensitive on Windows", function()
finally(function()
utils.setenv_default(TEST_NAME:upper(), nil)
end)

assert.is_nil(utils.getenv(TEST_NAME:lower())) -- verify it's unset first
utils.setenv_default(TEST_NAME:upper(), "value")
assert.equal(utils.getenv(TEST_NAME:lower()), "value")
end)
end

end)

end)
Loading