Merge #6789 from ZyX-I/lua-path

lua: Add paths from &runtimepath to package.path and package.cpath
This commit is contained in:
Justin M. Keyes 2017-06-27 02:29:15 +02:00 committed by GitHub
commit f34befe74c
13 changed files with 483 additions and 54 deletions

View File

@ -600,9 +600,26 @@ if(LUACHECK_PRG)
add_custom_target(testlint
COMMAND ${CMAKE_COMMAND}
-DLUACHECK_PRG=${LUACHECK_PRG}
-DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
-DLUAFILES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
-DIGNORE_PATTERN="*/preload.lua"
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P ${PROJECT_SOURCE_DIR}/cmake/RunTestsLint.cmake)
-P ${PROJECT_SOURCE_DIR}/cmake/RunLuacheck.cmake)
add_custom_target(
blobcodelint
COMMAND
${CMAKE_COMMAND}
-DLUACHECK_PRG=${LUACHECK_PRG}
-DLUAFILES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/src/nvim/lua
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-DREAD_GLOBALS=vim
-P ${PROJECT_SOURCE_DIR}/cmake/RunLuacheck.cmake
)
# TODO(ZyX-I): Run linter for all lua code in src
add_custom_target(
lualint
DEPENDS blobcodelint
)
endif()
set(CPACK_PACKAGE_NAME "Neovim")

View File

@ -107,6 +107,9 @@ functionaltest-lua: | nvim
testlint: | build/.ran-cmake deps
$(BUILD_CMD) -C build testlint
lualint: | build/.ran-cmake deps
$(BUILD_CMD) -C build lualint
unittest: | nvim
+$(BUILD_CMD) -C build unittest
@ -138,6 +141,6 @@ check-single-includes: build/.ran-cmake
appimage:
bash scripts/genappimage.sh
lint: check-single-includes clint testlint
lint: check-single-includes clint testlint lualint
.PHONY: test testlint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage
.PHONY: test testlint lualint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage

View File

@ -20,6 +20,12 @@ run_test 'top_make testlint' testlint
exit_suite --continue
enter_suite 'lualint'
run_test 'top_make lualint' lualint
exit_suite --continue
enter_suite single-includes
CLICOLOR_FORCE=1 run_test_wd \

22
cmake/RunLuacheck.cmake Normal file
View File

@ -0,0 +1,22 @@
set(LUACHECK_ARGS -q "${LUAFILES_DIR}")
if(DEFINED IGNORE_PATTERN)
list(APPEND LUACHECK_ARGS --exclude-files "${LUAFILES_DIR}/${IGNORE_PATTERN}")
endif()
if(DEFINED CHECK_PATTERN)
list(APPEND LUACHECK_ARGS --include-files "${LUAFILES_DIR}/${CHECK_PATTERN}")
endif()
if(DEFINED READ_GLOBALS)
list(APPEND LUACHECK_ARGS --read-globals "${READ_GLOBALS}")
endif()
execute_process(
COMMAND "${LUACHECK_PRG}" ${LUACHECK_ARGS}
WORKING_DIRECTORY "${LUAFILES_DIR}"
ERROR_VARIABLE err
RESULT_VARIABLE res
)
if(NOT res EQUAL 0)
message(STATUS "Output to stderr:\n${err}")
message(FATAL_ERROR "Linting tests failed with error: ${res}.")
endif()

View File

@ -1,13 +0,0 @@
set(IGNORE_FILES "${TEST_DIR}/*/preload.lua")
execute_process(
COMMAND ${LUACHECK_PRG} -q ${TEST_DIR} --exclude-files ${IGNORE_FILES}
WORKING_DIRECTORY ${TEST_DIR}
ERROR_VARIABLE err
RESULT_VARIABLE res
${EXTRA_ARGS})
if(NOT res EQUAL 0)
message(STATUS "Output to stderr:\n${err}")
message(FATAL_ERROR "Linting tests failed with error: ${res}.")
endif()

View File

@ -9,7 +9,147 @@ Lua Interface to Nvim *lua* *Lua*
Type <M-]> to see the table of contents.
==============================================================================
1. Commands *lua-commands*
1. Importing modules *lua-require*
Neovim lua interface automatically adjusts `package.path` and `package.cpath`
according to effective &runtimepath value. Adjustment happens after
'runtimepath' is changed. `package.path` is adjusted by simply appending
`/lua/?.lua` and `/lua/?/init.lua` to each directory from 'runtimepath' (`/`
is actually the first character of `package.config`).
Similarly to `package.path`, modified directories from `runtimepath` are also
added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and
`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of
the existing `package.cpath` are used. Here is an example:
1. Given that
- 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`;
- initial (defined at compile time or derived from
`$LUA_CPATH`/`$LUA_INIT`) `package.cpath` contains
`./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`.
2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in
order: parts of the path starting from the first path component containing
question mark and preceding path separator.
3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as its the same
as the suffix of the first path from `package.path` (i.e. `./?.so`). Which
leaves `/?.so` and `/a?d/j/g.elf`, in this order.
4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`. The
second one contains semicolon which is a paths separator so it is out,
leaving only `/foo/bar` and `/abc`, in order.
5. The cartesian product of paths from 4. and suffixes from 3. is taken,
giving four variants. In each variant `/lua` path segment is inserted
between path and suffix, leaving
- `/foo/bar/lua/?.so`
- `/foo/bar/lua/a?d/j/g.elf`
- `/abc/lua/?.so`
- `/abc/lua/a?d/j/g.elf`
6. New paths are prepended to the original `package.cpath`.
The result will look like this:
`/foo/bar,/xxx;yyy/baz,/abc` ('runtimepath')
× `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` (`package.cpath`)
= `/foo/bar/lua/?.so;/foo/bar/lua/a?d/j/g.elf;/abc/lua/?.so;/abc/lua/a?d/j/g.elf;./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`
Note: to keep up with 'runtimepath' updates paths added at previous update are
remembered and removed at the next update, while all paths derived from the
new 'runtimepath' are prepended as described above. This allows removing
paths when path is removed from 'runtimepath', adding paths when they are
added and reordering `package.path`/`package.cpath` content if 'runtimepath'
was reordered.
Note 2: even though adjustments happens automatically Neovim does not track
current values of `package.path` or `package.cpath`. If you happened to
delete some paths from there you need to reset 'runtimepath' to make them
readded. Just running `let &runtimepath = &runtimepath` should work.
Note 3: skipping paths from 'runtimepath' which contain semicolons applies
both to `package.path` and `package.cpath`. Given that there is a number of
badly written plugins using shell which will not work with paths containing
semicolons it is better to not have them in 'runtimepath' at all.
------------------------------------------------------------------------------
1.1. Example of the plugin which uses lua modules: *lua-require-example*
The following example plugin adds a command `:MakeCharBlob` which transforms
current buffer into a long `unsigned char` array. Lua contains transformation
function in a module `lua/charblob.lua` which is imported in
`autoload/charblob.vim` (`require("charblob")`). Example plugin is supposed
to be put into any directory from 'runtimepath', e.g. `~/.config/nvim` (in
this case `lua/charblob.lua` means `~/.config/nvim/lua/charblob.lua`).
autoload/charblob.vim: >
function charblob#encode_buffer()
call setline(1, luaeval(
\ 'require("charblob").encode(unpack(_A))',
\ [getline(1, '$'), &textwidth, ' ']))
endfunction
plugin/charblob.vim: >
if exists('g:charblob_loaded')
finish
endif
let g:charblob_loaded = 1
command MakeCharBlob :call charblob#encode_buffer()
lua/charblob.lua: >
local function charblob_bytes_iter(lines)
local init_s = {
next_line_idx = 1,
next_byte_idx = 1,
lines = lines,
}
local function next(s, _)
if lines[s.next_line_idx] == nil then
return nil
end
if s.next_byte_idx > #(lines[s.next_line_idx]) then
s.next_line_idx = s.next_line_idx + 1
s.next_byte_idx = 1
return ('\n'):byte()
end
local ret = lines[s.next_line_idx]:byte(s.next_byte_idx)
if ret == ('\n'):byte() then
ret = 0 -- See :h NL-used-for-NUL.
end
s.next_byte_idx = s.next_byte_idx + 1
return ret
end
return next, init_s, nil
end
local function charblob_encode(lines, textwidth, indent)
local ret = {
'const unsigned char blob[] = {',
indent,
}
for byte in charblob_bytes_iter(lines) do
-- .- space + number (width 3) + comma
if #(ret[#ret]) + 5 > textwidth then
ret[#ret + 1] = indent
else
ret[#ret] = ret[#ret] .. ' '
end
ret[#ret] = ret[#ret] .. (('%3u,'):format(byte))
end
ret[#ret + 1] = '};'
return ret
end
return {
bytes_iter = charblob_bytes_iter,
encode = charblob_encode,
}
==============================================================================
2. Commands *lua-commands*
*:lua*
:[range]lua {chunk}

View File

@ -244,6 +244,8 @@ Lua interface (|if_lua.txt|):
while calling lua chunk: [string "<VimL compiled string>"]:1: TEST” in
Neovim.
- Lua has direct access to Nvim |API| via `vim.api`.
- Lua package.path and package.cpath are automatically updated according to
'runtimepath': |lua-require|.
- Currently, most legacy Vim features are missing.
|input()| and |inputdialog()| gained support for each others features (return

View File

@ -300,7 +300,7 @@ ArrayOf(String) nvim_list_runtime_paths(void)
FUNC_API_SINCE(1)
{
Array rv = ARRAY_DICT_INIT;
uint8_t *rtp = p_rtp;
char_u *rtp = p_rtp;
if (*rtp == NUL) {
// No paths
@ -314,13 +314,14 @@ ArrayOf(String) nvim_list_runtime_paths(void)
}
rtp++;
}
rv.size++;
// Allocate memory for the copies
rv.items = xmalloc(sizeof(Object) * rv.size);
rv.items = xmalloc(sizeof(*rv.items) * rv.size);
// Reset the position
rtp = p_rtp;
// Start copying
for (size_t i = 0; i < rv.size && *rtp != NUL; i++) {
for (size_t i = 0; i < rv.size; i++) {
rv.items[i].type = kObjectTypeString;
rv.items[i].data.string.data = xmalloc(MAXPATHL);
// Copy the path from 'runtimepath' to rv.items[i]
@ -709,7 +710,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
Integer nvim_get_color_by_name(String name)
FUNC_API_SINCE(1)
{
return name_to_color((uint8_t *)name.data);
return name_to_color((char_u *)name.data);
}
Dictionary nvim_get_color_map(void)
@ -871,7 +872,7 @@ static void write_msg(String message, bool to_err)
#define PUSH_CHAR(i, pos, line_buf, msg) \
if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \
line_buf[pos] = NUL; \
msg((uint8_t *)line_buf); \
msg((char_u *)line_buf); \
pos = 0; \
continue; \
} \

View File

@ -14,6 +14,7 @@
#include "nvim/api/vim.h"
#include "nvim/vim.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
#include "nvim/message.h"
#include "nvim/memline.h"
#include "nvim/buffer_defs.h"
@ -284,7 +285,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
///
/// Crashes NeoVim if initialization fails. Should be called once per lua
/// interpreter instance.
static lua_State *init_lua(void)
///
/// @return New lua interpreter instance.
static lua_State *nlua_init(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
lua_State *lstate = luaL_newstate();
@ -297,7 +300,43 @@ static lua_State *init_lua(void)
return lstate;
}
static lua_State *global_lstate = NULL;
/// Enter lua interpreter
///
/// Calls nlua_init() if needed. Is responsible for pre-lua call initalization
/// like updating `package.[c]path` with directories derived from &runtimepath.
///
/// @return Interprter instance to use. Will either be initialized now or taken
/// from previous initalization.
static lua_State *nlua_enter(void)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
static lua_State *global_lstate = NULL;
if (global_lstate == NULL) {
global_lstate = nlua_init();
}
lua_State *const lstate = global_lstate;
// Last used p_rtp value. Must not be dereferenced because value pointed to
// may already be freed. Used to check whether &runtimepath option value
// changed.
static const void *last_p_rtp = NULL;
if (last_p_rtp != (const void *)p_rtp) {
// stack: (empty)
lua_getglobal(lstate, "vim");
// stack: vim
lua_getfield(lstate, -1, "_update_package_paths");
// stack: vim, vim._update_package_paths
if (lua_pcall(lstate, 0, 0, 0)) {
// stack: vim, error
nlua_error(lstate, _("E5117: Error while updating package paths: %.*s"));
// stack: vim
}
// stack: vim
lua_pop(lstate, 1);
// stack: (empty)
last_p_rtp = (const void *)p_rtp;
}
return lstate;
}
/// Execute lua string
///
@ -308,11 +347,7 @@ static lua_State *global_lstate = NULL;
void executor_exec_lua(const String str, typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
if (global_lstate == NULL) {
global_lstate = init_lua();
}
NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_lua_string, 0,
NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_lua_string, 0,
(void *)&str, ret_tv);
}
@ -551,11 +586,7 @@ void executor_eval_lua(const String str, typval_T *const arg,
typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
if (global_lstate == NULL) {
global_lstate = init_lua();
}
NLUA_CALL_C_FUNCTION_3(global_lstate, nlua_eval_lua_string, 0,
NLUA_CALL_C_FUNCTION_3(nlua_enter(), nlua_eval_lua_string, 0,
(void *)&str, arg, ret_tv);
}
@ -570,12 +601,8 @@ void executor_eval_lua(const String str, typval_T *const arg,
/// @return Return value of the execution.
Object executor_exec_lua_api(const String str, const Array args, Error *err)
{
if (global_lstate == NULL) {
global_lstate = init_lua();
}
Object retval = NIL;
NLUA_CALL_C_FUNCTION_4(global_lstate, nlua_exec_lua_string_api, 0,
NLUA_CALL_C_FUNCTION_4(nlua_enter(), nlua_exec_lua_string_api, 0,
(void *)&str, (void *)&args, &retval, err);
return retval;
}
@ -609,9 +636,6 @@ void ex_lua(exarg_T *const eap)
void ex_luado(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
if (global_lstate == NULL) {
global_lstate = init_lua();
}
if (u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) {
EMSG(_("cannot save undo information"));
return;
@ -621,7 +645,7 @@ void ex_luado(exarg_T *const eap)
.data = (char *)eap->arg,
};
const linenr_T range[] = { eap->line1, eap->line2 };
NLUA_CALL_C_FUNCTION_2(global_lstate, nlua_exec_luado_string, 0,
NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_luado_string, 0,
(void *)&cmd, (void *)range);
}
@ -633,9 +657,6 @@ void ex_luado(exarg_T *const eap)
void ex_luafile(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
if (global_lstate == NULL) {
global_lstate = init_lua();
}
NLUA_CALL_C_FUNCTION_1(global_lstate, nlua_exec_lua_file, 0,
NLUA_CALL_C_FUNCTION_1(nlua_enter(), nlua_exec_lua_file, 0,
(void *)eap->arg);
}

View File

@ -1,2 +1,64 @@
-- TODO(ZyX-I): Create compatibility layer.
return {}
--{{{1 package.path updater function
-- Last inserted paths. Used to clear out items from package.[c]path when they
-- are no longer in &runtimepath.
local last_nvim_paths = {}
local function _update_package_paths()
local cur_nvim_paths = {}
local rtps = vim.api.nvim_list_runtime_paths()
local sep = package.config:sub(1, 1)
for _, key in ipairs({'path', 'cpath'}) do
local orig_str = package[key] .. ';'
local pathtrails_ordered = {}
local orig = {}
-- Note: ignores trailing item without trailing `;`. Not using something
-- simpler in order to preserve empty items (stand for default path).
for s in orig_str:gmatch('[^;]*;') do
s = s:sub(1, -2) -- Strip trailing semicolon
orig[#orig + 1] = s
end
if key == 'path' then
-- /?.lua and /?/init.lua
pathtrails_ordered = {sep .. '?.lua', sep .. '?' .. sep .. 'init.lua'}
else
local pathtrails = {}
for _, s in ipairs(orig) do
-- Find out path patterns. pathtrail should contain something like
-- /?.so, \?.dll. This allows not to bother determining what correct
-- suffixes are.
local pathtrail = s:match('[/\\][^/\\]*%?.*$')
if pathtrail and not pathtrails[pathtrail] then
pathtrails[pathtrail] = true
pathtrails_ordered[#pathtrails_ordered + 1] = pathtrail
end
end
end
local new = {}
for _, rtp in ipairs(rtps) do
if not rtp:match(';') then
for _, pathtrail in pairs(pathtrails_ordered) do
local new_path = rtp .. sep .. 'lua' .. pathtrail
-- Always keep paths from &runtimepath at the start:
-- append them here disregarding orig possibly containing one of them.
new[#new + 1] = new_path
cur_nvim_paths[new_path] = true
end
end
end
for _, orig_path in ipairs(orig) do
-- Handle removing obsolete paths originating from &runtimepath: such
-- paths either belong to cur_nvim_paths and were already added above or
-- to last_nvim_paths and should not be added at all if corresponding
-- entry was removed from &runtimepath list.
if not (cur_nvim_paths[orig_path] or last_nvim_paths[orig_path]) then
new[#new + 1] = orig_path
end
end
package[key] = table.concat(new, ';')
end
last_nvim_paths = cur_nvim_paths
end
--{{{1 Module definition
return {
_update_package_paths = _update_package_paths,
}

View File

@ -327,11 +327,11 @@ describe('api', function()
{'nvim_get_mode', {}},
{'nvim_eval', {'1'}},
}
eq({{{mode='n', blocking=false},
13,
{mode='n', blocking=false}, -- TODO: should be blocked=true
1},
NIL}, meths.call_atomic(req))
eq({ { {mode='n', blocking=false},
13,
{mode='n', blocking=false}, -- TODO: should be blocked=true
1 },
NIL}, meths.call_atomic(req))
eq({mode='r', blocking=true}, nvim("get_mode"))
end)
-- TODO: bug #6166
@ -588,6 +588,36 @@ describe('api', function()
end)
end)
describe('list_runtime_paths', function()
it('returns nothing with empty &runtimepath', function()
meths.set_option('runtimepath', '')
eq({}, meths.list_runtime_paths())
end)
it('returns single runtimepath', function()
meths.set_option('runtimepath', 'a')
eq({'a'}, meths.list_runtime_paths())
end)
it('returns two runtimepaths', function()
meths.set_option('runtimepath', 'a,b')
eq({'a', 'b'}, meths.list_runtime_paths())
end)
it('returns empty strings when appropriate', function()
meths.set_option('runtimepath', 'a,,b')
eq({'a', '', 'b'}, meths.list_runtime_paths())
meths.set_option('runtimepath', ',a,b')
eq({'', 'a', 'b'}, meths.list_runtime_paths())
meths.set_option('runtimepath', 'a,b,')
eq({'a', 'b', ''}, meths.list_runtime_paths())
end)
it('truncates too long paths', function()
local long_path = ('/a'):rep(8192)
meths.set_option('runtimepath', long_path)
local paths_list = meths.list_runtime_paths()
neq({long_path}, paths_list)
eq({long_path:sub(1, #(paths_list[1]))}, paths_list)
end)
end)
it('can throw exceptions', function()
local status, err = pcall(nvim, 'get_option', 'invalid-option')
eq(false, status)

View File

@ -581,6 +581,24 @@ local function missing_provider(provider)
end
end
local function alter_slashes(obj)
if not iswin() then
return obj
end
if type(obj) == 'string' then
local ret = obj:gsub('/', '\\')
return ret
elseif type(obj) == 'table' then
local ret = {}
for k, v in pairs(obj) do
ret[k] = alter_slashes(v)
end
return ret
else
assert(false, 'Could only alter slashes for tables of strings and strings')
end
end
local module = {
prepend_argv = prepend_argv,
clear = clear,
@ -649,6 +667,7 @@ local module = {
NIL = mpack.NIL,
get_pathsep = get_pathsep,
missing_provider = missing_provider,
alter_slashes = alter_slashes,
}
return function(after_each)

View File

@ -3,14 +3,17 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
local neq = helpers.neq
local NIL = helpers.NIL
local feed = helpers.feed
local clear = helpers.clear
local funcs = helpers.funcs
local meths = helpers.meths
local iswin = helpers.iswin
local command = helpers.command
local write_file = helpers.write_file
local redir_exec = helpers.redir_exec
local alter_slashes = helpers.alter_slashes
local screen
@ -173,3 +176,119 @@ describe('debug.debug', function()
]])
end)
end)
describe('package.path/package.cpath', function()
local sl = alter_slashes
local function get_new_paths(sufs, runtimepaths)
runtimepaths = runtimepaths or meths.list_runtime_paths()
local new_paths = {}
local sep = package.config:sub(1, 1)
for _, v in ipairs(runtimepaths) do
for _, suf in ipairs(sufs) do
new_paths[#new_paths + 1] = v .. sep .. 'lua' .. suf
end
end
return new_paths
end
local function execute_lua(cmd, ...)
return meths.execute_lua(cmd, {...})
end
local function eval_lua(expr, ...)
return meths.execute_lua('return ' .. expr, {...})
end
local function set_path(which, value)
return execute_lua('package[select(1, ...)] = select(2, ...)', which, value)
end
it('contains directories from &runtimepath on first invocation', function()
local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'})
local new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'})
local new_cpaths_str = table.concat(new_cpaths, ';')
eq(new_cpaths_str, eval_lua('package.cpath'):sub(1, #new_cpaths_str))
end)
it('puts directories from &runtimepath always at the start', function()
meths.set_option('runtimepath', 'a,b')
local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b'})
local new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
set_path('path', sl'foo/?.lua;foo/?/init.lua;' .. new_paths_str)
neq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
command('set runtimepath+=c')
new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a', 'b', 'c'})
new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
end)
it('understands uncommon suffixes', function()
set_path('cpath', './?/foo/bar/baz/x.nlua')
meths.set_option('runtimepath', 'a')
local new_paths = get_new_paths({'/?/foo/bar/baz/x.nlua'}, {'a'})
local new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
set_path('cpath', './yyy?zzz/x')
meths.set_option('runtimepath', 'b')
new_paths = get_new_paths({'/yyy?zzz/x'}, {'b'})
new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
set_path('cpath', './yyy?zzz/123?ghi/x')
meths.set_option('runtimepath', 'b')
new_paths = get_new_paths({'/yyy?zzz/123?ghi/x'}, {'b'})
new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.cpath'):sub(1, #new_paths_str))
end)
it('preserves empty items', function()
local many_empty_path = ';;;;;;'
local many_empty_cpath = ';;;;;;./?.luaso'
set_path('path', many_empty_path)
set_path('cpath', many_empty_cpath)
meths.set_option('runtimepath', 'a')
local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'})
local new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str .. ';' .. many_empty_path, eval_lua('package.path'))
local new_cpaths = get_new_paths({'/?.luaso'}, {'a'})
local new_cpaths_str = table.concat(new_cpaths, ';')
eq(new_cpaths_str .. ';' .. many_empty_cpath, eval_lua('package.cpath'))
end)
it('preserves empty value', function()
set_path('path', '')
meths.set_option('runtimepath', 'a')
local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {'a'})
local new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str .. ';', eval_lua('package.path'))
end)
it('purges out all additions if runtimepath is set to empty', function()
local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'})
local new_paths_str = table.concat(new_paths, ';')
local path = eval_lua('package.path')
eq(new_paths_str, path:sub(1, #new_paths_str))
local new_cpaths = get_new_paths(iswin() and {'\\?.dll'} or {'/?.so'})
local new_cpaths_str = table.concat(new_cpaths, ';')
local cpath = eval_lua('package.cpath')
eq(new_cpaths_str, cpath:sub(1, #new_cpaths_str))
meths.set_option('runtimepath', '')
eq(path:sub(#new_paths_str + 2, -1), eval_lua('package.path'))
eq(cpath:sub(#new_cpaths_str + 2, -1), eval_lua('package.cpath'))
end)
it('works with paths with escaped commas', function()
meths.set_option('runtimepath', '\\,')
local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','})
local new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
end)
it('ignores paths with semicolons', function()
meths.set_option('runtimepath', 'foo;bar,\\,')
local new_paths = get_new_paths(sl{'/?.lua', '/?/init.lua'}, {','})
local new_paths_str = table.concat(new_paths, ';')
eq(new_paths_str, eval_lua('package.path'):sub(1, #new_paths_str))
end)
end)