manen

Fancy Lua REPL
Log | Files | Refs | README | LICENSE

commit 7295ea536717320f4012a3bb7b8fb945d469b881
parent 759525159ae3e79db23d64b9e7c2540a0734135c
Author: Sylvia Ivory <git@sivory.net>
Date:   Tue,  1 Jul 2025 17:26:16 -0700

Allow loading from config files

Diffstat:
MCargo.lock | 38++++++++++++++++++++++++++++++++++++++
MCargo.toml | 2+-
Asrc/config.rs | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/editor.rs | 81++++++++++++++++++-------------------------------------------------------------
Msrc/inspect.rs | 10++++++++--
Msrc/main.rs | 1+
6 files changed, 208 insertions(+), 66 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -794,6 +794,7 @@ dependencies = [ "futures-util", "libloading", "mlua-sys", + "mlua_derive", "num-traits", "parking_lot", "rustc-hash 2.1.1", @@ -815,6 +816,21 @@ dependencies = [ ] [[package]] +name = "mlua_derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870d71c172fcf491c6b5fb4c04160619a2ee3e5a42a1402269c66bcbf1dd4deb" +dependencies = [ + "itertools 0.13.0", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -928,6 +944,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -25,7 +25,7 @@ ctrlc = "3.4.7" directories = "6.0.0" emmylua_parser = "0.10.8" lazy_static = "1.5.0" -mlua = { version = "0.10.5", features = ["anyhow", "send", "async"] } +mlua = { version = "0.10.5", features = ["anyhow", "send", "async", "macros"] } nu-ansi-term = "0.50.1" reedline = "0.40.0" rexpect = "0.6.2" diff --git a/src/config.rs b/src/config.rs @@ -0,0 +1,142 @@ +use directories::ProjectDirs; +use mlua::prelude::*; +use std::{path::PathBuf, sync::Arc}; + +use crate::{ + inspect::TableFormat, + lua::{LuaExecutor, MluaExecutor, SystemLuaError, SystemLuaExecutor}, +}; + +#[derive(Clone, Copy)] +pub enum Executor { + System, + Embedded, +} + +impl Default for Executor { + fn default() -> Self { + Self::Embedded + } +} + +#[derive(Clone, Default, FromLua)] +pub struct Config { + pub executor: Executor, + pub system_lua: Option<PathBuf>, + pub table_format: TableFormat, + pub history_size: usize, + pub color_output: bool, +} + +impl Config { + pub fn load() -> LuaResult<Self> { + let config = Self::default(); + + if let Some(proj_dirs) = ProjectDirs::from("net.sivory", "", "Manen") { + let config_file = proj_dirs.config_dir().join("config.lua"); + + if !config_file.exists() { + return Ok(config); + } + + let lua = Lua::new(); + + lua.globals().set("manen", config)?; + lua.load(config_file).exec()?; + + return lua.globals().get("manen"); + } + + Ok(config) + } + + pub fn get_executor(&self) -> Result<Arc<dyn LuaExecutor>, SystemLuaError> { + match self.executor { + Executor::Embedded => Ok(Arc::new(MluaExecutor::new())), + Executor::System => { + if let Some(path) = &self.system_lua { + Ok(Arc::new(SystemLuaExecutor::new(&path.to_string_lossy())?)) + } else { + Ok(Arc::new(MluaExecutor::new())) + } + } + } + } +} + +macro_rules! field { + ($value:ident, $as_field:ident, $field:expr, $expected:expr) => { + $value.$as_field().ok_or_else(|| { + LuaError::RuntimeError(format!( + "invalid type '{}' for {}, expected {}", + $value.type_name(), + $field, + $expected, + )) + })? + }; +} + +impl LuaUserData for Config { + fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) { + methods.add_meta_method_mut( + LuaMetaMethod::NewIndex, + |lua, this, (key, value): (String, LuaValue)| { + match key.as_str() { + "executor" => { + let executor = field!(value, as_string_lossy, "executor", "string"); + + match executor.as_str() { + "system" => this.executor = Executor::System, + "embedded" => this.executor = Executor::Embedded, + _ => { + return Err(LuaError::RuntimeError(String::from( + "expected valid executor format", + ))); + } + } + } + "system_lua" => { + if value.is_nil() { + this.system_lua = None; + return Ok(()); + } + + let path = PathBuf::from_lua(value, lua)?; + + if !path.exists() { + return Err(LuaError::RuntimeError(format!( + "path '{}' does not exist", + path.to_string_lossy() + ))); + } + + this.system_lua = Some(path); + } + "table_format" => { + let format = field!(value, as_string_lossy, "table_format", "string"); + + match format.as_str() { + "address" => this.table_format = TableFormat::Address, + "inspect" => this.table_format = TableFormat::Inspect, + "comfytable" => this.table_format = TableFormat::ComfyTable, + _ => { + return Err(LuaError::RuntimeError(String::from( + "expected valid table format", + ))); + } + } + } + "history_size" => { + this.history_size = field!(value, as_usize, "history_size", "integer"); + } + "color_output" => { + this.color_output = field!(value, as_boolean, "color_output", "bool"); + } + key => return Err(LuaError::RuntimeError(format!("invalid key '{key}'"))), + } + Ok(()) + }, + ); + } +} diff --git a/src/editor.rs b/src/editor.rs @@ -15,27 +15,25 @@ use reedline::{ }; use crate::{ - completion::LuaCompleter, - hinter::LuaHinter, - inspect::{TableFormat, display_basic}, - lua::{LuaExecutor, MluaExecutor, SystemLuaExecutor}, - parse::LuaHighlighter, - validator::LuaValidator, + completion::LuaCompleter, config::Config, hinter::LuaHinter, inspect::display_basic, + lua::LuaExecutor, parse::LuaHighlighter, validator::LuaValidator, }; pub struct Editor { prompt: DefaultPrompt, editor: Reedline, lua_executor: Arc<dyn LuaExecutor>, - - table_format: TableFormat, + config: Config, } impl Editor { pub fn new() -> LuaResult<Self> { - // let mlua = Arc::new(MluaExecutor::new()); - let mlua = Arc::new(SystemLuaExecutor::new("lua5.1").unwrap()); - let version: String = mlua.globals()?.get("_VERSION")?; + let config = Config::load()?; + let lua_executor = config + .get_executor() + .map_err(|e| LuaError::ExternalError(Arc::new(e)))?; + + let version: String = lua_executor.globals()?.get("_VERSION")?; let prompt = DefaultPrompt::new( DefaultPromptSegment::Basic(version), @@ -62,7 +60,7 @@ impl Editor { let mut editor = Reedline::create() .with_validator(Box::new(LuaValidator::new())) .with_completer(Box::new(LuaCompleter::new( - mlua.clone() as Arc<dyn LuaExecutor> + lua_executor.clone() as Arc<dyn LuaExecutor> ))) .with_highlighter(Box::new(LuaHighlighter)) .with_hinter(Box::new(LuaHinter)) @@ -71,6 +69,9 @@ impl Editor { if let Some(proj_dirs) = ProjectDirs::from("net.sivory", "", "Manen") { let history = FileBackedHistory::with_file(256, proj_dirs.data_dir().join("history")); + config.history_size, + proj_dirs.data_dir().join("history"), + ); if let Ok(history) = history { editor = editor.with_history(Box::new(history)) @@ -80,9 +81,8 @@ impl Editor { Ok(Self { prompt, editor, - - table_format: TableFormat::ComfyTable(true), - lua_executor: mlua, + lua_executor, + config, }) } @@ -109,14 +109,6 @@ impl Editor { match signal { Ok(Signal::Success(line)) => { - if line.starts_with(".") { - if let Err(e) = self.eval_special(&line) { - eprintln!("{e}") - } - - continue; - } - is_running_lua.store(true, Ordering::Relaxed); if let Err(e) = self.eval(&line) { @@ -131,50 +123,13 @@ impl Editor { } } - // .help - // .format <format> [true/false] - fn eval_special(&mut self, line: &str) -> LuaResult<()> { - let mut split = line.strip_prefix(".").unwrap().split_whitespace(); - - let cmd = split.next(); - - match cmd { - Some("help") => { - println!(".help\tPrint this message"); - println!( - ".format <inspect|address|comfytable> [true|false]\tConfigure table printing, boolean configures nesting" - ); - } - Some("format") => match split.next() { - Some("inspect") => { - self.table_format = TableFormat::Inspect; - } - Some("address") => { - self.table_format = TableFormat::Address; - } - Some("comfytable") => { - let nested = split - .next() - .unwrap_or("") - .parse::<bool>() - .unwrap_or_default(); - - self.table_format = TableFormat::ComfyTable(nested); - } - _ => println!("unknown subcommand"), - }, - _ => println!("unknown command"), - } - - Ok(()) - } - fn eval(&self, line: &str) -> LuaResult<()> { let value: LuaValue = self.lua_executor.exec(line)?; + let config = &self.config; let stringify = match value { - LuaValue::Table(tbl) => self.table_format.format(&tbl, true)?, - value => display_basic(&value, true), + LuaValue::Table(tbl) => config.table_format.format(&tbl, config.color_output)?, + value => display_basic(&value, config.color_output), }; println!("{stringify}"); diff --git a/src/inspect.rs b/src/inspect.rs @@ -368,11 +368,17 @@ pub fn inspect(value: &LuaValue, colorize: bool) -> LuaResult<String> { #[derive(Clone, Copy)] pub enum TableFormat { - ComfyTable(bool), + ComfyTable, Inspect, Address, } +impl Default for TableFormat { + fn default() -> Self { + Self::Inspect + } +} + fn comfy_table_inner( tbl: &LuaTable, recursive: bool, @@ -447,7 +453,7 @@ impl TableFormat { TableFormat::Inspect => { display_table(tbl, colorize).map_err(|e| LuaError::ExternalError(Arc::new(e))) } - TableFormat::ComfyTable(recursive) => comfy_table(tbl, *recursive), + TableFormat::ComfyTable => comfy_table(tbl, true), } } } diff --git a/src/main.rs b/src/main.rs @@ -15,6 +15,7 @@ use inspect::{comfy_table, inspect}; use parse::LuaHighlighter; mod completion; +mod config; mod editor; mod hinter; mod inspect;