xianyun_executor/exec/
mise_decorator.rs1use super::repository::{Command, RepositoryExecutorDecorator};
6use crate::exec::ExecError;
7
8#[derive(Default)]
10pub struct MiseDecorator;
11
12impl RepositoryExecutorDecorator for MiseDecorator {
13 fn wrap(&self, original: Command) -> Result<Command, ExecError> {
14 if look_path("mise").is_none() {
15 return Ok(original);
17 }
18
19 let mut new_args = vec!["exec".to_string(), "--".to_string(), original.name];
20 new_args.extend(original.args);
21
22 Ok(Command {
23 name: "mise".to_string(),
24 args: new_args,
25 env: original.env,
26 dir: original.dir,
27 })
28 }
29}
30
31fn look_path(name: &str) -> Option<std::path::PathBuf> {
34 let path_var = std::env::var_os("PATH")?;
35 for dir in std::env::split_paths(&path_var) {
36 let candidate = dir.join(name);
37 if candidate.is_file() {
38 #[cfg(unix)]
39 {
40 use std::os::unix::fs::PermissionsExt;
41 let Ok(meta) = std::fs::metadata(&candidate) else {
42 continue;
43 };
44 if meta.permissions().mode() & 0o111 == 0 {
45 continue;
46 }
47 }
48 return Some(candidate);
49 }
50 }
51 None
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 fn mise_available() -> bool {
59 look_path("mise").is_some()
60 }
61
62 #[test]
63 fn implements_interface() {
64 let _: &dyn RepositoryExecutorDecorator = &MiseDecorator;
65 }
66
67 #[test]
68 fn with_mise_available() {
69 if !mise_available() {
70 return; }
72
73 let decorator = MiseDecorator;
74 let original = Command {
75 name: "kubectl".into(),
76 args: vec!["get".into(), "pods".into()],
77 env: Some(vec!["TEST_VAR=value".into()]),
78 dir: Some("/test/dir".into()),
79 };
80
81 let result = decorator.wrap(original).unwrap();
82
83 assert_eq!(result.name, "mise");
84 assert_eq!(result.args, vec!["exec", "--", "kubectl", "get", "pods"]);
85 assert_eq!(result.env, Some(vec!["TEST_VAR=value".to_string()]));
86 assert_eq!(result.dir, Some("/test/dir".to_string()));
87 }
88
89 #[test]
90 fn with_mise_missing() {
91 if mise_available() {
92 return; }
94
95 let decorator = MiseDecorator;
96 let original = Command {
97 name: "kubectl".into(),
98 args: vec!["get".into(), "pods".into()],
99 env: None,
100 dir: None,
101 };
102
103 let result = decorator.wrap(original.clone()).unwrap();
104 assert_eq!(
105 result.name, original.name,
106 "should return original command unchanged when mise is not available"
107 );
108 assert_eq!(result.args, original.args);
109 }
110
111 #[test]
112 fn preserves_environment() {
113 if !mise_available() {
114 return;
115 }
116
117 let decorator = MiseDecorator;
118 let original = Command {
119 name: "test-command".into(),
120 args: vec![],
121 env: Some(vec![
122 "VAR1=value1".into(),
123 "VAR2=value2".into(),
124 format!("PATH={}", std::env::var("PATH").unwrap_or_default()),
125 ]),
126 dir: None,
127 };
128 let expected_env = original.env.clone();
129
130 let result = decorator.wrap(original).unwrap();
131 assert_eq!(
132 result.env, expected_env,
133 "environment variables should be preserved"
134 );
135 }
136
137 #[test]
138 fn preserves_directory() {
139 if !mise_available() {
140 return;
141 }
142
143 let decorator = MiseDecorator;
144 let original = Command {
145 name: "test-command".into(),
146 args: vec!["arg1".into(), "arg2".into()],
147 env: None,
148 dir: Some("/custom/working/directory".into()),
149 };
150
151 let result = decorator.wrap(original).unwrap();
152 assert_eq!(
153 result.dir,
154 Some("/custom/working/directory".to_string()),
155 "working directory should be preserved"
156 );
157 }
158
159 #[test]
160 fn handles_command_with_no_args() {
161 if !mise_available() {
162 return;
163 }
164
165 let decorator = MiseDecorator;
166 let original = Command {
167 name: "standalone-command".into(),
168 args: vec![],
169 env: None,
170 dir: None,
171 };
172
173 let result = decorator.wrap(original).unwrap();
174 assert_eq!(result.name, "mise");
175 assert_eq!(result.args, vec!["exec", "--", "standalone-command"]);
176 }
177
178 #[test]
179 fn handles_command_with_multiple_args() {
180 if !mise_available() {
181 return;
182 }
183
184 let decorator = MiseDecorator;
185 let original = Command {
186 name: "kubectl".into(),
187 args: vec![
188 "get".into(),
189 "pods".into(),
190 "--namespace".into(),
191 "default".into(),
192 "-o".into(),
193 "json".into(),
194 ],
195 env: None,
196 dir: None,
197 };
198
199 let result = decorator.wrap(original).unwrap();
200 assert_eq!(result.name, "mise");
201 assert_eq!(
202 result.args,
203 vec![
204 "exec",
205 "--",
206 "kubectl",
207 "get",
208 "pods",
209 "--namespace",
210 "default",
211 "-o",
212 "json"
213 ]
214 );
215 }
216}