husk/
shell.rs

1//! Higher-level abstractions to the [`syntax`](mod@crate::syntax), [`expand`](mod@crate::expand), and [`interp`](mod@crate::interp) modules.
2use crate::{bindings, util};
3use std::{collections::HashMap, ffi::CString};
4
5/// Perform shell expansion on `shell_string` as if it were within double quotes, using `env_vars`
6/// to resolve variables. This includes parameter expansion, arithmetic expansion, and quote
7/// removal.
8///
9/// System environment variables can be used for expansion by using the [`util::env_map`]
10/// function.
11///
12/// Command substitutions like `$(echo foo)` aren't supported to avoid running arbitrary code. To
13/// support those, use an interpreter from the [`expand`](mod@crate::expand) module.
14///
15/// If the input string has invalid syntax, [`Err`] is returned.
16///
17/// # Example
18/// ```rust
19/// use std::env;
20/// use husk::{shell, util};
21///
22/// env::set_var("NAME", "Foo Bar");
23/// let env_map = util::env_map();
24/// let expanded_string = shell::expand("Hello, ${NAME}!", env_map).unwrap();
25/// assert_eq!(expanded_string, "Hello, Foo Bar!");
26/// ```
27pub fn expand<S: AsRef<str>>(
28    shell_string: &str,
29    env_vars: HashMap<S, S>,
30) -> Result<String, String> {
31    let shell_string_ffi = CString::new(shell_string).unwrap();
32    let mut env_vec: Vec<CString> = vec![];
33
34    for (key, value) in env_vars {
35        let env_var_ffi =
36            CString::new(format!("{}={}", key.as_ref(), value.as_ref()).as_str()).unwrap();
37        env_vec.push(env_var_ffi);
38    }
39
40    let env_vec_ptrs: Vec<*mut libc::c_char> = env_vec
41        .iter()
42        .map(|string| string.as_ptr() as *mut libc::c_char)
43        .collect();
44    let resp = unsafe {
45        bindings::HuskShellExpand(
46            shell_string_ffi.as_ptr() as *mut libc::c_char,
47            env_vec_ptrs.as_ptr() as *mut *mut libc::c_char,
48            env_vec_ptrs.len().try_into().unwrap(),
49        )
50    };
51
52    let res = unsafe { CString::from_raw(resp.r0).into_string().unwrap() };
53    if resp.r1 == 0 {
54        Ok(res)
55    } else {
56        Err(res)
57    }
58}
59
60/// Perform shell expansion on `shell_string` as if it were command arguments, using `env_vars` to
61/// resolve variables. It is similar to [`expand`], but includes brace expansion, tilde expansion,
62/// and globbing.
63///
64/// System environment variables can be used for expansion by using the [`util::env_map`]
65/// function.
66///
67/// If the input string has invalid syntax, [`Err`] is returned.
68pub fn fields<S: AsRef<str>>(
69    shell_string: &str,
70    env_vars: HashMap<S, S>,
71) -> Result<Vec<String>, String> {
72    let shell_string_ffi = CString::new(shell_string).unwrap();
73
74    let mut env_vec: Vec<CString> = vec![];
75
76    for (key, value) in env_vars {
77        let env_var_ffi =
78            CString::new(format!("{}={}", key.as_ref(), value.as_ref()).as_str()).unwrap();
79        env_vec.push(env_var_ffi);
80    }
81
82    let env_vec_ptrs: Vec<*mut libc::c_char> = env_vec
83        .iter()
84        .map(|string| string.as_ptr() as *mut libc::c_char)
85        .collect();
86    let resp = unsafe {
87        bindings::HuskShellFields(
88            shell_string_ffi.as_ptr() as *mut libc::c_char,
89            env_vec_ptrs.as_ptr() as *mut *mut libc::c_char,
90            env_vec_ptrs.len().try_into().unwrap(),
91        )
92    };
93
94    if !resp.r1.is_null() {
95        Err(unsafe { CString::from_raw(resp.r1).into_string().unwrap() })
96    } else {
97        Ok(unsafe { util::go_to_rust_string_vec(resp.r0) })
98    }
99}