Compare commits

..

2 Commits

Author SHA1 Message Date
James Trew
0af91129ed refactor(fs.normalize): ~ expansion 2024-09-09 21:04:59 -04:00
James Trew
6948948fa2 fix(fs.normalize): improve environment variable expansion
Adds Windows support.
2024-09-09 21:04:59 -04:00
3 changed files with 130 additions and 26 deletions

View File

@ -23,7 +23,7 @@ env:
NVIM_LOG_FILE: ${{ github.workspace }}/build/.nvimlog
TSAN_OPTIONS: log_path=${{ github.workspace }}/build/log/tsan
VALGRIND_LOG: ${{ github.workspace }}/build/log/valgrind-%p.log
# TEST_FILE: test/functional/core/startup_spec.lua
TEST_FILE: test/functional/lua/fs_spec.lua
# TEST_FILTER: foo
jobs:

View File

@ -489,23 +489,108 @@ end
---@param path string
---@return string
local function windows_env_expand(path)
-- Variables have a percent sign on both sides: %ThisIsAVariable%
-- The variable name can include spaces, punctuation and mixed case:
-- %_Another Ex.ample%
-- But they aren't case sensitive
--
-- A variable name may include any of the following characters:
-- A-Z, a-z, 0-9, # $ ' ( ) * + , - . ? @ [ ] _ { } ~
-- The first character of the name must not be numeric.
local pattern = "%%([A-Za-z#$'()*+,%-.?@[%]_{}~ ][A-Za-z0-9#$'()*+,%-.?@[%]_{}~ ]*%)%"
return (path:gsub(pattern, vim.uv.os_getenv):gsub('\\', '/'))
local res = {}
local i = 1
---@param var string
---@param end_ number
local function add_expand(var, end_)
local val = vim.uv.os_getenv(var)
if val then
table.insert(res, (val:gsub('\\', '/')))
else
table.insert(res, path:sub(i, end_))
end
end
while i <= #path do
local ch = path:sub(i, i)
if ch == "'" then -- no expansion inside single quotes
local end_ = path:find("'", i + 1, true)
if end_ then
table.insert(res, path:sub(i, end_))
i = end_
else
table.insert(res, ch)
end
elseif ch == '%' then
local end_ = path:find('%', i + 1, true)
if end_ then
local var = path:sub(i + 1, end_ - 1)
add_expand(var, end_)
i = end_
else
table.insert(res, ch)
end
elseif ch == '$' then
local nextch = path:sub(i + 1, i + 1)
if nextch == '$' then
i = i + 1
table.insert(res, ch)
elseif nextch == '{' then
local end_ = path:find('}', i + 2, true)
if end_ then
local var = path:sub(i + 2, end_ - 1)
add_expand(var, end_)
i = end_
else
table.insert(res, ch)
end
else
local end_ = path:find('[^%w_]', i + 1, false) or #path + 1
local var = path:sub(i + 1, end_ - 1)
add_expand(var, end_ - 1)
i = end_ - 1
end
else
table.insert(res, ch)
end
i = i + 1
end
return table.concat(res)
end
---@param path string
---@return string
local function posix_env_expand(path)
local pattern = '%$([A-Za-z_][A-Za-z0-9_]*)'
return (path:gsub(pattern, vim.uv.os_getenv))
local res = {}
local i = 1
local function add_expand(var, end_)
local val = vim.uv.os_getenv(var)
if val then
table.insert(res, val)
else
table.insert(res, path:sub(i, end_))
end
end
while i <= #path do
local ch = path:sub(i, i)
if ch == '$' then
if path:sub(i + 1, i + 1) == '{' then
local end_ = path:find('}', i + 2, true)
if end_ then
local var = path:sub(i + 2, end_ - 1)
add_expand(var, end_)
i = end_
else
table.insert(res, ch)
end
else
local end_ = path:find('[^%w_]', i + 1, false) or #path + 1
local var = path:sub(i + 1, end_ - 1)
add_expand(var, end_ - 1)
i = end_ - 1
end
else
table.insert(res, ch)
end
i = i + 1
end
return table.concat(res)
end
--- expand ~/ in a path
@ -529,7 +614,11 @@ end
--- @class vim.fs.normalize.Opts
--- @inlinedoc
---
--- Expand environment variables.
--- Expand environment variables. Substrings in the form of `$VAR` or `${VAR}` are replaced with the
--- value of the environment variable `VAR`. If the environment variable is not set, the substring
--- is left unchanged.
--- On Windows, substrings in the form of `%VAR%` are also replaced unless they are inside single
--- quotes (eg. `'%VAR%'`).
--- (default: `true`)
--- @field expand_env? boolean
---

View File

@ -456,26 +456,41 @@ describe('vim.fs', function()
describe('expands env vars', function()
before_each(function()
vim.uv.os_setenv('FOOVAR', 'foo')
vim.uv.os_setenv('_BARVAR_42', 'bar')
vim.uv.os_setenv('W!NDOW$_CR@ZY', 'windows')
vim.uv.os_setenv('{foovar', 'foo1')
vim.uv.os_setenv('{foovar}', 'foo\\2')
end)
after_each(function()
vim.uv.os_unsetenv('FOOVAR')
vim.uv.os_unsetenv('_BARVAR_42')
vim.uv.os_unsetenv('W!NDOW$_CR@ZY')
vim.uv.os_unsetenv('{foovar')
vim.uv.os_unsetenv('{foovar}')
end)
it('', function()
eq('foo', vim.fs.normalize('$FOOVAR'))
eq('foo/foo', vim.fs.normalize('$FOOVAR/$FOOVAR'))
eq('foo foo', vim.fs.normalize('$FOOVAR $FOOVAR'))
eq('foo$', vim.fs.normalize('$FOOVAR$'))
eq('foo', vim.fs.normalize('${FOOVAR}'))
eq('foo$', vim.fs.normalize('${FOOVAR}$'))
eq('foo/foo', vim.fs.normalize('${FOOVAR}/${FOOVAR}'))
eq('foo foo', vim.fs.normalize('${FOOVAR} ${FOOVAR}'))
eq('${FOO$VAR}', vim.fs.normalize('${FOO$VAR}'))
eq('foo/$/bar$', vim.fs.normalize('foo/$/bar$'))
eq('foo/${}/bar${}', vim.fs.normalize('foo/${}/bar${}'))
eq('$UNSET_ENV_VAR/baz', vim.fs.normalize('$UNSET_ENV_VAR/baz'))
if is_os('win') then
eq('foo/bar/baz', vim.fs.normalize('%foobar%/%_BARVAR_42%/baz'))
eq('foo/windows/baz', vim.fs.normalize('foo/%W!NDOW$_CR@ZY%/baz'))
eq('foo/%UNSET_ENV_VAR%/baz', vim.fs.normalize('foo/%UNSET_ENV_VAR%/baz'))
else
eq('foo/bar/baz', vim.fs.normalize('$FOOVAR/$_BARVAR_42/baz'))
eq('$foovar/bar/baz', vim.fs.normalize('$foovar/$_BARVAR_42/baz'))
eq('foo/$1_INVALID_ENV_VAR/baz', vim.fs.normalize('foo/$1_INVALID_ENV_VAR/baz'))
eq('foo/$UNSET_ENV_VAR/baz', vim.fs.normalize('foo/$UNSET_ENV_VAR/baz'))
eq('foo', vim.fs.normalize('%FOOVAR%'))
eq('foo', vim.fs.normalize('%foovar%'))
eq('foo1', vim.fs.normalize('%{foovar%'))
eq('foo1', vim.fs.normalize('${{foovar}'))
eq('foo1}', vim.fs.normalize('${{foovar}}'))
eq("'%FOOVAR%'", vim.fs.normalize("'%FOOVAR%'"))
eq("'$FOOVAR'", vim.fs.normalize("'$FOOVAR'"))
eq("'${FOOVAR}'", vim.fs.normalize("'${FOOVAR}'"))
eq('foo/2', vim.fs.normalize('%{foovar}%'))
eq('foo/%%/bar%%', vim.fs.normalize('foo/%%/bar%%'))
end
end)
end)