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