cargo_e/
e_command_builder.rs1use std::collections::HashSet;
2use std::io::Read;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5use which::which;
6
7use crate::e_target::{CargoTarget, TargetKind, TargetOrigin};
8
9#[derive(Clone)]
11pub struct CargoCommandBuilder {
12 pub args: Vec<String>,
13 pub alternate_cmd: Option<String>,
14 pub execution_dir: Option<PathBuf>,
15 pub suppressed_flags: HashSet<String>,
16}
17impl Default for CargoCommandBuilder {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22impl CargoCommandBuilder {
23 pub fn new() -> Self {
25 CargoCommandBuilder {
26 args: Vec::new(),
27 alternate_cmd: None,
28 execution_dir: None,
29 suppressed_flags: HashSet::new(),
30 }
31 }
32
33 pub fn with_target(mut self, target: &CargoTarget) -> Self {
75 if let Some(origin) = target.origin.clone() {
76 println!("Target origin: {:?}", origin);
77 } else {
78 println!("Target origin is not set");
79 }
80 match target.kind {
81 TargetKind::Unknown => {
82 return self;
83 }
84 TargetKind::Bench => {
85 self.alternate_cmd = Some("bench".to_string());
87 self.args.push(target.name.clone());
88 }
89 TargetKind::Test => {
90 self.args.push("test".into());
91 self.args.push(target.name.clone());
93 }
94 TargetKind::Example | TargetKind::ExtendedExample => {
95 self.args.push("run".into());
96 self.args.push("--example".into());
98 self.args.push(target.name.clone());
99 self.args.push("--manifest-path".into());
100 self.args.push(
101 target
102 .manifest_path
103 .clone()
104 .to_str()
105 .unwrap_or_default()
106 .to_owned(),
107 );
108 }
109 TargetKind::Binary | TargetKind::ExtendedBinary => {
110 self.args.push("run".into());
111 self.args.push("--bin".into());
112 self.args.push(target.name.clone());
113 self.args.push("--manifest-path".into());
114 self.args.push(
115 target
116 .manifest_path
117 .clone()
118 .to_str()
119 .unwrap_or_default()
120 .to_owned(),
121 );
122 }
123 TargetKind::Manifest => {
124 self.suppressed_flags.insert("quiet".to_string());
125 self.args.push("run".into());
126 self.args.push("--manifest-path".into());
127 self.args.push(
128 target
129 .manifest_path
130 .clone()
131 .to_str()
132 .unwrap_or_default()
133 .to_owned(),
134 );
135 }
136 TargetKind::ManifestTauriExample => {
137 self.suppressed_flags.insert("quiet".to_string());
138 self.args.push("run".into());
139 self.args.push("--example".into());
140 self.args.push(target.name.clone());
141 self.args.push("--manifest-path".into());
142 self.args.push(
143 target
144 .manifest_path
145 .clone()
146 .to_str()
147 .unwrap_or_default()
148 .to_owned(),
149 );
150 }
151 TargetKind::ManifestTauri => {
152 self.suppressed_flags.insert("quiet".to_string());
153 let has_tauri_conf = |dir: &Path| -> bool { dir.join("tauri.conf.json").exists() };
155
156 let candidate_dir_opt = match &target.origin {
158 Some(TargetOrigin::SingleFile(path))
159 | Some(TargetOrigin::DefaultBinary(path)) => path.parent(),
160 _ => None,
161 };
162
163 if let Some(candidate_dir) = candidate_dir_opt {
164 if has_tauri_conf(candidate_dir) {
165 println!("Using candidate directory: {}", candidate_dir.display());
166 self.execution_dir = Some(candidate_dir.to_path_buf());
167 } else if let Some(manifest_parent) = target.manifest_path.parent() {
168 if has_tauri_conf(manifest_parent) {
169 println!("Using manifest parent: {}", manifest_parent.display());
170 self.execution_dir = Some(manifest_parent.to_path_buf());
171 } else if let Some(grandparent) = manifest_parent.parent() {
172 if has_tauri_conf(grandparent) {
173 println!("Using manifest grandparent: {}", grandparent.display());
174 self.execution_dir = Some(grandparent.to_path_buf());
175 } else {
176 println!("No tauri.conf.json found in candidate, manifest parent, or grandparent; defaulting to manifest parent: {}", manifest_parent.display());
177 self.execution_dir = Some(manifest_parent.to_path_buf());
178 }
179 } else {
180 println!("No grandparent for manifest; defaulting to candidate directory: {}", candidate_dir.display());
181 self.execution_dir = Some(candidate_dir.to_path_buf());
182 }
183 } else {
184 println!(
185 "No manifest parent found for: {}",
186 target.manifest_path.display()
187 );
188 }
189 } else if let Some(manifest_parent) = target.manifest_path.parent() {
190 if has_tauri_conf(manifest_parent) {
191 println!("Using manifest parent: {}", manifest_parent.display());
192 self.execution_dir = Some(manifest_parent.to_path_buf());
193 } else if let Some(grandparent) = manifest_parent.parent() {
194 if has_tauri_conf(grandparent) {
195 println!("Using manifest grandparent: {}", grandparent.display());
196 self.execution_dir = Some(grandparent.to_path_buf());
197 } else {
198 println!(
199 "No tauri.conf.json found; defaulting to manifest parent: {}",
200 manifest_parent.display()
201 );
202 self.execution_dir = Some(manifest_parent.to_path_buf());
203 }
204 }
205 } else {
206 println!(
207 "No manifest parent found for: {}",
208 target.manifest_path.display()
209 );
210 }
211 self.args.push("tauri".into());
212 self.args.push("dev".into());
213 }
214 TargetKind::ManifestLeptos => {
215 let readme_path = target
216 .manifest_path
217 .parent()
218 .map(|p| p.join("README.md"))
219 .filter(|p| p.exists())
220 .or_else(|| {
221 target
222 .manifest_path
223 .parent()
224 .map(|p| p.join("readme.md"))
225 .filter(|p| p.exists())
226 });
227
228 if let Some(readme) = readme_path {
229 if let Ok(mut file) = std::fs::File::open(&readme) {
230 let mut contents = String::new();
231 if file.read_to_string(&mut contents).is_ok()
232 && contents.contains("cargo leptos watch")
233 {
234 println!("Detected 'cargo leptos watch' in {}", readme.display());
236 self.execution_dir =
237 target.manifest_path.parent().map(|p| p.to_path_buf());
238 self.alternate_cmd = Some("cargo".to_string());
239 self.args.push("leptos".into());
240 self.args.push("watch".into());
241 self = self.with_required_features(&target.manifest_path, target);
242 return self;
243 }
244 }
245 }
246
247 let exe_path = match which("trunk") {
249 Ok(path) => path,
250 Err(err) => {
251 eprintln!("Error: 'trunk' not found in PATH: {}", err);
252 return self;
253 }
254 };
255
256 if let Some(manifest_parent) = target.manifest_path.parent() {
257 println!("Manifest path: {}", target.manifest_path.display());
258 println!(
259 "Execution directory (same as manifest folder): {}",
260 manifest_parent.display()
261 );
262 self.execution_dir = Some(manifest_parent.to_path_buf());
263 } else {
264 println!(
265 "No manifest parent found for: {}",
266 target.manifest_path.display()
267 );
268 }
269
270 self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
271 self.args.push("serve".into());
272 self.args.push("--open".into());
273 self = self.with_required_features(&target.manifest_path, target);
274 }
275 TargetKind::ManifestDioxus => {
276 let exe_path = match which("dx") {
277 Ok(path) => path,
278 Err(err) => {
279 eprintln!("Error: 'dx' not found in PATH: {}", err);
280 return self;
281 }
282 };
283 if let Some(manifest_parent) = target.manifest_path.parent() {
286 println!("Manifest path: {}", target.manifest_path.display());
287 println!(
288 "Execution directory (same as manifest folder): {}",
289 manifest_parent.display()
290 );
291 self.execution_dir = Some(manifest_parent.to_path_buf());
292 } else {
293 println!(
294 "No manifest parent found for: {}",
295 target.manifest_path.display()
296 );
297 }
298 self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
299 self.args.push("serve".into());
300 self = self.with_required_features(&target.manifest_path, target);
301 }
302 TargetKind::ManifestDioxusExample => {
303 let exe_path = match which("dx") {
304 Ok(path) => path,
305 Err(err) => {
306 eprintln!("Error: 'dx' not found in PATH: {}", err);
307 return self;
308 }
309 };
310 if let Some(manifest_parent) = target.manifest_path.parent() {
313 println!("Manifest path: {}", target.manifest_path.display());
314 println!(
315 "Execution directory (same as manifest folder): {}",
316 manifest_parent.display()
317 );
318 self.execution_dir = Some(manifest_parent.to_path_buf());
319 } else {
320 println!(
321 "No manifest parent found for: {}",
322 target.manifest_path.display()
323 );
324 }
325 self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
326 self.args.push("serve".into());
327 self.args.push("--example".into());
328 self.args.push(target.name.clone());
329 self = self.with_required_features(&target.manifest_path, target);
330 }
331 }
332 self
333 }
334
335 pub fn with_cli(mut self, cli: &crate::Cli) -> Self {
337 if cli.quiet && !self.suppressed_flags.contains("quiet") {
338 if let Some(pos) = self.args.iter().position(|arg| arg == "run") {
340 self.args.insert(pos + 1, "--quiet".into());
341 } else {
342 self.args.push("--quiet".into());
343 }
344 }
345 if cli.release {
346 if let Some(pos) = self.args.iter().position(|arg| arg == "run") {
349 self.args.insert(pos + 1, "--release".into());
350 } else {
351 self.args.push("--release".into());
353 }
354 }
355 if !cli.extra.is_empty() {
357 self.args.push("--".into());
358 self.args.extend(cli.extra.iter().cloned());
359 }
360 self
361 }
362 pub fn with_required_features(mut self, manifest: &PathBuf, target: &CargoTarget) -> Self {
366 if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
367 manifest,
368 &target.kind,
369 &target.name,
370 ) {
371 self.args.push("--features".to_string());
372 self.args.push(features);
373 }
374 self
375 }
376
377 pub fn with_extra_args(mut self, extra: &[String]) -> Self {
379 if !extra.is_empty() {
380 self.args.push("--".into());
382 self.args.extend(extra.iter().cloned());
383 }
384 self
385 }
386
387 pub fn build(self) -> Vec<String> {
389 self.args
390 }
391
392 pub fn build_command(self) -> Command {
394 let mut cmd = if let Some(alternate) = self.alternate_cmd {
395 Command::new(alternate)
396 } else {
397 Command::new("cargo")
398 };
399 cmd.args(self.args);
400 if let Some(dir) = self.execution_dir {
401 cmd.current_dir(dir);
402 }
403 cmd
404 }
405}
406
407#[cfg(test)]
409mod tests {
410 use crate::e_target::TargetOrigin;
411
412 use super::*;
413
414 #[test]
415 fn test_command_builder_example() {
416 let target = CargoTarget {
417 name: "my_example".to_string(),
418 display_name: "My Example".to_string(),
419 manifest_path: "Cargo.toml".into(),
420 kind: TargetKind::Example,
421 extended: true,
422 toml_specified: false,
423 origin: Some(TargetOrigin::SingleFile(PathBuf::from(
424 "examples/my_example.rs",
425 ))),
426 };
427
428 let extra_args = vec!["--flag".to_string(), "value".to_string()];
429
430 let args = CargoCommandBuilder::new()
431 .with_target(&target)
432 .with_extra_args(&extra_args)
433 .build();
434
435 assert!(args.contains(&"run".to_string()));
438 assert!(args.contains(&"--example".to_string()));
439 assert!(args.contains(&"my_example".to_string()));
440 assert!(args.contains(&"--manifest-path".to_string()));
441 assert!(args.contains(&"Cargo.toml".to_string()));
442 assert!(args.contains(&"--".to_string()));
443 assert!(args.contains(&"--flag".to_string()));
444 assert!(args.contains(&"value".to_string()));
445 }
446}