mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-24 02:24:14 -05:00
config/lua: allow wildcards or absolute paths in require (#15461)
This commit is contained in:
@@ -27,6 +27,9 @@ install(TARGETS hyprtester)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.lua
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
|
||||
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lua-require
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/plugin/hyprtestplugin.so
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
return "absolute"
|
||||
@@ -0,0 +1 @@
|
||||
return "relative"
|
||||
@@ -0,0 +1 @@
|
||||
return "a"
|
||||
@@ -0,0 +1 @@
|
||||
return "b"
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
|
||||
TEST_CASE(luaRequire) {
|
||||
constexpr auto EXPECTED = "absolute:relative:a:b";
|
||||
|
||||
EXPECT(getFromSocket("/repl return _G.hyprtester_lua_require_result"), EXPECTED);
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
EXPECT(getFromSocket("/repl return _G.hyprtester_lua_require_result"), EXPECTED);
|
||||
}
|
||||
+18
-1
@@ -1,5 +1,23 @@
|
||||
-- Hyprtester Lua config
|
||||
|
||||
local function config_dir()
|
||||
local source = debug.getinfo(1, "S").source
|
||||
local path = source:sub(1, 1) == "@" and source:sub(2) or source
|
||||
|
||||
if path:sub(1, 1) ~= "/" then
|
||||
path = os.getenv("PWD") .. "/" .. path
|
||||
end
|
||||
|
||||
return path:match("^(.*)/[^/]*$") or "."
|
||||
end
|
||||
|
||||
local requireAbsolute = require(config_dir() .. "/lua-require/absolute.lua")
|
||||
local requireRelative = require("./lua-require/relative.lua")
|
||||
local requireWildcard = require("./lua-require/wildcard/*")
|
||||
|
||||
_G.hyprtester_lua_require_result = table.concat({ requireAbsolute, requireRelative, requireWildcard[1], requireWildcard[2] }, ":")
|
||||
assert(_G.hyprtester_lua_require_result == "absolute:relative:a:b")
|
||||
|
||||
hl.monitor({ output = "HEADLESS-1", mode = "1920x1080@60", position = "auto-right", scale = "1" })
|
||||
hl.monitor({ output = "HEADLESS-2", mode = "1920x1080@60", position = "auto-right", scale = "1" })
|
||||
hl.monitor({ output = "HEADLESS-3", mode = "1920x1080@60", position = "auto-right", scale = "1" })
|
||||
@@ -325,4 +343,3 @@ hl.layout.register("grid", {
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <fstream>
|
||||
#include <glob.h>
|
||||
#include <hyprutils/string/String.hpp>
|
||||
#include <hyprutils/string/Numeric.hpp>
|
||||
|
||||
@@ -37,6 +39,7 @@
|
||||
#include "../../managers/eventLoop/EventLoopManager.hpp"
|
||||
#include "../../managers/input/trackpad/TrackpadGestures.hpp"
|
||||
#include "../../notification/NotificationOverlay.hpp"
|
||||
#include "../../helpers/MiscFunctions.hpp"
|
||||
|
||||
using namespace Config;
|
||||
using namespace Config::Lua;
|
||||
@@ -55,6 +58,211 @@ static bool isValidLuaIdentifier(const std::string& value) {
|
||||
return std::ranges::all_of(value, [](const char& c) { return std::isalnum(c) || c == '_'; });
|
||||
}
|
||||
|
||||
static std::string normalizedConfigPath(const std::string& path) {
|
||||
if (path.empty())
|
||||
return path;
|
||||
|
||||
return std::filesystem::path(path).lexically_normal().string();
|
||||
}
|
||||
|
||||
static void trackConfigPath(CConfigManager* mgr, const std::string& path) {
|
||||
if (!mgr || path.empty())
|
||||
return;
|
||||
|
||||
const auto NORMALIZED = normalizedConfigPath(path);
|
||||
if (std::ranges::find(mgr->m_configPaths, NORMALIZED) == mgr->m_configPaths.end())
|
||||
mgr->m_configPaths.emplace_back(NORMALIZED);
|
||||
}
|
||||
|
||||
static bool isExplicitRequirePath(std::string_view moduleName) {
|
||||
return moduleName.starts_with('/') || moduleName.starts_with("./") || moduleName.starts_with("../") || moduleName.starts_with("~/");
|
||||
}
|
||||
|
||||
static bool hasGlobMeta(std::string_view value) {
|
||||
return value.find_first_of("*?[") != std::string_view::npos;
|
||||
}
|
||||
|
||||
static std::string resolveRequirePath(CConfigManager* mgr, const std::string& rawPath) {
|
||||
if (!mgr)
|
||||
return rawPath;
|
||||
|
||||
return absolutePath(rawPath, mgr->getMainConfigPath());
|
||||
}
|
||||
|
||||
static std::optional<std::string> resolveExplicitLuaRequireFile(CConfigManager* mgr, const std::string& moduleName) {
|
||||
std::vector<std::string> candidates;
|
||||
|
||||
const auto BASE = resolveRequirePath(mgr, moduleName);
|
||||
candidates.emplace_back(BASE);
|
||||
|
||||
if (!BASE.ends_with(".lua"))
|
||||
candidates.emplace_back(BASE + ".lua");
|
||||
|
||||
candidates.emplace_back((std::filesystem::path(BASE) / "init.lua").string());
|
||||
|
||||
for (const auto& candidate : candidates) {
|
||||
std::error_code ec;
|
||||
const auto STATUS = std::filesystem::status(candidate, ec);
|
||||
if (!ec && std::filesystem::is_regular_file(STATUS))
|
||||
return normalizedConfigPath(candidate);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void trackWildcardParentDirectory(CConfigManager* mgr, const std::string& pattern) {
|
||||
const auto META = pattern.find_first_of("*?[");
|
||||
if (META == std::string::npos)
|
||||
return;
|
||||
|
||||
const auto SLASH = pattern.substr(0, META).find_last_of('/');
|
||||
if (SLASH == std::string::npos)
|
||||
return;
|
||||
|
||||
std::string parent = SLASH == 0 ? "/" : pattern.substr(0, SLASH);
|
||||
if (parent.empty())
|
||||
parent = ".";
|
||||
|
||||
std::error_code ec;
|
||||
const auto STATUS = std::filesystem::status(parent, ec);
|
||||
if (!ec && std::filesystem::is_directory(STATUS))
|
||||
trackConfigPath(mgr, parent);
|
||||
}
|
||||
|
||||
static std::expected<std::vector<std::string>, std::string> expandRequireWildcard(CConfigManager* mgr, const std::string& moduleName) {
|
||||
const auto PATTERN = resolveRequirePath(mgr, moduleName);
|
||||
trackWildcardParentDirectory(mgr, PATTERN);
|
||||
|
||||
glob_t globBuf = {};
|
||||
const int GLOBRESULT = glob(PATTERN.c_str(), GLOB_TILDE, nullptr, &globBuf);
|
||||
if (GLOBRESULT != 0) {
|
||||
globfree(&globBuf);
|
||||
if (GLOBRESULT == GLOB_NOMATCH)
|
||||
return std::unexpected("found no match");
|
||||
if (GLOBRESULT == GLOB_ABORTED)
|
||||
return std::unexpected("read error");
|
||||
return std::unexpected("out of memory");
|
||||
}
|
||||
|
||||
std::vector<std::string> paths;
|
||||
for (size_t i = 0; i < globBuf.gl_pathc; ++i) {
|
||||
std::string path = globBuf.gl_pathv[i];
|
||||
std::error_code ec;
|
||||
const auto STATUS = std::filesystem::status(path, ec);
|
||||
if (!ec && std::filesystem::is_regular_file(STATUS))
|
||||
paths.emplace_back(normalizedConfigPath(path));
|
||||
}
|
||||
|
||||
globfree(&globBuf);
|
||||
|
||||
std::ranges::sort(paths);
|
||||
paths.erase(std::ranges::unique(paths).begin(), paths.end());
|
||||
|
||||
if (paths.empty())
|
||||
return std::unexpected("found no regular file matches");
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static bool pushPackageLoaded(lua_State* L, const std::string& moduleName) {
|
||||
const int stackTop = lua_gettop(L);
|
||||
|
||||
lua_getglobal(L, "package");
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_settop(L, stackTop);
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_getfield(L, -1, "loaded");
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_settop(L, stackTop);
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_pushstring(L, moduleName.c_str());
|
||||
lua_gettable(L, -2);
|
||||
if (!lua_toboolean(L, -1)) {
|
||||
lua_settop(L, stackTop);
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_remove(L, stackTop + 2); // loaded
|
||||
lua_remove(L, stackTop + 1); // package
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setPackageLoaded(lua_State* L, const std::string& moduleName, int valueIdx) {
|
||||
const int absValueIdx = lua_absindex(L, valueIdx);
|
||||
|
||||
lua_getglobal(L, "package");
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
lua_getfield(L, -1, "loaded");
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_pop(L, 2);
|
||||
return;
|
||||
}
|
||||
|
||||
lua_pushstring(L, moduleName.c_str());
|
||||
lua_pushvalue(L, absValueIdx);
|
||||
lua_settable(L, -3);
|
||||
lua_pop(L, 2);
|
||||
}
|
||||
|
||||
static int requireWildcard(lua_State* L, CConfigManager* mgr, const std::string& moduleName) {
|
||||
if (pushPackageLoaded(L, moduleName))
|
||||
return 1;
|
||||
|
||||
const auto PATHS = expandRequireWildcard(mgr, moduleName);
|
||||
if (!PATHS)
|
||||
return luaL_error(L, "module '%s' not found: wildcard %s", moduleName.c_str(), PATHS.error().c_str());
|
||||
|
||||
lua_newtable(L);
|
||||
const int resultIdx = lua_gettop(L);
|
||||
size_t resultI = 1;
|
||||
|
||||
for (const auto& path : *PATHS) {
|
||||
trackConfigPath(mgr, path);
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
lua_pushstring(L, path.c_str());
|
||||
|
||||
const int status = lua_pcall(L, 1, LUA_MULTRET, 0);
|
||||
if (status == LUA_OK) {
|
||||
const int nresults = lua_gettop(L) - resultIdx;
|
||||
if (nresults > 0 && !lua_isnil(L, resultIdx + 1))
|
||||
lua_pushvalue(L, resultIdx + 1);
|
||||
else
|
||||
lua_pushboolean(L, true);
|
||||
lua_rawseti(L, resultIdx, resultI++);
|
||||
lua_pop(L, nresults);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string err;
|
||||
{
|
||||
size_t len = 0;
|
||||
const char* str = luaL_tolstring(L, -1, &len);
|
||||
if (str)
|
||||
err.assign(str, len);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1); // error object
|
||||
|
||||
if (mgr)
|
||||
mgr->addError(std::format("require(\"{}\"): {}", path, err));
|
||||
|
||||
lua_newtable(L);
|
||||
lua_rawseti(L, resultIdx, resultI++);
|
||||
}
|
||||
|
||||
setPackageLoaded(L, moduleName, resultIdx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int pluginLuaFunctionDispatcher(lua_State* L) {
|
||||
auto* mgr = CConfigManager::fromLuaState(L);
|
||||
if (!mgr)
|
||||
@@ -94,8 +302,8 @@ static void trackRequiredLuaModulePath(lua_State* L, CConfigManager* mgr, const
|
||||
|
||||
if (lua_pcall(L, 2, 2, 0) == LUA_OK && lua_isstring(L, -2)) {
|
||||
const auto* resolvedPath = lua_tostring(L, -2);
|
||||
if (resolvedPath && std::ranges::find(mgr->m_configPaths, resolvedPath) == mgr->m_configPaths.end())
|
||||
mgr->m_configPaths.emplace_back(resolvedPath);
|
||||
if (resolvedPath)
|
||||
trackConfigPath(mgr, resolvedPath);
|
||||
}
|
||||
|
||||
lua_settop(L, stackTop);
|
||||
@@ -108,6 +316,9 @@ static int safeLuaRequire(lua_State* L) {
|
||||
if (lua_isstring(L, 1))
|
||||
moduleName = lua_tostring(L, 1);
|
||||
|
||||
if (isExplicitRequirePath(moduleName) && hasGlobMeta(moduleName))
|
||||
return requireWildcard(L, CConfigManager::fromLuaState(L), moduleName);
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
lua_insert(L, 1);
|
||||
|
||||
@@ -349,12 +560,29 @@ void CConfigManager::reinitLuaState() {
|
||||
m_lua,
|
||||
[](lua_State* L) -> int {
|
||||
// upvalue 1: original searcher, upvalue 2: CConfigManager*
|
||||
auto* self = sc<CConfigManager*>(lua_touserdata(L, lua_upvalueindex(2)));
|
||||
std::string moduleName;
|
||||
if (lua_isstring(L, 1))
|
||||
moduleName = lua_tostring(L, 1);
|
||||
|
||||
if (isExplicitRequirePath(moduleName) && !hasGlobMeta(moduleName)) {
|
||||
const auto resolved = resolveExplicitLuaRequireFile(self, moduleName);
|
||||
if (resolved) {
|
||||
trackConfigPath(self, *resolved);
|
||||
|
||||
if (luaL_loadfile(L, resolved->c_str()) != LUA_OK)
|
||||
return luaL_error(L, "error loading module '%s' from file '%s':\n\t%s", moduleName.c_str(), resolved->c_str(), lua_tostring(L, -1));
|
||||
|
||||
lua_pushstring(L, resolved->c_str());
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
lua_pushvalue(L, 1); // module name
|
||||
lua_call(L, 1, 2); // -> loader?, filename?
|
||||
if (lua_isfunction(L, -2) && lua_isstring(L, -1)) {
|
||||
auto* self = sc<CConfigManager*>(lua_touserdata(L, lua_upvalueindex(2)));
|
||||
self->m_configPaths.emplace_back(lua_tostring(L, -1));
|
||||
trackConfigPath(self, lua_tostring(L, -1));
|
||||
}
|
||||
return 2;
|
||||
},
|
||||
|
||||
@@ -50,8 +50,14 @@ void CConfigWatcher::setWatchList(const std::vector<std::string>& paths) {
|
||||
|
||||
// add new paths
|
||||
for (const auto& path : paths) {
|
||||
std::error_code ecDir;
|
||||
const bool isDirectory = std::filesystem::is_directory(path, ecDir);
|
||||
const uint32_t fileMask = IN_CLOSE_WRITE | IN_DONT_FOLLOW;
|
||||
const uint32_t directoryMask = fileMask | IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM;
|
||||
const uint32_t mask = isDirectory ? directoryMask : fileMask;
|
||||
|
||||
m_watches.emplace_back(SInotifyWatch{
|
||||
.wd = inotify_add_watch(m_inotifyFd.get(), path.c_str(), IN_CLOSE_WRITE | IN_DONT_FOLLOW),
|
||||
.wd = inotify_add_watch(m_inotifyFd.get(), path.c_str(), mask),
|
||||
.file = path,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
|
||||
extern "C" {
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
@@ -23,6 +29,17 @@ namespace Config::Lua {
|
||||
lua_pushlightuserdata(L, &mgr);
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, "hl_lua_manager");
|
||||
}
|
||||
|
||||
static void initializeOwnedLuaState(CConfigManager& mgr, const std::filesystem::path& mainConfigPath) {
|
||||
mgr.m_mainConfigPath = mainConfigPath.string();
|
||||
mgr.m_configPaths.clear();
|
||||
mgr.m_configPaths.emplace_back(mgr.m_mainConfigPath);
|
||||
mgr.reinitLuaState();
|
||||
}
|
||||
|
||||
static lua_State* luaState(CConfigManager& mgr) {
|
||||
return mgr.m_lua;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,6 +67,70 @@ namespace {
|
||||
lua_pushstring(L, "pong");
|
||||
return 1;
|
||||
}
|
||||
|
||||
class CTempDir {
|
||||
public:
|
||||
CTempDir() {
|
||||
const auto NOW = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
m_path = std::filesystem::temp_directory_path() / std::format("hyprland-lua-require-{}", NOW);
|
||||
std::filesystem::create_directories(m_path);
|
||||
}
|
||||
|
||||
~CTempDir() {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(m_path, ec);
|
||||
}
|
||||
|
||||
const std::filesystem::path& path() const {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
private:
|
||||
std::filesystem::path m_path;
|
||||
};
|
||||
|
||||
class CScopedCompositor {
|
||||
public:
|
||||
CScopedCompositor() : m_prevCompositor(std::move(g_pCompositor)), m_prevKeybindManager(std::move(g_pKeybindManager)) {
|
||||
g_pCompositor = makeUnique<CCompositor>(true);
|
||||
g_pKeybindManager = makeUnique<CKeybindManager>();
|
||||
}
|
||||
|
||||
~CScopedCompositor() {
|
||||
g_pKeybindManager = std::move(m_prevKeybindManager);
|
||||
g_pCompositor = std::move(m_prevCompositor);
|
||||
}
|
||||
|
||||
private:
|
||||
UP<CCompositor> m_prevCompositor;
|
||||
UP<CKeybindManager> m_prevKeybindManager;
|
||||
};
|
||||
|
||||
std::string luaString(const std::string& value) {
|
||||
std::string out = "\"";
|
||||
for (const auto& c : value) {
|
||||
if (c == '\\' || c == '"')
|
||||
out += '\\';
|
||||
out += c;
|
||||
}
|
||||
out += '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
void writeFile(const std::filesystem::path& path, const std::string& content) {
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
std::ofstream file(path);
|
||||
file << content;
|
||||
}
|
||||
|
||||
std::string normalizedPath(const std::filesystem::path& path) {
|
||||
return path.lexically_normal().string();
|
||||
}
|
||||
|
||||
void expectTracked(CConfigManager& mgr, const std::filesystem::path& path) {
|
||||
const auto& paths = mgr.getConfigPaths();
|
||||
EXPECT_NE(std::ranges::find(paths, normalizedPath(path)), paths.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ConfigLuaBindingsInternal, parseDirectionAliases) {
|
||||
@@ -254,3 +335,128 @@ TEST(ConfigLuaBindingsInternal, pluginLuaFnIsUnloadedWithoutDanglingCall) {
|
||||
|
||||
g_pCompositor = std::move(PREVCOMPOSITOR);
|
||||
}
|
||||
|
||||
TEST(ConfigLuaRequire, absolutePathLoadsAndTracksFile) {
|
||||
CScopedCompositor compositor;
|
||||
CTempDir tmp;
|
||||
const auto mainConfig = tmp.path() / "hyprland.lua";
|
||||
const auto module = tmp.path() / "absolute.lua";
|
||||
writeFile(mainConfig, "");
|
||||
writeFile(module, "return { value = 42 }");
|
||||
|
||||
CConfigManager mgr;
|
||||
CConfigManagerPluginLuaTestAccessor::initializeOwnedLuaState(mgr, mainConfig);
|
||||
const auto L = CConfigManagerPluginLuaTestAccessor::luaState(mgr);
|
||||
|
||||
const auto CODE = "mod = require(" + luaString(module.string()) + ")";
|
||||
ASSERT_EQ(luaL_dostring(L, CODE.c_str()), LUA_OK) << lua_tostring(L, -1);
|
||||
|
||||
lua_getglobal(L, "mod");
|
||||
ASSERT_TRUE(lua_istable(L, -1));
|
||||
lua_getfield(L, -1, "value");
|
||||
EXPECT_EQ(lua_tointeger(L, -1), 42);
|
||||
lua_pop(L, 2);
|
||||
|
||||
expectTracked(mgr, module);
|
||||
}
|
||||
|
||||
TEST(ConfigLuaRequire, relativePathResolvesFromConfigDirectory) {
|
||||
CScopedCompositor compositor;
|
||||
CTempDir tmp;
|
||||
const auto mainConfig = tmp.path() / "hyprland.lua";
|
||||
const auto module = tmp.path() / "modules" / "relative.lua";
|
||||
writeFile(mainConfig, "");
|
||||
writeFile(module, "return 'relative-ok'");
|
||||
|
||||
CConfigManager mgr;
|
||||
CConfigManagerPluginLuaTestAccessor::initializeOwnedLuaState(mgr, mainConfig);
|
||||
const auto L = CConfigManagerPluginLuaTestAccessor::luaState(mgr);
|
||||
|
||||
ASSERT_EQ(luaL_dostring(L, R"(
|
||||
mod = require("./modules/relative.lua")
|
||||
)"),
|
||||
LUA_OK)
|
||||
<< lua_tostring(L, -1);
|
||||
|
||||
lua_getglobal(L, "mod");
|
||||
ASSERT_TRUE(lua_isstring(L, -1));
|
||||
EXPECT_STREQ(lua_tostring(L, -1), "relative-ok");
|
||||
lua_pop(L, 1);
|
||||
|
||||
expectTracked(mgr, module);
|
||||
}
|
||||
|
||||
TEST(ConfigLuaRequire, wildcardLoadsSortedTableAndTracksFilesAndDirectory) {
|
||||
CScopedCompositor compositor;
|
||||
CTempDir tmp;
|
||||
const auto mainConfig = tmp.path() / "hyprland.lua";
|
||||
const auto modulesDir = tmp.path() / "modules";
|
||||
const auto moduleA = modulesDir / "a.lua";
|
||||
const auto moduleB = modulesDir / "b.lua";
|
||||
writeFile(mainConfig, "");
|
||||
writeFile(moduleB, "return 'b'");
|
||||
writeFile(moduleA, "return 'a'");
|
||||
|
||||
CConfigManager mgr;
|
||||
CConfigManagerPluginLuaTestAccessor::initializeOwnedLuaState(mgr, mainConfig);
|
||||
const auto L = CConfigManagerPluginLuaTestAccessor::luaState(mgr);
|
||||
|
||||
ASSERT_EQ(luaL_dostring(L, R"(
|
||||
mods = require("./modules/*")
|
||||
assert(#mods == 2)
|
||||
assert(mods[1] == "a")
|
||||
assert(mods[2] == "b")
|
||||
)"),
|
||||
LUA_OK)
|
||||
<< lua_tostring(L, -1);
|
||||
|
||||
expectTracked(mgr, modulesDir);
|
||||
expectTracked(mgr, moduleA);
|
||||
expectTracked(mgr, moduleB);
|
||||
}
|
||||
|
||||
TEST(ConfigLuaRequire, wildcardNoMatchIsCatchableError) {
|
||||
CScopedCompositor compositor;
|
||||
CTempDir tmp;
|
||||
const auto mainConfig = tmp.path() / "hyprland.lua";
|
||||
writeFile(mainConfig, "");
|
||||
|
||||
CConfigManager mgr;
|
||||
CConfigManagerPluginLuaTestAccessor::initializeOwnedLuaState(mgr, mainConfig);
|
||||
const auto L = CConfigManagerPluginLuaTestAccessor::luaState(mgr);
|
||||
|
||||
ASSERT_EQ(luaL_dostring(L, R"(
|
||||
ok, err = pcall(require, "./missing/*")
|
||||
assert(ok == false)
|
||||
assert(type(err) == "string")
|
||||
assert(string.find(err, "module './missing/*' not found", 1, true) ~= nil)
|
||||
)"),
|
||||
LUA_OK)
|
||||
<< lua_tostring(L, -1);
|
||||
}
|
||||
|
||||
TEST(ConfigLuaRequire, normalModuleRequireStillUsesConfigDirectoryPackagePath) {
|
||||
CScopedCompositor compositor;
|
||||
CTempDir tmp;
|
||||
const auto mainConfig = tmp.path() / "hyprland.lua";
|
||||
const auto module = tmp.path() / "colors.lua";
|
||||
writeFile(mainConfig, "");
|
||||
writeFile(module, "return 'normal-ok'");
|
||||
|
||||
CConfigManager mgr;
|
||||
CConfigManagerPluginLuaTestAccessor::initializeOwnedLuaState(mgr, mainConfig);
|
||||
const auto L = CConfigManagerPluginLuaTestAccessor::luaState(mgr);
|
||||
|
||||
ASSERT_EQ(luaL_dostring(L, R"(
|
||||
mod = require("colors")
|
||||
)"),
|
||||
LUA_OK)
|
||||
<< lua_tostring(L, -1);
|
||||
|
||||
lua_getglobal(L, "mod");
|
||||
ASSERT_TRUE(lua_isstring(L, -1));
|
||||
EXPECT_STREQ(lua_tostring(L, -1), "normal-ok");
|
||||
lua_pop(L, 1);
|
||||
|
||||
expectTracked(mgr, module);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user