cargo_e/
e_command_builder.rs

1use std::ffi::OsString;
2use std::path::PathBuf;
3use std::process::Command;
4
5#[derive(Debug, Clone)]
6pub enum TargetOrigin {
7    SingleFile(PathBuf),
8    MultiFile(PathBuf),
9    SubProject(PathBuf),
10    Named(OsString),
11}
12
13#[derive(Debug, Clone)]
14pub enum TargetKind {
15    Example,
16    Binary,
17    Test,
18    Manifest, // For browsing the entire Cargo.toml or package-level targets.
19}
20
21#[derive(Debug, Clone)]
22pub struct CargoTarget {
23    pub name: String,
24    pub display_name: String,
25    pub manifest_path: String,
26    pub kind: TargetKind,
27    pub extended: bool,
28    pub origin: Option<TargetOrigin>, // Captures where the target was discovered.
29}
30
31/// A builder that constructs a Cargo command for a given target.
32pub struct CargoCommandBuilder {
33    args: Vec<String>,
34}
35
36impl CargoCommandBuilder {
37    /// Creates a new, empty builder.
38    pub fn new() -> Self {
39        CargoCommandBuilder { args: Vec::new() }
40    }
41
42    /// Configures the command based on the provided CargoTarget.
43    pub fn with_target(mut self, target: &CargoTarget) -> Self {
44        match target.kind {
45            TargetKind::Example => {
46                self.args.push("run".into());
47                self.args.push("--example".into());
48                self.args.push(target.name.clone());
49            }
50            TargetKind::Binary => {
51                self.args.push("run".into());
52                self.args.push("--bin".into());
53                self.args.push(target.name.clone());
54            }
55            TargetKind::Test => {
56                self.args.push("test".into());
57                self.args.push(target.name.clone());
58            }
59            TargetKind::Manifest => {
60                // For a manifest target, you might simply want to open or browse it.
61                // Adjust the behavior as needed.
62                self.args.push("manifest".into());
63            }
64        }
65
66        // If the target is "extended", add a --manifest-path flag
67        if target.extended {
68            self.args.push("--manifest-path".into());
69            self.args.push(target.manifest_path.clone());
70        }
71
72        // Optionally use the origin information if available.
73        if let Some(ref origin) = target.origin {
74            match origin {
75                // If it's a subproject, override the manifest path to point directly to that subproject.
76                TargetOrigin::SubProject(path) => {
77                    self.args.push("--manifest-path".into());
78                    self.args.push(path.to_string_lossy().to_string());
79                }
80                // For other variants, you might want to log or adjust behavior.
81                _ => { /* No additional flags needed for SingleFile, MultiFile, or Named */ }
82            }
83        }
84
85        self
86    }
87
88    /// Appends extra arguments to the command.
89    pub fn with_extra_args(mut self, extra: &[String]) -> Self {
90        if !extra.is_empty() {
91            // Use "--" to separate Cargo arguments from target-specific arguments.
92            self.args.push("--".into());
93            self.args.extend(extra.iter().cloned());
94        }
95        self
96    }
97
98    /// Builds the final vector of command-line arguments.
99    pub fn build(self) -> Vec<String> {
100        self.args
101    }
102
103    /// Optionally, builds a std::process::Command.
104    pub fn build_command(self) -> Command {
105        let mut cmd = Command::new("cargo");
106        cmd.args(self.args);
107        cmd
108    }
109}
110
111// --- Example usage ---
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_command_builder_example() {
118        let target = CargoTarget {
119            name: "my_example".to_string(),
120            display_name: "My Example".to_string(),
121            manifest_path: "Cargo.toml".to_string(),
122            kind: TargetKind::Example,
123            extended: true,
124            origin: Some(TargetOrigin::SingleFile(PathBuf::from(
125                "examples/my_example.rs",
126            ))),
127        };
128
129        let extra_args = vec!["--flag".to_string(), "value".to_string()];
130
131        let args = CargoCommandBuilder::new()
132            .with_target(&target)
133            .with_extra_args(&extra_args)
134            .build();
135
136        // For an example target, we expect something like:
137        // cargo run --example my_example --manifest-path Cargo.toml -- --flag value
138        assert!(args.contains(&"run".to_string()));
139        assert!(args.contains(&"--example".to_string()));
140        assert!(args.contains(&"my_example".to_string()));
141        assert!(args.contains(&"--manifest-path".to_string()));
142        assert!(args.contains(&"Cargo.toml".to_string()));
143        assert!(args.contains(&"--".to_string()));
144        assert!(args.contains(&"--flag".to_string()));
145        assert!(args.contains(&"value".to_string()));
146    }
147}