cargo_nvim/
cargo_commands.rs

1// src/cargo_commands.rs
2use mlua::prelude::*;
3use std::process::Command;
4use std::sync::Arc;
5use tokio::runtime::Runtime;
6
7/// Structure for handling Cargo commands
8/// Contains a runtime for async operations
9#[derive(Clone)]
10pub struct CargoCommands {
11    runtime: Arc<Runtime>,
12}
13
14impl CargoCommands {
15    /// Create a new CargoCommands instance
16    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    /// Executes a future on the runtime
28    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    /// Execute a Cargo command with the given arguments
36    #[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    /// Execute a Cargo command with the given arguments (public for testing)
42    #[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    /// Internal implementation of execute_cargo_command
48    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    // Basic Cargo Commands
73
74    /// Run benchmarks
75    pub async fn cargo_bench(&self, args: &[&str]) -> LuaResult<String> {
76        self.execute_cargo_command("bench", args).await
77    }
78
79    /// Build the project
80    pub async fn cargo_build(&self, args: &[&str]) -> LuaResult<String> {
81        self.execute_cargo_command("build", args).await
82    }
83
84    /// Clean the target directory
85    pub async fn cargo_clean(&self, args: &[&str]) -> LuaResult<String> {
86        self.execute_cargo_command("clean", args).await
87    }
88
89    /// Generate documentation
90    pub async fn cargo_doc(&self, args: &[&str]) -> LuaResult<String> {
91        self.execute_cargo_command("doc", args).await
92    }
93
94    /// Create a new package
95    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    /// Run the project
102    pub async fn cargo_run(&self, args: &[&str]) -> LuaResult<String> {
103        self.execute_cargo_command("run", args).await
104    }
105
106    /// Run the tests
107    pub async fn cargo_test(&self, args: &[&str]) -> LuaResult<String> {
108        self.execute_cargo_command("test", args).await
109    }
110
111    /// Update dependencies
112    pub async fn cargo_update(&self, args: &[&str]) -> LuaResult<String> {
113        self.execute_cargo_command("update", args).await
114    }
115
116    // Additional Cargo Commands
117
118    /// Check the project for errors
119    pub async fn cargo_check(&self, args: &[&str]) -> LuaResult<String> {
120        self.execute_cargo_command("check", args).await
121    }
122
123    /// Initialize a new package in an existing directory
124    pub async fn cargo_init(&self, args: &[&str]) -> LuaResult<String> {
125        self.execute_cargo_command("init", args).await
126    }
127
128    /// Add dependencies to a manifest file
129    pub async fn cargo_add(&self, args: &[&str]) -> LuaResult<String> {
130        self.execute_cargo_command("add", args).await
131    }
132
133    /// Remove dependencies from a manifest file
134    pub async fn cargo_remove(&self, args: &[&str]) -> LuaResult<String> {
135        self.execute_cargo_command("remove", args).await
136    }
137
138    /// Format Rust code
139    pub async fn cargo_fmt(&self, args: &[&str]) -> LuaResult<String> {
140        self.execute_cargo_command("fmt", args).await
141    }
142
143    /// Run the Clippy linter
144    pub async fn cargo_clippy(&self, args: &[&str]) -> LuaResult<String> {
145        self.execute_cargo_command("clippy", args).await
146    }
147
148    /// Automatically fix lint warnings
149    pub async fn cargo_fix(&self, args: &[&str]) -> LuaResult<String> {
150        self.execute_cargo_command("fix", args).await
151    }
152
153    /// Package and upload crate to registry
154    pub async fn cargo_publish(&self, args: &[&str]) -> LuaResult<String> {
155        self.execute_cargo_command("publish", args).await
156    }
157
158    /// Install a Rust binary
159    pub async fn cargo_install(&self, args: &[&str]) -> LuaResult<String> {
160        self.execute_cargo_command("install", args).await
161    }
162
163    /// Uninstall a Rust binary
164    pub async fn cargo_uninstall(&self, args: &[&str]) -> LuaResult<String> {
165        self.execute_cargo_command("uninstall", args).await
166    }
167
168    /// Search packages in registry
169    pub async fn cargo_search(&self, args: &[&str]) -> LuaResult<String> {
170        self.execute_cargo_command("search", args).await
171    }
172
173    /// Display dependency tree
174    pub async fn cargo_tree(&self, args: &[&str]) -> LuaResult<String> {
175        self.execute_cargo_command("tree", args).await
176    }
177
178    /// Vendor all dependencies locally
179    pub async fn cargo_vendor(&self, args: &[&str]) -> LuaResult<String> {
180        self.execute_cargo_command("vendor", args).await
181    }
182
183    /// Audit dependencies for security vulnerabilities
184    pub async fn cargo_audit(&self, args: &[&str]) -> LuaResult<String> {
185        self.execute_cargo_command("audit", args).await
186    }
187
188    /// Show outdated dependencies
189    pub async fn cargo_outdated(&self, args: &[&str]) -> LuaResult<String> {
190        self.execute_cargo_command("outdated", args).await
191    }
192
193    /// Get Cargo help
194    pub async fn cargo_help(&self, args: &[&str]) -> LuaResult<String> {
195        self.execute_cargo_command("help", args).await
196    }
197
198    /// Run cargo-autodd command
199    pub async fn cargo_autodd(&self, args: &[&str]) -> LuaResult<String> {
200        // Check if cargo-autodd is installed
201        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        // More flexible error message checking
259        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") // For Docker environment
265                || err_msg.contains("no such file or directory"), // For Docker environment
266            "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") // For Docker environment
294                    || err_msg.contains("no such file or directory") // For Docker environment
295                    || err_msg.contains("status code 404"),
296                "Unexpected error message: {}",
297                err_msg
298            );
299        }
300    }
301}