mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-04 05:50:15 +00:00
[lua] Add require() function (fix aseprite/api#10)
This is the first attempt to finally implement the require() function on Lua. The main problem was how to solve conflicts between plugins that use the same library name. Here we separate each plugin like in a namespace, so require(name) inside a plugin will save the module in _LOADED["pluginName/libraryName"] to avoid conflicts with other libraryName from other plugins.
This commit is contained in:
parent
8ff62f7a5f
commit
1c6e583c87
@ -196,6 +196,7 @@ if(ENABLE_SCRIPTING)
|
||||
script/properties_class.cpp
|
||||
script/range_class.cpp
|
||||
script/rectangle_class.cpp
|
||||
script/require.cpp
|
||||
script/security.cpp
|
||||
script/selection_class.cpp
|
||||
script/site_class.cpp
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -137,7 +137,7 @@ int DefaultCliDelegate::execScript(const std::string& filename,
|
||||
const Params& params)
|
||||
{
|
||||
auto engine = App::instance()->scriptEngine();
|
||||
if (!engine->evalFile(filename, params))
|
||||
if (!engine->evalUserFile(filename, params))
|
||||
throw base::Exception("Error executing script %s", filename.c_str());
|
||||
return engine->returnCode();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -78,7 +78,7 @@ void RunScriptCommand::onExecute(Context* context)
|
||||
|
||||
App::instance()
|
||||
->scriptEngine()
|
||||
->evalFile(m_filename, m_params);
|
||||
->evalUserFile(m_filename, m_params);
|
||||
|
||||
#if ENABLE_UI
|
||||
if (context->isUIAvailable())
|
||||
|
@ -35,6 +35,7 @@
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "app/script/require.h"
|
||||
#endif
|
||||
|
||||
#include "archive.h"
|
||||
@ -627,6 +628,10 @@ void Extension::initScripts()
|
||||
script::push_plugin(L, this);
|
||||
m_plugin.pluginRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
||||
// Set the _PLUGIN global so require() can find .lua files from the
|
||||
// plugin path.
|
||||
script::SetPluginForRequire setPlugin(L, m_plugin.pluginRef);
|
||||
|
||||
// Read plugin.preferences value
|
||||
{
|
||||
std::string fn = base::join_path(m_path, kPrefLua);
|
||||
@ -665,7 +670,7 @@ void Extension::initScripts()
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// Call the init() function of thi sscript with a Plugin object as first parameter
|
||||
// Call the init() function of this script with a Plugin object as first parameter
|
||||
if (lua_getglobal(L, "init") == LUA_TFUNCTION) {
|
||||
// Call init(plugin)
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_plugin.pluginRef);
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/script/blend_mode.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "app/script/require.h"
|
||||
#include "app/script/security.h"
|
||||
#include "app/sprite_sheet_type.h"
|
||||
#include "app/tilemap_mode.h"
|
||||
@ -217,16 +218,7 @@ Engine::Engine()
|
||||
#endif
|
||||
|
||||
// Standard Lua libraries
|
||||
luaL_requiref(L, LUA_GNAME, luaopen_base, 1);
|
||||
luaL_requiref(L, LUA_COLIBNAME, luaopen_coroutine, 1);
|
||||
luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1);
|
||||
luaL_requiref(L, LUA_IOLIBNAME, luaopen_io, 1);
|
||||
luaL_requiref(L, LUA_OSLIBNAME, luaopen_os, 1);
|
||||
luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1);
|
||||
luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1);
|
||||
luaL_requiref(L, LUA_UTF8LIBNAME, luaopen_utf8, 1);
|
||||
luaL_requiref(L, LUA_DBLIBNAME, luaopen_debug, 1);
|
||||
lua_pop(L, 9);
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Overwrite Lua functions
|
||||
lua_register(L, "print", print);
|
||||
@ -255,6 +247,9 @@ Engine::Engine()
|
||||
lua_setfield(L, -2, "execute");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Enhance require() function for plugins
|
||||
custom_require_function(L);
|
||||
|
||||
// Generic code used by metatables
|
||||
run_mt_index_code(L);
|
||||
|
||||
@ -579,7 +574,7 @@ bool Engine::evalFile(const std::string& filename,
|
||||
}
|
||||
std::string absFilename = base::get_absolute_path(filename);
|
||||
|
||||
AddScriptFilename add(absFilename);
|
||||
AddScriptFilename addScript(absFilename);
|
||||
set_app_params(L, params);
|
||||
|
||||
if (g_debuggerDelegate)
|
||||
@ -593,6 +588,18 @@ bool Engine::evalFile(const std::string& filename,
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Engine::evalUserFile(const std::string& filename,
|
||||
const Params& params)
|
||||
{
|
||||
// Set the _SCRIPT_PATH global so require() can find .lua files from
|
||||
// the script path.
|
||||
std::string path =
|
||||
base::get_file_path(
|
||||
base::get_absolute_path(filename));
|
||||
SetScriptForRequire setScript(L, path.c_str());
|
||||
return evalFile(filename, params);
|
||||
}
|
||||
|
||||
void Engine::startDebugger(DebuggerDelegate* debuggerDelegate)
|
||||
{
|
||||
g_debuggerDelegate = debuggerDelegate;
|
||||
|
@ -96,6 +96,8 @@ namespace app {
|
||||
const std::string& filename = std::string());
|
||||
bool evalFile(const std::string& filename,
|
||||
const Params& params = Params());
|
||||
bool evalUserFile(const std::string& filename,
|
||||
const Params& params = Params());
|
||||
|
||||
void consolePrint(const char* text) {
|
||||
onConsolePrint(text);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -44,6 +44,8 @@ void run_mt_index_code(lua_State* L)
|
||||
const char* s = lua_tostring(L, -1);
|
||||
if (s)
|
||||
std::puts(s);
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,6 +313,20 @@ int Plugin_newMenuSeparator(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Plugin_get_name(lua_State* L)
|
||||
{
|
||||
auto plugin = get_obj<Plugin>(L, 1);
|
||||
lua_pushstring(L, plugin->ext->name().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Plugin_get_path(lua_State* L)
|
||||
{
|
||||
auto plugin = get_obj<Plugin>(L, 1);
|
||||
lua_pushstring(L, plugin->ext->path().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Plugin_get_preferences(lua_State* L)
|
||||
{
|
||||
if (!lua_getuservalue(L, 1)) {
|
||||
@ -341,6 +355,8 @@ const luaL_Reg Plugin_methods[] = {
|
||||
};
|
||||
|
||||
const Property Plugin_properties[] = {
|
||||
{ "name", Plugin_get_name, nullptr },
|
||||
{ "path", Plugin_get_path, nullptr },
|
||||
{ "preferences", Plugin_get_preferences, Plugin_set_preferences },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
105
src/app/script/require.cpp
Normal file
105
src/app/script/require.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2023 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/script/require.h"
|
||||
|
||||
#include "app/extensions.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
static void eval_code(lua_State* L, const char* code)
|
||||
{
|
||||
if (luaL_loadbuffer(L, code, std::strlen(code), "internal") ||
|
||||
lua_pcall(L, 0, 0, 0)) {
|
||||
// Error case
|
||||
const char* s = lua_tostring(L, -1);
|
||||
if (s)
|
||||
std::puts(s);
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void custom_require_function(lua_State* L)
|
||||
{
|
||||
eval_code(L, R"(
|
||||
_PACKAGE_PATH_STACK = {}
|
||||
|
||||
local origRequire = require
|
||||
function require(name)
|
||||
if _PLUGIN then
|
||||
local module = origRequire(_PLUGIN.name .. '/' .. name)
|
||||
if module then return module end
|
||||
end
|
||||
return origRequire(name)
|
||||
end
|
||||
|
||||
local origLuaSearcher = package.searchers[2]
|
||||
package.searchers[2] = function(name)
|
||||
if _PLUGIN then
|
||||
name = name:sub(#_PLUGIN.name+2)
|
||||
end
|
||||
return origLuaSearcher(name)
|
||||
end
|
||||
)");
|
||||
}
|
||||
|
||||
SetPluginForRequire::SetPluginForRequire(lua_State* L, int pluginRef) : L(L)
|
||||
{
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, pluginRef);
|
||||
lua_setglobal(L, "_PLUGIN");
|
||||
|
||||
// We could use all elements of package.config, but "/?.lua;" should work:
|
||||
//
|
||||
// local templateSep = package.config:sub(3, 3)
|
||||
// local substitutionPoint = package.config:sub(5, 5)
|
||||
//
|
||||
eval_code(L, R"(
|
||||
table.insert(_PACKAGE_PATH_STACK, package.path)
|
||||
|
||||
local pathSep = package.config:sub(1, 1)
|
||||
package.path = _PLUGIN.path .. pathSep .. '?.lua;' .. package.path
|
||||
)");
|
||||
}
|
||||
|
||||
SetPluginForRequire::~SetPluginForRequire()
|
||||
{
|
||||
eval_code(L, R"(
|
||||
package.path = table.remove(_PACKAGE_PATH_STACK)
|
||||
_PLUGIN_OLDPATH = nil
|
||||
_PLUGIN = nil
|
||||
)");
|
||||
}
|
||||
|
||||
SetScriptForRequire::SetScriptForRequire(lua_State* L, const char* path) : L(L)
|
||||
{
|
||||
lua_pushstring(L, path);
|
||||
lua_setglobal(L, "_SCRIPT_PATH");
|
||||
eval_code(L, R"(
|
||||
table.insert(_PACKAGE_PATH_STACK, package.path)
|
||||
|
||||
local pathSep = package.config:sub(1, 1)
|
||||
package.path = _SCRIPT_PATH .. pathSep .. '?.lua;' .. package.path
|
||||
)");
|
||||
}
|
||||
|
||||
SetScriptForRequire::~SetScriptForRequire()
|
||||
{
|
||||
eval_code(L, R"(
|
||||
package.path = table.remove(_PACKAGE_PATH_STACK)
|
||||
_SCRIPT_PATH = nil
|
||||
)");
|
||||
}
|
||||
|
||||
} // namespace script
|
||||
} // namespace app
|
39
src/app/script/require.h
Normal file
39
src/app/script/require.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2023 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_SCRIPT_REQUIRE_H_INCLUDED
|
||||
#define APP_SCRIPT_REQUIRE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/script/luacpp.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
class SetPluginForRequire {
|
||||
public:
|
||||
SetPluginForRequire(lua_State* L, int pluginRef);
|
||||
~SetPluginForRequire();
|
||||
private:
|
||||
lua_State* L;
|
||||
};
|
||||
|
||||
class SetScriptForRequire {
|
||||
public:
|
||||
SetScriptForRequire(lua_State* L, const char* path);
|
||||
~SetScriptForRequire();
|
||||
private:
|
||||
lua_State* L;
|
||||
};
|
||||
|
||||
void custom_require_function(lua_State* L);
|
||||
|
||||
} // namespace script
|
||||
} // namespace app
|
||||
|
||||
#endif
|
11
tests/scripts/require.lua
Normal file
11
tests/scripts/require.lua
Normal file
@ -0,0 +1,11 @@
|
||||
-- Copyright (c) 2023 Igara Studio S.A.
|
||||
--
|
||||
-- This file is released under the terms of the MIT license.
|
||||
-- Read LICENSE.txt for more information.
|
||||
|
||||
local mod = require 'require_module'
|
||||
assert(mod.a == 5)
|
||||
mod.a = 6
|
||||
|
||||
local mod2 = require 'require_module'
|
||||
assert(mod2.a == 6) -- Now mod and mod2 points to the same object
|
6
tests/scripts/require_module.lua
Normal file
6
tests/scripts/require_module.lua
Normal file
@ -0,0 +1,6 @@
|
||||
-- Copyright (c) 2023 Igara Studio S.A.
|
||||
--
|
||||
-- This file is released under the terms of the MIT license.
|
||||
-- Read LICENSE.txt for more information.
|
||||
|
||||
return { a=5 }
|
2
third_party/lua
vendored
2
third_party/lua
vendored
@ -1 +1 @@
|
||||
Subproject commit db8284f5fc6f02f8f800c99b84b5e4f640e893ce
|
||||
Subproject commit 04abf20c9e8c973c62b30d0b98f6884ff665b1ba
|
Loading…
Reference in New Issue
Block a user