zoi_project/
lua_config.rs1use crate::config::{
2 CommandSpec, EnvironmentSpec, PackageSpec, ProjectConfig, ProjectLocalConfig, RegistrySpec,
3};
4use anyhow::{Result, anyhow};
5use mlua::{Lua, LuaSerdeExt, Table, Value};
6use std::collections::HashMap;
7use std::fs;
8use std::path::Path;
9
10pub fn load_zoi_lua(path: &Path, env: HashMap<String, String>) -> Result<ProjectConfig> {
20 let lua = Lua::new();
21 let content = fs::read_to_string(path)?;
22
23 let project_data = std::sync::Arc::new(std::sync::Mutex::new(HashMap::new()));
24 let packages_data = std::sync::Arc::new(std::sync::Mutex::new(HashMap::new()));
25 let registries_data = std::sync::Arc::new(std::sync::Mutex::new(HashMap::new()));
26 let tasks_data = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
27 let environments_data = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
28
29 let env_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
30 for (k, v) in &env {
31 env_table
32 .set(k.as_str(), v.as_str())
33 .map_err(|e| anyhow!(e.to_string()))?;
34 }
35 lua.globals()
36 .set("ENV", env_table)
37 .map_err(|e| anyhow!(e.to_string()))?;
38
39 let p_clone = project_data.clone();
40 let project_fn = lua
41 .create_function(move |lua, table: Table| {
42 let mut data = p_clone.lock().unwrap();
43 for pair in table.pairs::<String, Value>() {
44 let (k, v) = pair?;
45 data.insert(k, lua.from_value::<serde_json::Value>(v)?);
46 }
47 Ok(())
48 })
49 .map_err(|e| anyhow!(e.to_string()))?;
50 lua.globals()
51 .set("project", project_fn)
52 .map_err(|e| anyhow!(e.to_string()))?;
53
54 let pkgs_clone = packages_data.clone();
55 let packages_fn = lua
56 .create_function(move |lua, table: Table| {
57 let mut data = pkgs_clone.lock().unwrap();
58 for pair in table.pairs::<Value, Value>() {
59 let (k, v) = pair?;
60 match k {
61 Value::String(s) => {
62 let key = s.to_str()?.trim().to_string();
63 let spec = lua.from_value::<PackageSpec>(v)?;
64 data.insert(key, spec);
65 }
66 Value::Integer(_) => {
67 if let Value::String(s) = v {
68 data.insert(
69 s.to_str()?.trim().to_string(),
70 PackageSpec {
71 package_type: None,
72 install_method: None,
73 sub_packages: None,
74 version: None,
75 dependencies: None,
76 options: None,
77 optionals: None,
78 },
79 );
80 }
81 }
82 _ => {}
83 }
84 }
85 Ok(())
86 })
87 .map_err(|e| anyhow!(e.to_string()))?;
88 lua.globals()
89 .set("packages", packages_fn)
90 .map_err(|e| anyhow!(e.to_string()))?;
91
92 let regs_clone = registries_data.clone();
93 let registries_fn = lua
94 .create_function(move |lua, table: Table| {
95 let mut data = regs_clone.lock().unwrap();
96 for pair in table.pairs::<String, Value>() {
97 let (k, v) = pair?;
98 let spec = lua.from_value::<RegistrySpec>(v)?;
99 data.insert(k, spec);
100 }
101 Ok(())
102 })
103 .map_err(|e| anyhow!(e.to_string()))?;
104 lua.globals()
105 .set("registries", registries_fn)
106 .map_err(|e| anyhow!(e.to_string()))?;
107
108 let tasks_clone = tasks_data.clone();
109 let tasks_fn = lua
110 .create_function(move |lua, table: Table| {
111 let mut data = tasks_clone.lock().unwrap();
112 for val in table.sequence_values::<Value>() {
113 let spec = lua.from_value::<CommandSpec>(val?)?;
114 data.push(spec);
115 }
116 Ok(())
117 })
118 .map_err(|e| anyhow!(e.to_string()))?;
119 lua.globals()
120 .set("tasks", tasks_fn)
121 .map_err(|e| anyhow!(e.to_string()))?;
122
123 let envs_clone = environments_data.clone();
124 let environments_fn = lua
125 .create_function(move |lua, table: Table| {
126 let mut data = envs_clone.lock().unwrap();
127 for val in table.sequence_values::<Value>() {
128 let spec = lua.from_value::<EnvironmentSpec>(val?)?;
129 data.push(spec);
130 }
131 Ok(())
132 })
133 .map_err(|e| anyhow!(e.to_string()))?;
134 lua.globals()
135 .set("environments", environments_fn)
136 .map_err(|e| anyhow!(e.to_string()))?;
137
138 lua.load(&content)
139 .exec()
140 .map_err(|e| anyhow!("Failed to execute zoi.lua: {}", e))?;
141
142 let project_map = project_data.lock().unwrap();
143 let name = project_map
144 .get("name")
145 .and_then(|v| v.as_str())
146 .map(|s| s.to_string())
147 .ok_or_else(|| anyhow!("zoi.lua must define project name"))?;
148
149 let local = project_map
150 .get("config")
151 .and_then(|v| v.as_object())
152 .and_then(|obj| obj.get("local"))
153 .and_then(|v| v.as_bool())
154 .or_else(|| project_map.get("local").and_then(|v| v.as_bool()))
155 .unwrap_or(false);
156
157 let mut pkgs = Vec::new();
158 let pkgs_v2 = packages_data.lock().unwrap().clone();
159 for (k, spec) in &pkgs_v2 {
160 let mut s = k.clone();
161 if let Some(v) = &spec.version {
162 s = format!("{}@{}", s, v);
163 }
164 pkgs.push(s);
165 }
166
167 Ok(ProjectConfig {
168 name,
169 registries: registries_data.lock().unwrap().clone(),
170 packages: Vec::new(),
171 pkgs,
172 pkgs_v2,
173 config: ProjectLocalConfig { local },
174 commands: tasks_data.lock().unwrap().clone(),
175 environments: environments_data.lock().unwrap().clone(),
176 shell: None,
177 })
178}