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