~aleteoryx/lfm_embed

79999cecedd84e99655fbcdba0bd4e715a22173f — alyx 5 months ago ca7c46e
Lua support feature-complete

Full implementation of debug mode still pending
M src/main.rs => src/main.rs +1 -1
@@ 52,7 52,7 @@ async fn main() {
      let (userinfo, refresh) = get_userinfo(&s).await;
      let (ctx, status) = get_ctx(userinfo, q.font, q.rest).await;

      let (theme, res) = render_theme(q.theme.as_deref(), &ctx);
      let (theme, res) = tokio::task::spawn_blocking(move || render_theme(q.theme.as_deref(), &ctx)).await.expect("fatal error in theme rendering");
      log::debug!(target: "lfm_embed::main::user", "Using theme {theme}");

      match res {

M src/theming.rs => src/theming.rs +4 -2
@@ 12,10 12,12 @@ pub fn render_theme(name: Option<&str>, ctx: &crate::ctx::Ctx) -> (String, Resul

  if let Some(name) = name {
    theme = name;
    res = hbs::render_theme(name, ctx)
    res = lua::render_theme(name, ctx)
      .or_else(|| hbs::render_theme(name, ctx))
  }

  res = res.or_else(|| { log::debug!("Falling back to default theme!"); theme = &CONFIG.default_theme; None })
  res = res.or_else(|| { log::debug!("Falling back to default theme from requested theme `{theme}`!"); theme = &CONFIG.default_theme; None })
    .or_else(|| lua::render_theme(theme, ctx))
    .or_else(|| hbs::render_theme(theme, ctx));

  let res = res.unwrap_or_else(|| { log::error!("Couldn't load requested theme or default theme `{}`!", CONFIG.default_theme); Err(StatusCode::INTERNAL_SERVER_ERROR)});

M src/theming/hbs.rs => src/theming/hbs.rs +2 -2
@@ 15,13 15,13 @@ static HANDLEBARS: LazyLock<Handlebars> = LazyLock::new(|| {
  hb.register_helper("url-encode", Box::new(url_encode));

  for (key, fulltext) in INTERNAL_THEMES {
    log::info!("Registering internal theme `{key}`");
    log::info!("Registering internal handlebars theme `{key}`");
    hb.register_template_string(key, fulltext).unwrap();
  }
  hb.set_dev_mode(CONFIG.theme_debug);

  if let Some(themes_dir) = CONFIG.theme_dir.as_ref() {
    log::info!("Registering theme dir `{themes_dir}`");
    log::info!("Registering handlebars theme dir `{themes_dir}` with extension `{}`.", CONFIG.theme_ext_hbs);
    hb.register_templates_directory(&CONFIG.theme_ext_hbs, themes_dir.as_ref()).unwrap();
  }


M src/theming/lua-lib/expect.lua => src/theming/lua-lib/expect.lua +3 -3
@@ 50,7 50,7 @@ local function get_display_type(value, t)
    -- Cobalt and only read the metatable for tables/userdata.
    if t ~= "table" and t ~= "userdata" then return t end

    local metatable = debug.getmetatable(value)
    local metatable = getmetatable(value)
    if not metatable then return t end

    local name = rawget(metatable, "__name")


@@ 72,8 72,8 @@ local function expect(index, value, ...)

    -- If we can determine the function name with a high level of confidence, try to include it.
    local name
    local ok, info = pcall(debug.getinfo, 3, "nS")
    if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
    -- local ok, info = pcall(debug.getinfo, 3, "nS")
    -- if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end

    t = get_display_type(value, t)


M src/theming/lua-lib/html.lua => src/theming/lua-lib/html.lua +10 -4
@@ 16,8 16,8 @@ local function html(el, tbl)
  expect(1, el, "string")
  expect(2, tbl, "table")

  innerHtml = ""
  attributes = ""
  local innerHtml = ""
  local attributes = ""

  for k, v in pairs(tbl) do
    if type(k) == "string" then


@@ 39,6 39,12 @@ local function root(tbl)
end

return setmetatable(
  { root    = root },
  {},
  { __call  = html,
    __index = function (idx) return function(tbl) html(idx, tbl) end end })
    __index = function (_, idx)
      if idx == "root" then
        return root
      else
        return function(tbl) return html(idx, tbl) end
      end
    end })

M src/theming/lua.rs => src/theming/lua.rs +27 -12
@@ 1,10 1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
use std::sync::LazyLock;
use std::ops::Deref;
use std::path::Path;
use std::fs;

use tokio::sync::Mutex;
use mlua::{Lua, Compiler, StdLib, Value, LuaOptions};
use std::sync::Mutex;
use mlua::{Lua, LuaSerdeExt, Compiler, StdLib, Value, LuaOptions, Table};
use http::StatusCode;

use crate::CONFIG;


@@ 37,16 37,19 @@ static LUA: LazyLock<Mutex<Lua>> = LazyLock::new(|| {
  let themes = lua.create_table().expect("creating themes table");

  for (k, v) in INTERNAL_THEMES {
    log::info!("Registering compiled lua theme `{k}`.");
    let _ = themes.set(*k, lua.load(*v).into_function().expect("loading internal theme"));
  }

  if let Some(theme_dir) = CONFIG.theme_dir.as_ref() {
    log::info!("Registering lua theme dir `{theme_dir}` with extension `{}`.", CONFIG.theme_ext_lua);
    for (k, v) in walk_dir(theme_dir.as_ref().as_ref()).expect("walking theme dir") {
      let _ = themes.set(k, lua.load(v).into_function().expect("loading internal theme"));
      log::info!("Registering runtime lua theme `{k}`.");
      let _ = themes.set(k, lua.load(v).eval::<Value>().expect("loading internal theme"));
    }
  }

  let _ = lua.globals().set("__themes", themes).unwrap();
  lua.globals().set("__themes", themes).unwrap();

  lua.globals().set_readonly(true);



@@ 54,7 57,7 @@ static LUA: LazyLock<Mutex<Lua>> = LazyLock::new(|| {
});

fn walk_dir(path: &Path) -> std::io::Result<Vec<(String, String)>> {
  use std::fs;
  let ext = CONFIG.theme_ext_lua.as_ref();

  let mut path_bits = vec![];
  let mut dir_readers = vec![fs::read_dir(path)?];


@@ 67,7 70,7 @@ fn walk_dir(path: &Path) -> std::io::Result<Vec<(String, String)>> {
      let name = ent.file_name().into_string().expect("why do you have such FUCKED UP FILE PATHS");
      let ty = ent.file_type()?;
      if ty.is_file() && name.ends_with(CONFIG.theme_ext_lua.as_ref()) {
        ret.push((path_bits.join("") + &name, fs::read_to_string(ent.path())?));
        ret.push((path_bits.join("") + &name[..name.len() - ext.len()], fs::read_to_string(ent.path())?));
      }
      else if ty.is_dir() {
        path_bits.push(name);


@@ 82,10 85,22 @@ fn walk_dir(path: &Path) -> std::io::Result<Vec<(String, String)>> {
  Ok(ret)
}

pub fn touch() {
  let _ = LUA.deref();
}

pub fn render_theme(name: &str, ctx: &crate::ctx::Ctx) -> Option<Result<String, StatusCode>> {
  return None
  LUA.clear_poison();
  let lua = LUA.lock().expect("FIXME: Mutex poisoning race condition.");
  let themes: Table = lua.globals().get("__themes").unwrap();
  let Ok(Value::Function(theme)) = themes.get(name)
  else {
    return None;
  };

  let ctx = match lua.to_value(ctx) {
    Ok(ok) => ok,
    Err(e) => {
      log::error!("Lua context serialization error: {e}");
      return Some(Err(StatusCode::INTERNAL_SERVER_ERROR))
    }
  };

  Some(theme.call((ctx,)).map_err(|e| { log::error!("Lua theme execution error: {e}"); StatusCode::INTERNAL_SERVER_ERROR }))
}