cargo_nvim/
cargo_commands.rs1use mlua::prelude::*;
3use std::process::Command;
4use std::sync::Arc;
5use tokio::runtime::Runtime;
6
7#[derive(Clone)]
10pub struct CargoCommands {
11 runtime: Arc<Runtime>,
12}
13
14impl CargoCommands {
15 pub fn new() -> LuaResult<Self> {
17 Ok(Self {
18 runtime: Arc::new(
19 tokio::runtime::Builder::new_current_thread()
20 .enable_all()
21 .build()
22 .map_err(|e| LuaError::RuntimeError(e.to_string()))?,
23 ),
24 })
25 }
26
27 pub fn execute<F, T>(&self, future: F) -> T
29 where
30 F: std::future::Future<Output = T>,
31 {
32 self.runtime.block_on(future)
33 }
34
35 #[cfg(not(test))]
37 async fn execute_cargo_command(&self, command: &str, args: &[&str]) -> LuaResult<String> {
38 self.execute_cargo_command_internal(command, args).await
39 }
40
41 #[cfg(test)]
43 pub async fn execute_cargo_command(&self, command: &str, args: &[&str]) -> LuaResult<String> {
44 self.execute_cargo_command_internal(command, args).await
45 }
46
47 async fn execute_cargo_command_internal(
49 &self,
50 command: &str,
51 args: &[&str],
52 ) -> LuaResult<String> {
53 let mut cmd = Command::new("cargo");
54 cmd.arg(command);
55 cmd.args(args);
56
57 let output = cmd.output().map_err(|e| {
58 LuaError::RuntimeError(format!("Failed to execute cargo {}: {}", command, e))
59 })?;
60
61 if !output.status.success() {
62 let error = String::from_utf8_lossy(&output.stderr);
63 return Err(LuaError::RuntimeError(format!(
64 "cargo {} failed: {}",
65 command, error
66 )));
67 }
68
69 Ok(String::from_utf8_lossy(&output.stdout).into_owned())
70 }
71
72 pub async fn cargo_bench(&self, args: &[&str]) -> LuaResult<String> {
76 self.execute_cargo_command("bench", args).await
77 }
78
79 pub async fn cargo_build(&self, args: &[&str]) -> LuaResult<String> {
81 self.execute_cargo_command("build", args).await
82 }
83
84 pub async fn cargo_clean(&self, args: &[&str]) -> LuaResult<String> {
86 self.execute_cargo_command("clean", args).await
87 }
88
89 pub async fn cargo_doc(&self, args: &[&str]) -> LuaResult<String> {
91 self.execute_cargo_command("doc", args).await
92 }
93
94 pub async fn cargo_new(&self, name: &str, args: &[&str]) -> LuaResult<String> {
96 let mut full_args = vec![name];
97 full_args.extend_from_slice(args);
98 self.execute_cargo_command("new", &full_args).await
99 }
100
101 pub async fn cargo_run(&self, args: &[&str]) -> LuaResult<String> {
103 self.execute_cargo_command("run", args).await
104 }
105
106 pub async fn cargo_test(&self, args: &[&str]) -> LuaResult<String> {
108 self.execute_cargo_command("test", args).await
109 }
110
111 pub async fn cargo_update(&self, args: &[&str]) -> LuaResult<String> {
113 self.execute_cargo_command("update", args).await
114 }
115
116 pub async fn cargo_check(&self, args: &[&str]) -> LuaResult<String> {
120 self.execute_cargo_command("check", args).await
121 }
122
123 pub async fn cargo_init(&self, args: &[&str]) -> LuaResult<String> {
125 self.execute_cargo_command("init", args).await
126 }
127
128 pub async fn cargo_add(&self, args: &[&str]) -> LuaResult<String> {
130 self.execute_cargo_command("add", args).await
131 }
132
133 pub async fn cargo_remove(&self, args: &[&str]) -> LuaResult<String> {
135 self.execute_cargo_command("remove", args).await
136 }
137
138 pub async fn cargo_fmt(&self, args: &[&str]) -> LuaResult<String> {
140 self.execute_cargo_command("fmt", args).await
141 }
142
143 pub async fn cargo_clippy(&self, args: &[&str]) -> LuaResult<String> {
145 self.execute_cargo_command("clippy", args).await
146 }
147
148 pub async fn cargo_fix(&self, args: &[&str]) -> LuaResult<String> {
150 self.execute_cargo_command("fix", args).await
151 }
152
153 pub async fn cargo_publish(&self, args: &[&str]) -> LuaResult<String> {
155 self.execute_cargo_command("publish", args).await
156 }
157
158 pub async fn cargo_install(&self, args: &[&str]) -> LuaResult<String> {
160 self.execute_cargo_command("install", args).await
161 }
162
163 pub async fn cargo_uninstall(&self, args: &[&str]) -> LuaResult<String> {
165 self.execute_cargo_command("uninstall", args).await
166 }
167
168 pub async fn cargo_search(&self, args: &[&str]) -> LuaResult<String> {
170 self.execute_cargo_command("search", args).await
171 }
172
173 pub async fn cargo_tree(&self, args: &[&str]) -> LuaResult<String> {
175 self.execute_cargo_command("tree", args).await
176 }
177
178 pub async fn cargo_vendor(&self, args: &[&str]) -> LuaResult<String> {
180 self.execute_cargo_command("vendor", args).await
181 }
182
183 pub async fn cargo_audit(&self, args: &[&str]) -> LuaResult<String> {
185 self.execute_cargo_command("audit", args).await
186 }
187
188 pub async fn cargo_outdated(&self, args: &[&str]) -> LuaResult<String> {
190 self.execute_cargo_command("outdated", args).await
191 }
192
193 pub async fn cargo_help(&self, args: &[&str]) -> LuaResult<String> {
195 self.execute_cargo_command("help", args).await
196 }
197
198 pub async fn cargo_autodd(&self, args: &[&str]) -> LuaResult<String> {
200 let check_output = Command::new("cargo").arg("--list").output().map_err(|e| {
202 LuaError::RuntimeError(format!("Failed to check cargo commands: {}", e))
203 })?;
204
205 let output_str = String::from_utf8_lossy(&check_output.stdout);
206 if !output_str.contains("autodd") {
207 return Err(LuaError::RuntimeError(
208 "cargo-autodd is not installed. Please install it with 'cargo install cargo-autodd'"
209 .to_string(),
210 ));
211 }
212
213 self.execute_cargo_command("autodd", args).await
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 fn setup_test_commands() -> CargoCommands {
222 CargoCommands::new().unwrap()
223 }
224
225 #[test]
226 fn test_cargo_command_execution() {
227 let rt = tokio::runtime::Runtime::new().unwrap();
228 let cargo_commands = setup_test_commands();
229 let result = rt.block_on(async { cargo_commands.cargo_help(&[]).await });
230 assert!(result.is_ok());
231 }
232
233 #[test]
234 fn test_invalid_command() {
235 let rt = tokio::runtime::Runtime::new().unwrap();
236 let cargo_commands = setup_test_commands();
237 let result =
238 rt.block_on(async { cargo_commands.execute_cargo_command("invalid", &[]).await });
239 assert!(result.is_err());
240 }
241
242 #[test]
243 fn test_execute_method() {
244 let cargo_commands = setup_test_commands();
245 let result = cargo_commands.execute(async { Ok::<_, LuaError>("test".to_string()) });
246 assert!(result.is_ok());
247 assert_eq!(result.unwrap(), "test");
248 }
249
250 #[test]
251 fn test_cargo_autodd() {
252 let rt = tokio::runtime::Runtime::new().unwrap();
253 let cargo_commands = setup_test_commands();
254 let result = rt.block_on(async { cargo_commands.cargo_autodd(&[]).await });
255 assert!(result.is_err());
256 let err_msg = result.unwrap_err().to_string().to_lowercase();
257
258 assert!(
260 err_msg.contains("failed to check cargo commands")
261 || err_msg.contains("cargo-autodd is not installed")
262 || err_msg.contains("no valid version found")
263 || err_msg.contains("cargo autodd failed")
264 || err_msg.contains("command not found") || err_msg.contains("no such file or directory"), "Unexpected error message: {}",
267 err_msg
268 );
269 }
270
271 #[test]
272 fn test_cargo_autodd_with_args() {
273 let rt = tokio::runtime::Runtime::new().unwrap();
274 let cargo_commands = setup_test_commands();
275 let test_args = vec![
276 vec!["update"],
277 vec!["report"],
278 vec!["security"],
279 vec!["--debug"],
280 vec!["update", "--debug"],
281 ];
282
283 for args in test_args {
284 let result = rt.block_on(async { cargo_commands.cargo_autodd(&args).await });
285 assert!(result.is_err());
286 let err_msg = result.unwrap_err().to_string().to_lowercase();
287
288 assert!(
289 err_msg.contains("failed to check cargo commands")
290 || err_msg.contains("cargo-autodd is not installed")
291 || err_msg.contains("no valid version found")
292 || err_msg.contains("cargo autodd failed")
293 || err_msg.contains("command not found") || err_msg.contains("no such file or directory") || err_msg.contains("status code 404"),
296 "Unexpected error message: {}",
297 err_msg
298 );
299 }
300 }
301}