openrr_config/
evaluate.rs
1use std::{env, path::Path, process::Command};
2
3use anyhow::{bail, Context, Result};
4
5pub fn evaluate(mut s: &str, current_dir: Option<&Path>) -> Result<String> {
25 let mut out = String::new();
26
27 loop {
28 match s.find('$') {
29 Some(pos) => {
30 out.push_str(&s[..pos]);
31 s = &s[pos..];
32 }
33 None => {
34 out.push_str(s);
35 break;
36 }
37 }
38 match s.as_bytes().get(1) {
39 Some(b'(') => {
40 let end = match s.find(')') {
41 Some(end) => end,
42 None => bail!("unclosed command literal {s:?}"),
43 };
44 let script = &s[2..end];
45 s = s.get(end + 1..).unwrap_or_default();
46
47 let mut script = script.split(' ');
48 let mut cmd = Command::new(script.next().unwrap());
49 cmd.args(script);
50 if let Some(dir) = current_dir {
51 cmd.current_dir(dir);
52 }
53 let output = cmd
54 .output()
55 .with_context(|| format!("could not run `{cmd:?}`"))?;
56 if !output.status.success() {
57 bail!(
58 "`{cmd:?}` didn't exit successfully\nstdout:\n{}\n\nstderr:\n{}\n",
59 String::from_utf8_lossy(&output.stdout),
60 String::from_utf8_lossy(&output.stderr)
61 );
62 }
63 let mut output = String::from_utf8(output.stdout)?;
64 while output.ends_with('\n') || output.ends_with('\r') {
65 output.pop();
66 }
67 out.push_str(&output);
68 }
69 Some(b'{') => {
70 let end = match s.find('}') {
71 Some(end) => end,
72 None => bail!("unclosed environment variable literal {s:?}"),
73 };
74 let key = &s[2..end];
75 s = s.get(end + 1..).unwrap_or_default();
76
77 let var = env::var(key)
78 .with_context(|| format!("could not get environment variable {key:?}"))?;
79 out.push_str(&var);
80 }
81 Some(_) => {
82 out.push('$');
83 s = &s[1..];
84 }
85 None => {
86 out.push('$');
87 break;
88 }
89 }
90 }
91
92 Ok(out)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test() {
101 assert_eq!(evaluate("a", None).unwrap(), "a");
102 assert_eq!(evaluate("a$", None).unwrap(), "a$");
103 assert_eq!(evaluate("$a", None).unwrap(), "$a");
104 assert_eq!(evaluate("$(echo a)", None).unwrap(), "a");
105 assert_eq!(evaluate("$(echo a)b", None).unwrap(), "ab");
106 assert_eq!(evaluate("$(echo a))", None).unwrap(), "a)");
107 assert_eq!(evaluate("$$(echo a)", None).unwrap(), "$a");
108 assert_eq!(
109 evaluate("$(echo a)", Some(&env::current_dir().unwrap())).unwrap(),
110 "a"
111 );
112 evaluate("$(echo a", None).unwrap_err();
113 evaluate("${OPENRR_CONFIG_TEST_ENV}", None).unwrap_err();
114 env::set_var("OPENRR_CONFIG_TEST_ENV", "a");
115 assert_eq!(evaluate("${OPENRR_CONFIG_TEST_ENV}", None).unwrap(), "a");
116 assert_eq!(evaluate("${OPENRR_CONFIG_TEST_ENV}b", None).unwrap(), "ab");
117 assert_eq!(evaluate("${OPENRR_CONFIG_TEST_ENV}}", None).unwrap(), "a}");
118 assert_eq!(
119 evaluate("$${OPENRR_CONFIG_TEST_ENV}}", None).unwrap(),
120 "$a}"
121 );
122 evaluate("${OPENRR_CONFIG_TEST_ENV", None).unwrap_err();
123 }
124}