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:
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;