commit
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
//! Выполнение Lua-сценариев приложения.
|
||||
//!
|
||||
//! При запуске приложение выполняет [`STARTUP_SCRIPT`] из каталога [`SCRIPT_DIR`]
|
||||
//! (см. [`ScriptEngine::run_startup`]). Скриптам доступны:
|
||||
//!
|
||||
//! * объект `app` — управление окном и редактором (методы [`AppApi`], вызываются
|
||||
//! через двоеточие: `app:next_tab()`);
|
||||
//! * функция `include(path)` — выполняет другой Lua-файл относительно [`SCRIPT_DIR`]
|
||||
//! (или по абсолютному пути) в общем глобальном окружении;
|
||||
//! * стандартный `require`, который также ищет модули в [`SCRIPT_DIR`].
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use mlua::{Lua, Table};
|
||||
|
||||
use crate::api::{AppApi, TabInfo};
|
||||
use crate::constants::{SCRIPT_DIR, STARTUP_SCRIPT};
|
||||
|
||||
/// Вкладка превращается в Lua-таблицу `{ index = 0, title = "main.rs" }`.
|
||||
impl mlua::IntoLua for TabInfo {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<mlua::Value> {
|
||||
let tab = lua.create_table()?;
|
||||
tab.set("index", self.index)?;
|
||||
tab.set("title", self.title)?;
|
||||
Ok(mlua::Value::Table(tab))
|
||||
}
|
||||
}
|
||||
|
||||
/// Движок Lua и состояние его глобального окружения.
|
||||
pub struct ScriptEngine {
|
||||
lua: Lua,
|
||||
}
|
||||
|
||||
impl ScriptEngine {
|
||||
/// Создаёт движок и публикует в нём API приложения.
|
||||
pub fn new(api: AppApi) -> mlua::Result<Self> {
|
||||
let engine = Self { lua: Lua::new() };
|
||||
engine.expose_app_api(api)?;
|
||||
engine.configure_require()?;
|
||||
Ok(engine)
|
||||
}
|
||||
|
||||
/// Выполняет стартовый сценарий из [`SCRIPT_DIR`].
|
||||
///
|
||||
/// # Errors
|
||||
/// Возвращает ошибку, если файла нет, его не удалось прочитать или в нём
|
||||
/// есть синтаксическая/исполняемая ошибка.
|
||||
pub fn run_startup(&self) -> mlua::Result<()> {
|
||||
self.run_file(&script_path(STARTUP_SCRIPT))
|
||||
}
|
||||
|
||||
/// Выполняет Lua-файл в глобальном окружении движка.
|
||||
pub fn run_file(&self, path: &Path) -> mlua::Result<()> {
|
||||
run_file(&self.lua, path).map(|_| ())
|
||||
}
|
||||
|
||||
/// Публикует объект `app` с функциями управления приложением.
|
||||
///
|
||||
/// Все методы [`AppApi`] из `#[mlua::userdata_impl]`-блока доступны как
|
||||
/// методы userdata: `app:next_tab()`, `app:set_active_text("...")`.
|
||||
/// Номера вкладок всюду нумеруются с нуля.
|
||||
fn expose_app_api(&self, api: AppApi) -> mlua::Result<()> {
|
||||
self.lua
|
||||
.globals()
|
||||
.set("app", self.lua.create_userdata(api)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Добавляет каталог сценариев в `package.path`, чтобы `require` находил
|
||||
/// соседние Lua-модули, и объявляет `include`.
|
||||
fn configure_require(&self) -> mlua::Result<()> {
|
||||
let globals = self.lua.globals();
|
||||
|
||||
if let Ok(package) = globals.get::<Table>("package") {
|
||||
let current: String = package.get("path").unwrap_or_default();
|
||||
let dir = SCRIPT_DIR;
|
||||
let extra = format!("{dir}/?.lua;{dir}/?/init.lua");
|
||||
let path = if current.is_empty() {
|
||||
extra
|
||||
} else {
|
||||
format!("{extra};{current}")
|
||||
};
|
||||
package.set("path", path)?;
|
||||
}
|
||||
|
||||
// include выполняет файл сразу, как dofile, но путь по умолчанию
|
||||
// отсчитывается от каталога сценариев, а не от cwd.
|
||||
globals.set(
|
||||
"include",
|
||||
self.lua
|
||||
.create_function(|lua, path: String| run_file(lua, &script_path(&path)))?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Путь к файлу сценария: абсолютные пути используются как есть, остальные
|
||||
/// отсчитываются от [`SCRIPT_DIR`].
|
||||
fn script_path(path: &str) -> PathBuf {
|
||||
let path = Path::new(path);
|
||||
if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
Path::new(SCRIPT_DIR).join(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Читает и выполняет Lua-файл, возвращая значения, которые вернул чанк.
|
||||
fn run_file(lua: &Lua, path: &Path) -> mlua::Result<mlua::MultiValue> {
|
||||
let source = std::fs::read_to_string(path).map_err(|err| {
|
||||
mlua::Error::RuntimeError(format!(
|
||||
"не удалось прочитать сценарий {}: {err}",
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
|
||||
lua.load(&source)
|
||||
.set_name(path.to_string_lossy().into_owned())
|
||||
.eval()
|
||||
}
|
||||
Reference in New Issue
Block a user