1use crate::module_exports::{ModuleContext, ModuleExports, ModuleFunction, ModuleParam};
8use shape_value::ValueWord;
9use std::sync::Arc;
10
11pub fn create_env_module() -> ModuleExports {
13 let mut module = ModuleExports::new("env");
14 module.description = "Environment variables and system information".to_string();
15
16 module.add_function_with_schema(
18 "get",
19 |args: &[ValueWord], _ctx: &ModuleContext| {
20 let name = args
21 .first()
22 .and_then(|a| a.as_str())
23 .ok_or_else(|| "env.get() requires a variable name string".to_string())?;
24
25 match std::env::var(name) {
26 Ok(val) => Ok(ValueWord::from_some(ValueWord::from_string(Arc::new(val)))),
27 Err(_) => Ok(ValueWord::none()),
28 }
29 },
30 ModuleFunction {
31 description: "Get the value of an environment variable, or none if not set".to_string(),
32 params: vec![ModuleParam {
33 name: "name".to_string(),
34 type_name: "string".to_string(),
35 required: true,
36 description: "Environment variable name".to_string(),
37 ..Default::default()
38 }],
39 return_type: Some("Option<string>".to_string()),
40 },
41 );
42
43 module.add_function_with_schema(
45 "has",
46 |args: &[ValueWord], _ctx: &ModuleContext| {
47 let name = args
48 .first()
49 .and_then(|a| a.as_str())
50 .ok_or_else(|| "env.has() requires a variable name string".to_string())?;
51
52 Ok(ValueWord::from_bool(std::env::var(name).is_ok()))
53 },
54 ModuleFunction {
55 description: "Check if an environment variable is set".to_string(),
56 params: vec![ModuleParam {
57 name: "name".to_string(),
58 type_name: "string".to_string(),
59 required: true,
60 description: "Environment variable name".to_string(),
61 ..Default::default()
62 }],
63 return_type: Some("bool".to_string()),
64 },
65 );
66
67 module.add_function_with_schema(
69 "all",
70 |_args: &[ValueWord], _ctx: &ModuleContext| {
71 let vars: Vec<(String, String)> = std::env::vars().collect();
72 let mut keys = Vec::with_capacity(vars.len());
73 let mut values = Vec::with_capacity(vars.len());
74
75 for (k, v) in vars.into_iter() {
76 keys.push(ValueWord::from_string(Arc::new(k)));
77 values.push(ValueWord::from_string(Arc::new(v)));
78 }
79
80 Ok(ValueWord::from_hashmap_pairs(keys, values))
81 },
82 ModuleFunction {
83 description: "Get all environment variables as a HashMap".to_string(),
84 params: vec![],
85 return_type: Some("HashMap<string, string>".to_string()),
86 },
87 );
88
89 module.add_function_with_schema(
91 "args",
92 |_args: &[ValueWord], _ctx: &ModuleContext| {
93 let args: Vec<ValueWord> = std::env::args()
94 .map(|a| ValueWord::from_string(Arc::new(a)))
95 .collect();
96 Ok(ValueWord::from_array(Arc::new(args)))
97 },
98 ModuleFunction {
99 description: "Get command-line arguments as an array of strings".to_string(),
100 params: vec![],
101 return_type: Some("Array<string>".to_string()),
102 },
103 );
104
105 module.add_function_with_schema(
107 "cwd",
108 |_args: &[ValueWord], _ctx: &ModuleContext| {
109 let cwd = std::env::current_dir().map_err(|e| format!("env.cwd() failed: {}", e))?;
110 let path_str = cwd.to_string_lossy().into_owned();
111 Ok(ValueWord::from_string(Arc::new(path_str)))
112 },
113 ModuleFunction {
114 description: "Get the current working directory".to_string(),
115 params: vec![],
116 return_type: Some("string".to_string()),
117 },
118 );
119
120 module.add_function_with_schema(
122 "os",
123 |_args: &[ValueWord], _ctx: &ModuleContext| {
124 Ok(ValueWord::from_string(Arc::new(
125 std::env::consts::OS.to_string(),
126 )))
127 },
128 ModuleFunction {
129 description: "Get the operating system name (e.g. linux, macos, windows)".to_string(),
130 params: vec![],
131 return_type: Some("string".to_string()),
132 },
133 );
134
135 module.add_function_with_schema(
137 "arch",
138 |_args: &[ValueWord], _ctx: &ModuleContext| {
139 Ok(ValueWord::from_string(Arc::new(
140 std::env::consts::ARCH.to_string(),
141 )))
142 },
143 ModuleFunction {
144 description: "Get the CPU architecture (e.g. x86_64, aarch64)".to_string(),
145 params: vec![],
146 return_type: Some("string".to_string()),
147 },
148 );
149
150 module
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 fn s(val: &str) -> ValueWord {
158 ValueWord::from_string(Arc::new(val.to_string()))
159 }
160
161 fn test_ctx() -> crate::module_exports::ModuleContext<'static> {
162 let registry = Box::leak(Box::new(crate::type_schema::TypeSchemaRegistry::new()));
163 crate::module_exports::ModuleContext {
164 schemas: registry,
165 invoke_callable: None,
166 raw_invoker: None,
167 function_hashes: None,
168 vm_state: None,
169 granted_permissions: None,
170 scope_constraints: None,
171 set_pending_resume: None,
172 set_pending_frame_resume: None,
173 }
174 }
175
176 #[test]
177 fn test_env_module_creation() {
178 let module = create_env_module();
179 assert_eq!(module.name, "env");
180 assert!(module.has_export("get"));
181 assert!(module.has_export("has"));
182 assert!(module.has_export("all"));
183 assert!(module.has_export("args"));
184 assert!(module.has_export("cwd"));
185 assert!(module.has_export("os"));
186 assert!(module.has_export("arch"));
187 }
188
189 #[test]
190 fn test_env_get_path() {
191 let module = create_env_module();
192 let ctx = test_ctx();
193 let f = module.get_export("get").unwrap();
194 let result = f(&[s("PATH")], &ctx).unwrap();
196 let inner = result.as_some_inner().expect("PATH should be set");
197 assert!(!inner.as_str().unwrap().is_empty());
198 }
199
200 #[test]
201 fn test_env_get_missing() {
202 let module = create_env_module();
203 let ctx = test_ctx();
204 let f = module.get_export("get").unwrap();
205 let result = f(&[s("__SHAPE_NONEXISTENT_VAR_12345__")], &ctx).unwrap();
206 assert!(result.is_none());
207 }
208
209 #[test]
210 fn test_env_get_requires_string() {
211 let module = create_env_module();
212 let ctx = test_ctx();
213 let f = module.get_export("get").unwrap();
214 assert!(f(&[ValueWord::from_f64(42.0)], &ctx).is_err());
215 }
216
217 #[test]
218 fn test_env_has_path() {
219 let module = create_env_module();
220 let ctx = test_ctx();
221 let f = module.get_export("has").unwrap();
222 let result = f(&[s("PATH")], &ctx).unwrap();
223 assert_eq!(result.as_bool(), Some(true));
224 }
225
226 #[test]
227 fn test_env_has_missing() {
228 let module = create_env_module();
229 let ctx = test_ctx();
230 let f = module.get_export("has").unwrap();
231 let result = f(&[s("__SHAPE_NONEXISTENT_VAR_12345__")], &ctx).unwrap();
232 assert_eq!(result.as_bool(), Some(false));
233 }
234
235 #[test]
236 fn test_env_all_returns_hashmap() {
237 let module = create_env_module();
238 let ctx = test_ctx();
239 let f = module.get_export("all").unwrap();
240 let result = f(&[], &ctx).unwrap();
241 let (keys, _values, _index) = result.as_hashmap().expect("should be hashmap");
242 assert!(!keys.is_empty());
244 }
245
246 #[test]
247 fn test_env_args_returns_array() {
248 let module = create_env_module();
249 let ctx = test_ctx();
250 let f = module.get_export("args").unwrap();
251 let result = f(&[], &ctx).unwrap();
252 let arr = result.as_any_array().expect("should be array").to_generic();
253 assert!(!arr.is_empty());
255 }
256
257 #[test]
258 fn test_env_cwd_returns_string() {
259 let module = create_env_module();
260 let ctx = test_ctx();
261 let f = module.get_export("cwd").unwrap();
262 let result = f(&[], &ctx).unwrap();
263 let cwd = result.as_str().expect("should be string");
264 assert!(!cwd.is_empty());
265 }
266
267 #[test]
268 fn test_env_os_returns_string() {
269 let module = create_env_module();
270 let ctx = test_ctx();
271 let f = module.get_export("os").unwrap();
272 let result = f(&[], &ctx).unwrap();
273 let os = result.as_str().expect("should be string");
274 assert!(!os.is_empty());
275 assert!(
277 ["linux", "macos", "windows", "freebsd", "android", "ios"].contains(&os),
278 "unexpected OS: {}",
279 os
280 );
281 }
282
283 #[test]
284 fn test_env_arch_returns_string() {
285 let module = create_env_module();
286 let ctx = test_ctx();
287 let f = module.get_export("arch").unwrap();
288 let result = f(&[], &ctx).unwrap();
289 let arch = result.as_str().expect("should be string");
290 assert!(!arch.is_empty());
291 }
292
293 #[test]
294 fn test_env_schemas() {
295 let module = create_env_module();
296
297 let get_schema = module.get_schema("get").unwrap();
298 assert_eq!(get_schema.params.len(), 1);
299 assert_eq!(get_schema.return_type.as_deref(), Some("Option<string>"));
300
301 let all_schema = module.get_schema("all").unwrap();
302 assert_eq!(all_schema.params.len(), 0);
303
304 let os_schema = module.get_schema("os").unwrap();
305 assert_eq!(os_schema.return_type.as_deref(), Some("string"));
306 }
307}