cargo_e/
e_command_builder.rs

1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5use crate::e_target::{CargoTarget, TargetKind, TargetOrigin};
6
7/// A builder that constructs a Cargo command for a given target.
8#[derive(Clone)]
9pub struct CargoCommandBuilder {
10    pub args: Vec<String>,
11    pub alternate_cmd: Option<String>,
12    pub execution_dir: Option<PathBuf>,
13    pub suppressed_flags: HashSet<String>,
14}
15impl Default for CargoCommandBuilder {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20impl CargoCommandBuilder {
21    /// Creates a new, empty builder.
22    pub fn new() -> Self {
23        CargoCommandBuilder {
24            args: Vec::new(),
25            alternate_cmd: None,
26            execution_dir: None,
27            suppressed_flags: HashSet::new(),
28        }
29    }
30
31    // /// Configures the command based on the provided CargoTarget.
32    // pub fn with_target(mut self, target: &CargoTarget) -> Self {
33    //     match target.kind {
34    //         CargoTargetKind::Example => {
35    //             self.args.push("run".into());
36    //             self.args.push("--example".into());
37    //             self.args.push(target.name.clone());
38    //         }
39    //         CargoTargetKind::Binary => {
40    //             self.args.push("run".into());
41    //             self.args.push("--bin".into());
42    //             self.args.push(target.name.clone());
43    //         }
44    //         CargoTargetKind::Test => {
45    //             self.args.push("test".into());
46    //             self.args.push(target.name.clone());
47    //         }
48    //         CargoTargetKind::Manifest => {
49    //             // For a manifest target, you might simply want to open or browse it.
50    //             // Adjust the behavior as needed.
51    //             self.args.push("manifest".into());
52    //         }
53    //     }
54
55    //     // If the target is "extended", add a --manifest-path flag
56    //     if target.extended {
57    //         self.args.push("--manifest-path".into());
58    //         self.args.push(target.manifest_path.clone());
59    //     }
60
61    //     // Optionally use the origin information if available.
62
63    //     if let Some(TargetOrigin::SubProject(ref path)) = target.origin {
64    //         self.args.push("--manifest-path".into());
65    //         self.args.push(path.display().to_string());
66    //     }
67
68    //     self
69    // }
70
71    /// Configure the command based on the target kind.
72    pub fn with_target(mut self, target: &CargoTarget) -> Self {
73        println!("Target origin: {:?}", target.origin);
74        match target.kind {
75            TargetKind::Unknown => {
76                return self;
77            }
78            TargetKind::Bench => {
79                // To run benchmarks, use the "bench" command.
80                self.alternate_cmd = Some("bench".to_string());
81                self.args.push(target.name.clone());
82            }
83            TargetKind::Test => {
84                self.args.push("test".into());
85                // Pass the target's name as a filter to run specific tests.
86                self.args.push(target.name.clone());
87            }
88            TargetKind::Example | TargetKind::ExtendedExample => {
89                self.args.push("run".into());
90                self.args.push("--example".into());
91                self.args.push(target.name.clone());
92                self.args.push("--manifest-path".into());
93                self.args.push(
94                    target
95                        .manifest_path
96                        .clone()
97                        .to_str()
98                        .unwrap_or_default()
99                        .to_owned(),
100                );
101            }
102            TargetKind::Binary | TargetKind::ExtendedBinary => {
103                self.args.push("run".into());
104                self.args.push("--bin".into());
105                self.args.push(target.name.clone());
106                self.args.push("--manifest-path".into());
107                self.args.push(
108                    target
109                        .manifest_path
110                        .clone()
111                        .to_str()
112                        .unwrap_or_default()
113                        .to_owned(),
114                );
115            }
116            TargetKind::Manifest => {
117                self.suppressed_flags.insert("quiet".to_string());
118                self.args.push("run".into());
119                self.args.push("--manifest-path".into());
120                self.args.push(
121                    target
122                        .manifest_path
123                        .clone()
124                        .to_str()
125                        .unwrap_or_default()
126                        .to_owned(),
127                );
128            }
129            TargetKind::ManifestTauriExample => {
130                self.suppressed_flags.insert("quiet".to_string());
131                self.args.push("run".into());
132                self.args.push("--example".into());
133                self.args.push(target.name.clone());
134                self.args.push("--manifest-path".into());
135                self.args.push(
136                    target
137                        .manifest_path
138                        .clone()
139                        .to_str()
140                        .unwrap_or_default()
141                        .to_owned(),
142                );
143            }
144            TargetKind::ManifestTauri => {
145                self.suppressed_flags.insert("quiet".to_string());
146                // Helper closure to check for tauri.conf.json in a directory.
147                let has_tauri_conf = |dir: &Path| -> bool { dir.join("tauri.conf.json").exists() };
148
149                // Try candidate's parent (if origin is SingleFile or DefaultBinary).
150                let candidate_dir_opt = match &target.origin {
151                    Some(TargetOrigin::SingleFile(path))
152                    | Some(TargetOrigin::DefaultBinary(path)) => path.parent(),
153                    _ => None,
154                };
155
156                if let Some(candidate_dir) = candidate_dir_opt {
157                    if has_tauri_conf(candidate_dir) {
158                        println!("Using candidate directory: {}", candidate_dir.display());
159                        self.execution_dir = Some(candidate_dir.to_path_buf());
160                    } else if let Some(manifest_parent) = target.manifest_path.parent() {
161                        if has_tauri_conf(manifest_parent) {
162                            println!("Using manifest parent: {}", manifest_parent.display());
163                            self.execution_dir = Some(manifest_parent.to_path_buf());
164                        } else if let Some(grandparent) = manifest_parent.parent() {
165                            if has_tauri_conf(grandparent) {
166                                println!("Using manifest grandparent: {}", grandparent.display());
167                                self.execution_dir = Some(grandparent.to_path_buf());
168                            } else {
169                                println!("No tauri.conf.json found in candidate, manifest parent, or grandparent; defaulting to manifest parent: {}", manifest_parent.display());
170                                self.execution_dir = Some(manifest_parent.to_path_buf());
171                            }
172                        } else {
173                            println!("No grandparent for manifest; defaulting to candidate directory: {}", candidate_dir.display());
174                            self.execution_dir = Some(candidate_dir.to_path_buf());
175                        }
176                    } else {
177                        println!(
178                            "No manifest parent found for: {}",
179                            target.manifest_path.display()
180                        );
181                    }
182                } else if let Some(manifest_parent) = target.manifest_path.parent() {
183                    if has_tauri_conf(manifest_parent) {
184                        println!("Using manifest parent: {}", manifest_parent.display());
185                        self.execution_dir = Some(manifest_parent.to_path_buf());
186                    } else if let Some(grandparent) = manifest_parent.parent() {
187                        if has_tauri_conf(grandparent) {
188                            println!("Using manifest grandparent: {}", grandparent.display());
189                            self.execution_dir = Some(grandparent.to_path_buf());
190                        } else {
191                            println!(
192                                "No tauri.conf.json found; defaulting to manifest parent: {}",
193                                manifest_parent.display()
194                            );
195                            self.execution_dir = Some(manifest_parent.to_path_buf());
196                        }
197                    }
198                } else {
199                    println!(
200                        "No manifest parent found for: {}",
201                        target.manifest_path.display()
202                    );
203                }
204                self.args.push("tauri".into());
205                self.args.push("dev".into());
206            }
207            TargetKind::ManifestDioxus => {
208                // For Dioxus targets, print the manifest path and set the execution directory
209                // to be the same directory as the manifest.
210                if let Some(manifest_parent) = target.manifest_path.parent() {
211                    println!("Manifest path: {}", target.manifest_path.display());
212                    println!(
213                        "Execution directory (same as manifest folder): {}",
214                        manifest_parent.display()
215                    );
216                    self.execution_dir = Some(manifest_parent.to_path_buf());
217                } else {
218                    println!(
219                        "No manifest parent found for: {}",
220                        target.manifest_path.display()
221                    );
222                }
223                self.alternate_cmd = Some("dx".to_string());
224                self.args.push("serve".into());
225                self = self.with_required_features(&target.manifest_path, target);
226            }
227            TargetKind::ManifestDioxusExample => {
228                // For Dioxus targets, print the manifest path and set the execution directory
229                // to be the same directory as the manifest.
230                if let Some(manifest_parent) = target.manifest_path.parent() {
231                    println!("Manifest path: {}", target.manifest_path.display());
232                    println!(
233                        "Execution directory (same as manifest folder): {}",
234                        manifest_parent.display()
235                    );
236                    self.execution_dir = Some(manifest_parent.to_path_buf());
237                } else {
238                    println!(
239                        "No manifest parent found for: {}",
240                        target.manifest_path.display()
241                    );
242                }
243                self.alternate_cmd = Some("dx".to_string());
244                self.args.push("serve".into());
245                self.args.push("--example".into());
246                self.args.push(target.name.clone());
247                self = self.with_required_features(&target.manifest_path, target);
248            }
249        }
250        self
251    }
252
253    /// Configure the command using CLI options.
254    pub fn with_cli(mut self, cli: &crate::Cli) -> Self {
255        if cli.quiet && !self.suppressed_flags.contains("quiet") {
256            // Insert --quiet right after "run" if present.
257            if let Some(pos) = self.args.iter().position(|arg| arg == "run") {
258                self.args.insert(pos + 1, "--quiet".into());
259            } else {
260                self.args.push("--quiet".into());
261            }
262        }
263        if cli.release {
264            // Insert --release right after the initial "run" command if applicable.
265            // For example, if the command already contains "run", insert "--release" after it.
266            if let Some(pos) = self.args.iter().position(|arg| arg == "run") {
267                self.args.insert(pos + 1, "--release".into());
268            } else {
269                // If not running a "run" command (like in the Tauri case), simply push it.
270                self.args.push("--release".into());
271            }
272        }
273        // Append extra arguments (if any) after a "--" separator.
274        if !cli.extra.is_empty() {
275            self.args.push("--".into());
276            self.args.extend(cli.extra.iter().cloned());
277        }
278        self
279    }
280    /// Append required features based on the manifest, target kind, and name.
281    /// This method queries your manifest helper function and, if features are found,
282    /// appends "--features" and the feature list.
283    pub fn with_required_features(mut self, manifest: &PathBuf, target: &CargoTarget) -> Self {
284        if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
285            manifest,
286            &target.kind,
287            &target.name,
288        ) {
289            self.args.push("--features".to_string());
290            self.args.push(features);
291        }
292        self
293    }
294
295    /// Appends extra arguments to the command.
296    pub fn with_extra_args(mut self, extra: &[String]) -> Self {
297        if !extra.is_empty() {
298            // Use "--" to separate Cargo arguments from target-specific arguments.
299            self.args.push("--".into());
300            self.args.extend(extra.iter().cloned());
301        }
302        self
303    }
304
305    /// Builds the final vector of command-line arguments.
306    pub fn build(self) -> Vec<String> {
307        self.args
308    }
309
310    /// Optionally, builds a std::process::Command.
311    pub fn build_command(self) -> Command {
312        let mut cmd = if let Some(alternate) = self.alternate_cmd {
313            Command::new(alternate)
314        } else {
315            Command::new("cargo")
316        };
317        cmd.args(self.args);
318        if let Some(dir) = self.execution_dir {
319            cmd.current_dir(dir);
320        }
321        cmd
322    }
323}
324
325// --- Example usage ---
326#[cfg(test)]
327mod tests {
328    use crate::e_target::TargetOrigin;
329
330    use super::*;
331
332    #[test]
333    fn test_command_builder_example() {
334        let target = CargoTarget {
335            name: "my_example".to_string(),
336            display_name: "My Example".to_string(),
337            manifest_path: "Cargo.toml".into(),
338            kind: TargetKind::Example,
339            extended: true,
340            origin: Some(TargetOrigin::SingleFile(PathBuf::from(
341                "examples/my_example.rs",
342            ))),
343        };
344
345        let extra_args = vec!["--flag".to_string(), "value".to_string()];
346
347        let args = CargoCommandBuilder::new()
348            .with_target(&target)
349            .with_extra_args(&extra_args)
350            .build();
351
352        // For an example target, we expect something like:
353        // cargo run --example my_example --manifest-path Cargo.toml -- --flag value
354        assert!(args.contains(&"run".to_string()));
355        assert!(args.contains(&"--example".to_string()));
356        assert!(args.contains(&"my_example".to_string()));
357        assert!(args.contains(&"--manifest-path".to_string()));
358        assert!(args.contains(&"Cargo.toml".to_string()));
359        assert!(args.contains(&"--".to_string()));
360        assert!(args.contains(&"--flag".to_string()));
361        assert!(args.contains(&"value".to_string()));
362    }
363}