1use mlua::prelude::LuaError;
2use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};
3use std::path::PathBuf;
4
5use crate::Plugin;
6use crate::error::Result;
7use crate::hooks::env_keys::EnvKey;
8
9#[derive(Debug)]
10pub struct MiseEnvContext<T: serde::Serialize> {
11 pub args: Vec<String>,
12 pub options: T,
13}
14
15#[derive(Debug, Default)]
19pub struct MiseEnvResult {
20 pub env: Vec<EnvKey>,
22 pub cacheable: bool,
25 pub watch_files: Vec<PathBuf>,
27}
28
29impl Plugin {
30 pub async fn mise_env<T: serde::Serialize>(
31 &self,
32 ctx: MiseEnvContext<T>,
33 ) -> Result<MiseEnvResult> {
34 debug!("[vfox:{}] mise_env", &self.name);
35 let result = self
36 .eval_async(chunk! {
37 require "hooks/mise_env"
38 return PLUGIN:MiseEnv($ctx)
39 })
40 .await?;
41
42 Ok(result)
43 }
44}
45
46impl<T: serde::Serialize> IntoLua for MiseEnvContext<T> {
47 fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
48 let table = lua.create_table()?;
49 table.set("options", lua.to_value(&self.options)?)?;
50 Ok(Value::Table(table))
51 }
52}
53
54impl FromLua for MiseEnvResult {
55 fn from_lua(value: Value, lua: &Lua) -> std::result::Result<Self, LuaError> {
56 match value {
57 Value::Table(table) => {
59 let has_env = table.contains_key("env")?;
61 let has_cacheable = table.contains_key("cacheable")?;
62 let has_watch_files = table.contains_key("watch_files")?;
63
64 if has_env || has_cacheable || has_watch_files {
65 let env: Vec<EnvKey> = table
67 .get::<Option<Vec<EnvKey>>>("env")
68 .map_err(|e| {
69 LuaError::RuntimeError(format!(
70 "Invalid 'env' field in MiseEnv result: expected array of {{key, value}} pairs. Error: {e}"
71 ))
72 })?
73 .unwrap_or_default();
74 let cacheable: bool = table
75 .get::<Option<bool>>("cacheable")
76 .map_err(|e| {
77 LuaError::RuntimeError(format!(
78 "Invalid 'cacheable' field in MiseEnv result: expected boolean. Error: {e}"
79 ))
80 })?
81 .unwrap_or(false);
82 let watch_files: Vec<String> = table
83 .get::<Option<Vec<String>>>("watch_files")
84 .map_err(|e| {
85 LuaError::RuntimeError(format!(
86 "Invalid 'watch_files' field in MiseEnv result: expected array of strings. Error: {e}"
87 ))
88 })?
89 .unwrap_or_default();
90
91 Ok(MiseEnvResult {
92 env,
93 cacheable,
94 watch_files: watch_files.into_iter().map(PathBuf::from).collect(),
95 })
96 } else {
97 let env: Vec<EnvKey> = Vec::from_lua(Value::Table(table), lua).map_err(|e| {
100 LuaError::RuntimeError(format!(
101 "Failed to parse MiseEnv hook result. Expected either:\n\
102 - Legacy format: array of {{key, value}} pairs like {{{{\"KEY\", \"VALUE\"}}, ...}}\n\
103 - Extended format: table with 'env' field like {{env = {{}}, cacheable = true}}\n\
104 Error: {e}"
105 ))
106 })?;
107 Ok(MiseEnvResult {
108 env,
109 cacheable: false,
110 watch_files: vec![],
111 })
112 }
113 }
114 Value::Nil => Ok(MiseEnvResult::default()),
116 _ => Err(LuaError::RuntimeError(
117 "Expected table or nil from MiseEnv hook".to_string(),
118 )),
119 }
120 }
121}