diff options
author | Jo-Philipp Wich <jo@mein.io> | 2022-09-13 23:50:12 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2022-10-25 01:03:37 +0200 |
commit | 673f38246ac3548caefec41183e3dd7477d9f6f6 (patch) | |
tree | b3b7682b14d8a81286f8b7fe2aa5239e5dfbf4b7 /modules/luci-base/ucode/runtime.uc | |
parent | ded8ccf93ec5163be35c41501869110e5dab30d1 (diff) |
treewide: separate Lua runtime resources
Move classes required for Lua runtime support into a new `luci-lua-runtime`
package. Also replace the `luci.http` and `luci.util` classes in
`luci-lib-base` with stubbed versions interacting with the ucode based
runtime environment.
Finally merge `luci-base-ucode` into the remainders of `luci-base`.
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'modules/luci-base/ucode/runtime.uc')
-rw-r--r-- | modules/luci-base/ucode/runtime.uc | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/modules/luci-base/ucode/runtime.uc b/modules/luci-base/ucode/runtime.uc new file mode 100644 index 0000000000..a8b6812e74 --- /dev/null +++ b/modules/luci-base/ucode/runtime.uc @@ -0,0 +1,162 @@ +// Copyright 2022 Jo-Philipp Wich <jo@mein.io> +// Licensed to the public under the Apache License 2.0. + +import { access, basename } from 'fs'; +import { cursor } from 'uci'; + +const template_directory = '/usr/share/ucode/luci/template'; + +function cut_message(msg) { + return trim(replace(msg, /\n--\n.*$/, '')); +} + +function format_nested_exception(ex) { + let msg = replace(cut_message(ex.message), /(\n+( \|[^\n]*(\n|$))+)/, (m, m1) => { + m1 = replace(m1, /(^|\n) \| ?/g, '$1'); + m = match(m1, /^(.+?)\n(In.*line \d+, byte \d+:.+)$/); + + return ` + <div class="exception"> + <div class="message">${cut_message(m ? m[1] : m1)}</div> + ${m ? `<pre class="context">${trim(m[2])}</pre>` : ''} + </div> + `; + }); + + return ` + <div class="exception"> + <div class="message">${cut_message(msg)}</div> + <pre class="context">${trim(ex.stacktrace[0].context)}</pre> + </div> + `; +} + +function format_lua_exception(ex) { + let m = match(ex.message, /^(.+)\nstack traceback:\n(.+)$/); + + return ` + <div class="exception"> + <div class="message">${cut_message(m ? m[1] : ex.message)}</div> + <pre class="context">${m ? trim(replace(m[2], /(^|\n)\t/g, '$1')) : ex.stacktrace[0].context}</pre> + </div> + `; +} + +const Class = { + init_lua: function() { + if (!this.L) { + this.L = this.env.dispatcher.load_luabridge().create(); + this.L.set('L', proto({ write: print }, this.env)); + this.L.invoke('require', 'luci.ucodebridge'); + + this.env.lua_active = true; + } + + return this.L; + }, + + render_ucode: function(path, scope) { + let tmplfunc = loadfile(path, { raw_mode: false }); + call(tmplfunc, null, scope ?? {}); + }, + + render_lua: function(path, scope) { + let vm = this.init_lua(); + let render = vm.get('_G', 'luci', 'ucodebridge', 'render'); + + render.call(path, scope ?? {}); + }, + + trycompile: function(path) { + let ucode_path = `${template_directory}/${path}.ut`; + + if (access(ucode_path)) { + try { + loadfile(ucode_path, { raw_mode: false }); + } + catch (ucode_err) { + return `Unable to compile '${path}' as ucode template: ${format_nested_exception(ucode_err)}`; + } + } + else { + try { + let vm = this.init_lua(); + let compile = vm.get('_G', 'luci', 'ucodebridge', 'compile'); + + compile.call(path); + } + catch (lua_err) { + return `Unable to compile '${path}' as Lua template: ${format_lua_exception(lua_err)}`; + } + } + + return true; + }, + + render_any: function(path, scope) { + let ucode_path = `${template_directory}/${path}.ut`; + + scope = proto(scope ?? {}, this.scopes[-1]); + + push(this.scopes, scope); + + try { + if (access(ucode_path)) + this.render_ucode(ucode_path, scope); + else + this.render_lua(path, scope); + } + catch (ex) { + pop(this.scopes); + die(ex); + } + + pop(this.scopes); + }, + + render: function(path, scope) { + let self = this; + this.env.http.write(render(() => self.render_any(path, scope))); + }, + + call: function(modname, method, ...args) { + let vm = this.init_lua(); + let lcall = vm.get('_G', 'luci', 'ucodebridge', 'call'); + + return lcall.call(modname, method, ...args); + } +}; + +export default function(env) { + const self = proto({ env: env ??= {}, scopes: [ proto(env, global) ], global }, Class); + const uci = cursor(); + + // determine theme + let media = uci.get('luci', 'main', 'mediaurlbase'); + let status = self.trycompile(`themes/${basename(media)}/header`); + + if (status !== true) { + media = null; + + for (let k, v in uci.get_all('luci', 'themes')) { + if (substr(k, 0, 1) != '.') { + status = self.trycompile(`themes/${basename(v)}/header`); + + if (status === true) { + media = v; + break; + } + } + } + + if (!media) + error500(`Unable to render any theme header template, last error was:\n${status}`); + } + + self.env.media = media; + self.env.theme = basename(media); + self.env.resource = uci.get('luci', 'main', 'resourcebase'); + self.env.include = (...args) => self.render_any(...args); + + return self; +}; |