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}
35impl Default for CargoCommandBuilder {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40impl CargoCommandBuilder {
41    /// Creates a new, empty builder.
42    pub fn new() -> Self {
43        CargoCommandBuilder { args: Vec::new() }
44    }
45
46    /// Configures the command based on the provided CargoTarget.
47    pub fn with_target(mut self, target: &CargoTarget) -> Self {
48        match target.kind {
49            TargetKind::Example => {
50                self.args.push("run".into());
51                self.args.push("--example".into());
52                self.args.push(target.name.clone());
53            }
54            TargetKind::Binary => {
55                self.args.push("run".into());
56                self.args.push("--bin".into());
57                self.args.push(target.name.clone());
58            }
59            TargetKind::Test => {
60                self.args.push("test".into());
61                self.args.push(target.name.clone());
62            }
63            TargetKind::Manifest => {
64                // For a manifest target, you might simply want to open or browse it.
65                // Adjust the behavior as needed.
66                self.args.push("manifest".into());
67            }
68        }
69
70        // If the target is "extended", add a --manifest-path flag
71        if target.extended {
72            self.args.push("--manifest-path".into());
73            self.args.push(target.manifest_path.clone());
74        }
75
76        // Optionally use the origin information if available.
77
78        if let Some(TargetOrigin::SubProject(ref path)) = target.origin {
79            self.args.push("--manifest-path".into());
80            self.args.push(path.display().to_string());
81        }
82
83        self
84    }
85
86    /// Appends extra arguments to the command.
87    pub fn with_extra_args(mut self, extra: &[String]) -> Self {
88        if !extra.is_empty() {
89            // Use "--" to separate Cargo arguments from target-specific arguments.
90            self.args.push("--".into());
91            self.args.extend(extra.iter().cloned());
92        }
93        self
94    }
95
96    /// Builds the final vector of command-line arguments.
97    pub fn build(self) -> Vec<String> {
98        self.args
99    }
100
101    /// Optionally, builds a std::process::Command.
102    pub fn build_command(self) -> Command {
103        let mut cmd = Command::new("cargo");
104        cmd.args(self.args);
105        cmd
106    }
107}
108
109// --- Example usage ---
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test_command_builder_example() {
116        let target = CargoTarget {
117            name: "my_example".to_string(),
118            display_name: "My Example".to_string(),
119            manifest_path: "Cargo.toml".to_string(),
120            kind: TargetKind::Example,
121            extended: true,
122            origin: Some(TargetOrigin::SingleFile(PathBuf::from(
123                "examples/my_example.rs",
124            ))),
125        };
126
127        let extra_args = vec!["--flag".to_string(), "value".to_string()];
128
129        let args = CargoCommandBuilder::new()
130            .with_target(&target)
131            .with_extra_args(&extra_args)
132            .build();
133
134        // For an example target, we expect something like:
135        // cargo run --example my_example --manifest-path Cargo.toml -- --flag value
136        assert!(args.contains(&"run".to_string()));
137        assert!(args.contains(&"--example".to_string()));
138        assert!(args.contains(&"my_example".to_string()));
139        assert!(args.contains(&"--manifest-path".to_string()));
140        assert!(args.contains(&"Cargo.toml".to_string()));
141        assert!(args.contains(&"--".to_string()));
142        assert!(args.contains(&"--flag".to_string()));
143        assert!(args.contains(&"value".to_string()));
144    }
145}