1use super::{Tool, Result, ToolError, common_options, parse_output_format, OutputFormat};
2use clap::{Arg, ArgMatches, Command};
3use colored::*;
4use std::path::Path;
5use std::fs;
6use std::env;
7use std::process::Command as ProcessCommand;
8use std::collections::HashMap;
9use serde::{Deserialize, Serialize};
10#[derive(Debug, Clone)]
11pub struct EnvCheckTool;
12#[derive(Debug, Clone, Serialize, Deserialize)]
13struct EnvironmentReport {
14 overall_status: String,
15 checks_passed: usize,
16 checks_failed: usize,
17 checks_warning: usize,
18 rust_toolchain: RustToolchainInfo,
19 system_info: SystemInfo,
20 dependencies: DependencyStatus,
21 configuration: ConfigStatus,
22 recommendations: Vec<String>,
23 issues: Vec<EnvironmentIssue>,
24 timestamp: String,
25}
26#[derive(Debug, Clone, Serialize, Deserialize)]
27struct RustToolchainInfo {
28 version: String,
29 channel: String,
30 target: String,
31 components: Vec<String>,
32 is_compatible: bool,
33 issues: Vec<String>,
34}
35#[derive(Debug, Clone, Serialize, Deserialize)]
36struct SystemInfo {
37 os: String,
38 arch: String,
39 memory_gb: u64,
40 available_disk_gb: u64,
41 cpu_cores: usize,
42}
43#[derive(Debug, Clone, Serialize, Deserialize)]
44struct DependencyStatus {
45 required_tools: HashMap<String, ToolStatus>,
46 optional_tools: HashMap<String, ToolStatus>,
47 rust_components: HashMap<String, ComponentStatus>,
48}
49#[derive(Debug, Clone, Serialize, Deserialize)]
50struct ConfigStatus {
51 cargo_config_exists: bool,
52 rustfmt_config_exists: bool,
53 clippy_config_exists: bool,
54 environment_variables: HashMap<String, EnvVarStatus>,
55}
56#[derive(Debug, Clone, Serialize, Deserialize)]
57struct ToolStatus {
58 installed: bool,
59 version: Option<String>,
60 required_version: Option<String>,
61 status: String,
62 path: Option<String>,
63}
64#[derive(Debug, Clone, Serialize, Deserialize)]
65struct ComponentStatus {
66 installed: bool,
67 status: String,
68}
69#[derive(Debug, Clone, Serialize, Deserialize)]
70struct EnvVarStatus {
71 set: bool,
72 value: Option<String>,
73 required: bool,
74 masked: bool,
75}
76#[derive(Debug, Clone, Serialize, Deserialize)]
77struct EnvironmentIssue {
78 severity: String,
79 category: String,
80 message: String,
81 solution: String,
82 command: Option<String>,
83}
84impl EnvCheckTool {
85 pub fn new() -> Self {
86 Self
87 }
88 fn check_rust_toolchain(&self) -> Result<RustToolchainInfo> {
89 let mut issues = Vec::new();
90 let version_output = ProcessCommand::new("rustc")
91 .arg("--version")
92 .output()
93 .map_err(|_| ToolError::ExecutionFailed("rustc not found".to_string()))?;
94 let version_str = String::from_utf8_lossy(&version_output.stdout);
95 let version = version_str
96 .split_whitespace()
97 .nth(1)
98 .unwrap_or("unknown")
99 .to_string();
100 let is_compatible = self.is_rust_version_compatible(&version);
101 if !is_compatible {
102 issues.push(format!("Rust version {} may be outdated", version));
103 }
104 let channel = if version.contains("nightly") {
105 "nightly".to_string()
106 } else if version.contains("beta") {
107 "beta".to_string()
108 } else {
109 "stable".to_string()
110 };
111 let target_output = ProcessCommand::new("rustc")
112 .args(&["--print", "target-list"])
113 .output()
114 .ok();
115 let target = target_output
116 .and_then(|output| {
117 String::from_utf8_lossy(&output.stdout)
118 .lines()
119 .next()
120 .map(|s| s.to_string())
121 })
122 .unwrap_or_else(|| "unknown".to_string());
123 let components = self.check_rust_components()?;
124 Ok(RustToolchainInfo {
125 version,
126 channel,
127 target,
128 components,
129 is_compatible,
130 issues,
131 })
132 }
133 fn is_rust_version_compatible(&self, version: &str) -> bool {
134 if let Some(version_part) = version.split('+').next() {
135 let parts: Vec<&str> = version_part.split('.').collect();
136 if parts.len() >= 2 {
137 if let (Ok(major), Ok(minor)) = (
138 parts[0].parse::<u32>(),
139 parts[1].parse::<u32>(),
140 ) {
141 return major > 1 || (major == 1 && minor >= 70);
142 }
143 }
144 }
145 false
146 }
147 fn check_rust_components(&self) -> Result<Vec<String>> {
148 let output = ProcessCommand::new("rustup")
149 .args(&["component", "list", "--installed"])
150 .output()
151 .map_err(|_| ToolError::ExecutionFailed("rustup not found".to_string()))?;
152 let components = String::from_utf8_lossy(&output.stdout)
153 .lines()
154 .map(|line| line.trim().to_string())
155 .filter(|line| !line.is_empty())
156 .collect();
157 Ok(components)
158 }
159 fn check_system_info(&self) -> Result<SystemInfo> {
160 let os = env::consts::OS.to_string();
161 let arch = env::consts::ARCH.to_string();
162 let memory_gb = self.get_memory_info();
163 let available_disk_gb = self.get_disk_info();
164 let cpu_cores = self.get_cpu_cores_fallback();
165 Ok(SystemInfo {
166 os,
167 arch,
168 memory_gb,
169 available_disk_gb,
170 cpu_cores,
171 })
172 }
173 fn get_memory_info(&self) -> u64 {
174 if cfg!(target_os = "linux") {
175 if let Ok(contents) = fs::read_to_string("/proc/meminfo") {
176 for line in contents.lines() {
177 if line.starts_with("MemTotal:") {
178 if let Some(kb_str) = line.split_whitespace().nth(1) {
179 if let Ok(kb) = kb_str.parse::<u64>() {
180 return kb / 1024 / 1024;
181 }
182 }
183 }
184 }
185 }
186 }
187 0
188 }
189 fn get_disk_info(&self) -> u64 {
190 if let Ok(stat) = fs::metadata(".") { 100 } else { 0 }
191 }
192 fn get_cpu_cores_fallback(&self) -> usize {
193 if cfg!(target_os = "linux") {
194 if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") {
195 let processor_count = contents
196 .lines()
197 .filter(|line| line.starts_with("processor"))
198 .count();
199 if processor_count > 0 {
200 return processor_count;
201 }
202 }
203 } else if cfg!(target_os = "macos") {
204 if let Ok(output) = ProcessCommand::new("sysctl")
205 .args(&["-n", "hw.ncpu"])
206 .output()
207 {
208 if let Ok(core_str) = String::from_utf8(output.stdout) {
209 if let Ok(cores) = core_str.trim().parse::<usize>() {
210 return cores;
211 }
212 }
213 }
214 }
215 4
216 }
217 fn check_dependencies(&self) -> Result<DependencyStatus> {
218 let mut required_tools = HashMap::new();
219 let mut optional_tools = HashMap::new();
220 let mut rust_components = HashMap::new();
221 let required = vec!["cargo", "rustc", "rustup", "git"];
222 for tool in required {
223 required_tools.insert(tool.to_string(), self.check_tool(tool)?);
224 }
225 let optional = vec!["docker", "node", "npm", "yarn", "python", "python3"];
226 for tool in optional {
227 optional_tools.insert(tool.to_string(), self.check_tool(tool)?);
228 }
229 let components = vec!["rustfmt", "clippy", "rust-docs", "rust-analyzer"];
230 for component in components {
231 rust_components
232 .insert(component.to_string(), self.check_rust_component(component)?);
233 }
234 Ok(DependencyStatus {
235 required_tools,
236 optional_tools,
237 rust_components,
238 })
239 }
240 fn check_tool(&self, tool: &str) -> Result<ToolStatus> {
241 let output = ProcessCommand::new(tool).arg("--version").output();
242 match output {
243 Ok(result) => {
244 if result.status.success() {
245 let version = String::from_utf8_lossy(&result.stdout)
246 .lines()
247 .next()
248 .unwrap_or("unknown")
249 .to_string();
250 let path_output = ProcessCommand::new("which")
251 .arg(tool)
252 .output()
253 .ok();
254 let path = path_output
255 .and_then(|p| {
256 if p.status.success() {
257 String::from_utf8_lossy(&p.stdout).trim().to_string().into()
258 } else {
259 None
260 }
261 });
262 Ok(ToolStatus {
263 installed: true,
264 version: Some(version),
265 required_version: None,
266 status: "ok".to_string(),
267 path,
268 })
269 } else {
270 Ok(ToolStatus {
271 installed: false,
272 version: None,
273 required_version: None,
274 status: "not_found".to_string(),
275 path: None,
276 })
277 }
278 }
279 Err(_) => {
280 Ok(ToolStatus {
281 installed: false,
282 version: None,
283 required_version: None,
284 status: "not_found".to_string(),
285 path: None,
286 })
287 }
288 }
289 }
290 fn check_rust_component(&self, component: &str) -> Result<ComponentStatus> {
291 let output = ProcessCommand::new("rustup")
292 .args(&["component", "list", "--installed"])
293 .output()
294 .map_err(|_| ToolError::ExecutionFailed("rustup not found".to_string()))?;
295 let installed_components = String::from_utf8_lossy(&output.stdout);
296 let installed = installed_components.contains(component);
297 Ok(ComponentStatus {
298 installed,
299 status: if installed { "ok".to_string() } else { "missing".to_string() },
300 })
301 }
302 fn check_configuration(&self) -> Result<ConfigStatus> {
303 let cargo_config_exists = Path::new("Cargo.toml").exists();
304 let rustfmt_config_exists = Path::new(".rustfmt.toml").exists()
305 || Path::new("rustfmt.toml").exists();
306 let clippy_config_exists = Path::new(".clippy.toml").exists()
307 || Path::new("clippy.toml").exists();
308 let env_vars = vec!["RUST_BACKTRACE", "CARGO_HOME", "RUSTUP_HOME", "PATH"];
309 let mut environment_variables = HashMap::new();
310 for var in env_vars {
311 let value = env::var(var).ok();
312 let set = value.is_some();
313 let required = matches!(var, "PATH");
314 let masked = matches!(var, "RUST_BACKTRACE" | "CARGO_HOME" | "RUSTUP_HOME");
315 environment_variables
316 .insert(
317 var.to_string(),
318 EnvVarStatus {
319 set,
320 value: if masked {
321 Some("***masked***".to_string())
322 } else {
323 value
324 },
325 required,
326 masked,
327 },
328 );
329 }
330 Ok(ConfigStatus {
331 cargo_config_exists,
332 rustfmt_config_exists,
333 clippy_config_exists,
334 environment_variables,
335 })
336 }
337 fn analyze_issues(&self, report: &EnvironmentReport) -> Vec<EnvironmentIssue> {
338 let mut issues = Vec::new();
339 if !report.rust_toolchain.is_compatible {
340 issues
341 .push(EnvironmentIssue {
342 severity: "warning".to_string(),
343 category: "rust_toolchain".to_string(),
344 message: format!(
345 "Rust version {} may be outdated", report.rust_toolchain.version
346 ),
347 solution: "Update Rust to version 1.70.0 or later".to_string(),
348 command: Some("rustup update stable".to_string()),
349 });
350 }
351 for (tool, status) in &report.dependencies.required_tools {
352 if !status.installed {
353 issues
354 .push(EnvironmentIssue {
355 severity: "error".to_string(),
356 category: "dependencies".to_string(),
357 message: format!("Required tool '{}' is not installed", tool),
358 solution: format!(
359 "Install {} using your system package manager", tool
360 ),
361 command: None,
362 });
363 }
364 }
365 for (component, status) in &report.dependencies.rust_components {
366 if !status.installed {
367 issues
368 .push(EnvironmentIssue {
369 severity: "warning".to_string(),
370 category: "rust_components".to_string(),
371 message: format!(
372 "Rust component '{}' is not installed", component
373 ),
374 solution: format!(
375 "Install with: rustup component add {}", component
376 ),
377 command: Some(format!("rustup component add {}", component)),
378 });
379 }
380 }
381 if report.system_info.memory_gb < 4 {
382 issues
383 .push(EnvironmentIssue {
384 severity: "warning".to_string(),
385 category: "system".to_string(),
386 message: format!(
387 "Low memory: {}GB available", report.system_info.memory_gb
388 ),
389 solution: "Consider upgrading to at least 8GB RAM for better Rust compilation performance"
390 .to_string(),
391 command: None,
392 });
393 }
394 issues
395 }
396 fn generate_recommendations(&self, report: &EnvironmentReport) -> Vec<String> {
397 let mut recommendations = Vec::new();
398 recommendations
399 .push("šÆ Keep Rust updated to the latest stable version".to_string());
400 recommendations
401 .push(
402 "š Install rustfmt and clippy for code formatting and linting"
403 .to_string(),
404 );
405 recommendations
406 .push(
407 "š§ Set RUST_BACKTRACE=1 for better error messages during development"
408 .to_string(),
409 );
410 if !report
411 .dependencies
412 .optional_tools
413 .get("docker")
414 .map_or(false, |t| t.installed)
415 {
416 recommendations
417 .push(
418 "š³ Consider installing Docker for integration testing".to_string(),
419 );
420 }
421 if !report.configuration.rustfmt_config_exists {
422 recommendations
423 .push(
424 "š Create .rustfmt.toml for consistent code formatting"
425 .to_string(),
426 );
427 }
428 if !report.configuration.clippy_config_exists {
429 recommendations
430 .push("š Create .clippy.toml to configure linting rules".to_string());
431 }
432 recommendations
433 }
434 fn determine_overall_status(&self, report: &EnvironmentReport) -> String {
435 let critical_issues = report
436 .issues
437 .iter()
438 .filter(|i| i.severity == "error")
439 .count();
440 let warning_issues = report
441 .issues
442 .iter()
443 .filter(|i| i.severity == "warning")
444 .count();
445 if critical_issues > 0 {
446 "ā Issues Found".to_string()
447 } else if warning_issues > 0 {
448 "ā ļø Needs Attention".to_string()
449 } else {
450 "ā
All Good".to_string()
451 }
452 }
453}
454impl Tool for EnvCheckTool {
455 fn name(&self) -> &'static str {
456 "env-check"
457 }
458 fn description(&self) -> &'static str {
459 "Validate development environment and provide setup recommendations"
460 }
461 fn command(&self) -> Command {
462 Command::new(self.name())
463 .about(self.description())
464 .long_about(
465 "Comprehensive development environment validation for Rust projects. Checks Rust toolchain, system resources, required tools, and provides actionable recommendations for optimizing your development setup.",
466 )
467 .args(
468 &[
469 Arg::new("detailed")
470 .long("detailed")
471 .short('d')
472 .help("Show detailed system information")
473 .action(clap::ArgAction::SetTrue),
474 Arg::new("fix")
475 .long("fix")
476 .help("Attempt to automatically fix issues")
477 .action(clap::ArgAction::SetTrue),
478 Arg::new("export")
479 .long("export")
480 .help("Export environment report to file")
481 .value_name("FILE"),
482 Arg::new("check-only")
483 .long("check-only")
484 .help("Only run checks, don't provide recommendations")
485 .action(clap::ArgAction::SetTrue),
486 ],
487 )
488 .args(&common_options())
489 }
490 fn execute(&self, matches: &ArgMatches) -> Result<()> {
491 let detailed = matches.get_flag("detailed");
492 let fix = matches.get_flag("fix");
493 let export_file = matches.get_one::<String>("export");
494 let check_only = matches.get_flag("check-only");
495 let dry_run = matches.get_flag("dry-run");
496 let verbose = matches.get_flag("verbose");
497 let output_format = parse_output_format(matches);
498 println!(
499 "š {} - {}", "CargoMate EnvCheck".bold().blue(), self.description().cyan()
500 );
501 if verbose {
502 println!(" š Analyzing development environment...");
503 }
504 let rust_toolchain = self.check_rust_toolchain()?;
505 let system_info = self.check_system_info()?;
506 let dependencies = self.check_dependencies()?;
507 let configuration = self.check_configuration()?;
508 let mut report = EnvironmentReport {
509 overall_status: String::new(),
510 checks_passed: 0,
511 checks_failed: 0,
512 checks_warning: 0,
513 rust_toolchain,
514 system_info,
515 dependencies,
516 configuration,
517 recommendations: Vec::new(),
518 issues: Vec::new(),
519 timestamp: chrono::Utc::now().to_rfc3339(),
520 };
521 report.issues = self.analyze_issues(&report);
522 report.recommendations = if check_only {
523 Vec::new()
524 } else {
525 self.generate_recommendations(&report)
526 };
527 report.checks_passed = report
528 .dependencies
529 .required_tools
530 .values()
531 .filter(|t| t.installed)
532 .count()
533 + report.dependencies.optional_tools.values().filter(|t| t.installed).count()
534 + report
535 .dependencies
536 .rust_components
537 .values()
538 .filter(|c| c.installed)
539 .count();
540 report.checks_failed = report
541 .issues
542 .iter()
543 .filter(|i| i.severity == "error")
544 .count();
545 report.checks_warning = report
546 .issues
547 .iter()
548 .filter(|i| i.severity == "warning")
549 .count();
550 report.overall_status = self.determine_overall_status(&report);
551 if fix && !dry_run {
552 self.apply_fixes(&report.issues)?;
553 }
554 if let Some(file_path) = export_file {
555 if !dry_run {
556 let json_report = serde_json::to_string_pretty(&report)?;
557 fs::write(file_path, json_report)?;
558 println!(" š¾ Report exported to: {}", file_path.cyan());
559 }
560 }
561 match output_format {
562 OutputFormat::Human => {
563 self.display_human_report(&report, detailed, check_only);
564 }
565 OutputFormat::Json => {
566 let json_report = serde_json::to_string_pretty(&report)?;
567 println!("{}", json_report);
568 }
569 OutputFormat::Table => {
570 self.display_table_report(&report);
571 }
572 }
573 if report.checks_failed > 0 {
574 println!(
575 "\nā Environment check failed - {} critical issues found", report
576 .checks_failed
577 );
578 std::process::exit(1);
579 } else if report.checks_warning > 0 {
580 println!(
581 "\nā ļø Environment check completed with {} warnings", report
582 .checks_warning
583 );
584 } else {
585 println!("\nā
Environment check passed - all systems go!");
586 }
587 Ok(())
588 }
589}
590impl EnvCheckTool {
591 fn display_human_report(
592 &self,
593 report: &EnvironmentReport,
594 detailed: bool,
595 check_only: bool,
596 ) {
597 println!("\nš {}", "Environment Report".bold().underline());
598 println!("Status: {}", report.overall_status);
599 println!("Timestamp: {}", report.timestamp);
600 println!(
601 "Checks: ā
{} passed, ā {} failed, ā ļø {} warnings", report
602 .checks_passed, report.checks_failed, report.checks_warning
603 );
604 println!("\nš¦ {}", "Rust Toolchain".bold());
605 println!(" Version: {}", report.rust_toolchain.version);
606 println!(" Channel: {}", report.rust_toolchain.channel);
607 println!(" Target: {}", report.rust_toolchain.target);
608 println!(
609 " Compatible: {}", if report.rust_toolchain.is_compatible { "ā
Yes" }
610 else { "ā No" }
611 );
612 if detailed {
613 println!(" Components: {}", report.rust_toolchain.components.join(", "));
614 }
615 if !report.rust_toolchain.issues.is_empty() {
616 for issue in &report.rust_toolchain.issues {
617 println!(" ā ļø {}", issue.yellow());
618 }
619 }
620 if detailed {
621 println!("\nš» {}", "System Information".bold());
622 println!(" OS: {} {}", report.system_info.os, report.system_info.arch);
623 println!(" Memory: {} GB", report.system_info.memory_gb);
624 println!(
625 " Disk Space: {} GB available", report.system_info.available_disk_gb
626 );
627 println!(" CPU Cores: {}", report.system_info.cpu_cores);
628 }
629 println!("\nš¦ {}", "Dependencies".bold());
630 println!(" {}", "Required Tools:".underline());
631 for (tool, status) in &report.dependencies.required_tools {
632 let status_icon = if status.installed { "ā
" } else { "ā" };
633 let version = status
634 .version
635 .as_ref()
636 .map(|v| format!(" ({})", v))
637 .unwrap_or_default();
638 println!(" {} {} {}", status_icon, tool, version);
639 }
640 if detailed {
641 println!(" {}", "Optional Tools:".underline());
642 for (tool, status) in &report.dependencies.optional_tools {
643 let status_icon = if status.installed { "ā
" } else { "āŖ" };
644 let version = status
645 .version
646 .as_ref()
647 .map(|v| format!(" ({})", v))
648 .unwrap_or_default();
649 println!(" {} {} {}", status_icon, tool, version);
650 }
651 println!(" {}", "Rust Components:".underline());
652 for (component, status) in &report.dependencies.rust_components {
653 let status_icon = if status.installed { "ā
" } else { "ā" };
654 println!(" {} {}", status_icon, component);
655 }
656 }
657 println!("\nāļø {}", "Configuration".bold());
658 println!(
659 " Cargo.toml: {}", if report.configuration.cargo_config_exists {
660 "ā
Found" } else { "ā Missing" }
661 );
662 println!(
663 " Rustfmt config: {}", if report.configuration.rustfmt_config_exists {
664 "ā
Found" } else { "ā Missing" }
665 );
666 println!(
667 " Clippy config: {}", if report.configuration.clippy_config_exists {
668 "ā
Found" } else { "ā Missing" }
669 );
670 if detailed {
671 println!(" {}", "Environment Variables:".underline());
672 for (var, status) in &report.configuration.environment_variables {
673 let status_icon = if status.set { "ā
" } else { "ā" };
674 let default_value = "not set".to_string();
675 let value = status.value.as_ref().unwrap_or(&default_value);
676 let required = if status.required { " (required)" } else { "" };
677 println!(" {} {} = {}{}", status_icon, var, value, required);
678 }
679 }
680 if !report.issues.is_empty() {
681 println!("\nšØ {}", "Issues Found".bold());
682 for issue in &report.issues {
683 let severity_icon = match issue.severity.as_str() {
684 "error" => "ā",
685 "warning" => "ā ļø ",
686 _ => "ā¹ļø ",
687 };
688 println!(" {} {}", severity_icon, issue.message);
689 println!(" š” Solution: {}", issue.solution);
690 if let Some(cmd) = &issue.command {
691 println!(" š ļø Command: {}", cmd.cyan());
692 }
693 println!();
694 }
695 }
696 if !check_only && !report.recommendations.is_empty() {
697 println!("\nš” {}", "Recommendations".bold());
698 for recommendation in &report.recommendations {
699 println!(" ⢠{}", recommendation);
700 }
701 }
702 }
703 fn display_table_report(&self, report: &EnvironmentReport) {
704 println!(
705 "{:<25} {:<12} {:<12} {:<12}", "Category", "Status", "Passed", "Issues"
706 );
707 println!("{}", "ā".repeat(70));
708 let rust_status = if report.rust_toolchain.is_compatible {
709 "ā
OK"
710 } else {
711 "ā ļø WARN"
712 };
713 println!(
714 "{:<25} {:<12} {:<12} {:<12}", "Rust Toolchain", rust_status, "1/1", "0"
715 );
716 let deps_passed = report
717 .dependencies
718 .required_tools
719 .values()
720 .filter(|t| t.installed)
721 .count();
722 let deps_total = report.dependencies.required_tools.len();
723 let deps_status = if deps_passed == deps_total { "ā
OK" } else { "ā FAIL" };
724 println!(
725 "{:<25} {:<12} {:<12} {:<12}", "Required Tools", deps_status,
726 format!("{}/{}", deps_passed, deps_total), "0"
727 );
728 let comp_passed = report
729 .dependencies
730 .rust_components
731 .values()
732 .filter(|c| c.installed)
733 .count();
734 let comp_total = report.dependencies.rust_components.len();
735 let comp_status = if comp_passed == comp_total {
736 "ā
OK"
737 } else {
738 "ā ļø WARN"
739 };
740 println!(
741 "{:<25} {:<12} {:<12} {:<12}", "Rust Components", comp_status,
742 format!("{}/{}", comp_passed, comp_total), "0"
743 );
744 }
745 fn apply_fixes(&self, issues: &[EnvironmentIssue]) -> Result<()> {
746 println!("\nš§ {}", "Applying Automatic Fixes".bold());
747 for issue in issues {
748 if let Some(command) = &issue.command {
749 println!(" š ļø Running: {}", command.cyan());
750 let output = ProcessCommand::new("sh").arg("-c").arg(command).output();
751 match output {
752 Ok(result) => {
753 if result.status.success() {
754 println!(" ā
Success");
755 } else {
756 println!(
757 " ā Failed: {}", String::from_utf8_lossy(& result
758 .stderr)
759 );
760 }
761 }
762 Err(e) => {
763 println!(" ā Error: {}", e);
764 }
765 }
766 }
767 }
768 Ok(())
769 }
770}
771impl Default for EnvCheckTool {
772 fn default() -> Self {
773 Self::new()
774 }
775}